Especificamente em Python, como as variáveis são compartilhadas entre os threads?
Embora eu tenha usado threading.Thread
antes, nunca realmente entendi ou vi exemplos de como as variáveis são compartilhadas. Eles são compartilhados entre o tópico principal e os filhos ou apenas entre os filhos? Quando eu precisaria usar o armazenamento local do thread para evitar esse compartilhamento?
Já vi muitos avisos sobre a sincronização de acesso a dados compartilhados entre threads usando bloqueios, mas ainda não vi um exemplo realmente bom do problema.
Desde já, obrigado!
Respostas:
Em Python, tudo é compartilhado, exceto as variáveis locais da função (porque cada chamada de função obtém seu próprio conjunto de locais e os threads são sempre chamadas de função separadas.) E mesmo assim, apenas as próprias variáveis (os nomes que se referem aos objetos) são locais para a função; os próprios objetos são sempre globais e qualquer coisa pode se referir a eles. O
Thread
objeto para um determinado segmento não é um objeto especial a esse respeito. Se você armazenar oThread
objeto em algum lugar que todos os threads podem acessar (como uma variável global), então todos os threads podem acessar aqueleThread
objeto. Se você deseja modificar atomicamente qualquer coisa a que outro thread tenha acesso, você deve protegê-lo com um bloqueio. E todos os threads devem compartilhar exatamente esse mesmo bloqueio, ou não seria muito eficaz.Se você deseja armazenamento local de thread real, é aí que
threading.local
entra. Os atributos dethreading.local
não são compartilhados entre os threads; cada thread vê apenas os atributos que ela mesma colocou lá. Se você estiver curioso sobre sua implementação, a fonte está em _threading_local.py na biblioteca padrão.fonte
Considere o seguinte código:
Aqui, threading.local () é usado como uma maneira rápida e suja de passar alguns dados de run () para bar () sem alterar a interface de foo ().
Observe que usar variáveis globais não resolverá o problema:
Enquanto isso, se você pudesse passar esses dados como um argumento de foo (), seria uma maneira mais elegante e bem projetada:
Mas isso nem sempre é possível ao usar código de terceiros ou mal projetado.
fonte
Você pode criar o armazenamento local do thread usando
threading.local()
.Os dados armazenados nos tls serão exclusivos para cada thread, o que ajudará a garantir que o compartilhamento não intencional não ocorra.
fonte
Assim como em qualquer outra linguagem, cada thread em Python tem acesso às mesmas variáveis. Não há distinção entre o 'thread principal' e os threads filho.
Uma diferença com Python é que o Global Interpreter Lock significa que apenas um thread pode estar executando o código Python por vez. No entanto, isso não ajuda muito quando se trata de sincronizar o acesso, já que todos os problemas de preempção usuais ainda se aplicam e você precisa usar primitivas de threading como em outras linguagens. Isso significa que você precisa reconsiderar se estava usando threads para desempenho, no entanto.
fonte
Posso estar errado aqui. Se você souber de outra forma, explique, pois isso ajudaria a explicar por que seria necessário usar o thread local ().
Esta afirmação parece errada, não está errada: "Se você deseja modificar atomicamente qualquer coisa a que outro thread tenha acesso, você deve protegê-lo com um cadeado." Eu acho que esta afirmação é -> efetivamente <- correta, mas não totalmente correta. Achei que o termo "atômico" significava que o interpretador Python criava um fragmento de código de bytes que não deixava espaço para um sinal de interrupção para a CPU.
Achei que as operações atômicas são pedaços de código de bytes do Python que não dão acesso a interrupções. As instruções do Python como "running = True" são atômicas. Você não precisa bloquear a CPU de interrupções neste caso (eu acredito). A divisão do código de bytes do Python está protegida contra interrupção do thread.
O código Python como "threads_running [5] = True" não é atômico. Existem dois blocos de código de bytes Python aqui; um para remover a referência da lista () de um objeto e outro fragmento de código de byte para atribuir um valor a um objeto, neste caso um "lugar" em uma lista. Uma interrupção pode ser gerada -> entre <- os dois códigos de byte -> blocos <-. Isso é onde coisas ruins acontecem.
Como o thread local () se relaciona com "atômico"? É por isso que a declaração parece enganosa para mim. Se não, você pode explicar?
fonte