Pergunta

Se você está confiando em uma implementação de Python que tem um bloqueio global do intérprete (ou seja CPython) e escrever código multithreaded, você realmente precisa de bloqueios em tudo?

Não Se a GIL não permite múltiplas instruções a serem executadas em paralelo, os dados compartilhados não seria desnecessária para proteger?

desculpe se isso é uma pergunta estúpida, mas é algo que eu sempre quis saber sobre Python em multi-processador máquinas / Core.

mesma coisa se aplica a qualquer outra implementação da linguagem que tem uma GIL.

Foi útil?

Solução

Você ainda vai precisar de fechamentos se você compartilhar estado entre threads. A GIL só protege o intérprete internamente. Você ainda pode ter atualizações inconsistentes em seu próprio código.

Por exemplo:

#!/usr/bin/env python
import threading

shared_balance = 0

class Deposit(threading.Thread):
    def run(self):
        for _ in xrange(1000000):
            global shared_balance
            balance = shared_balance
            balance += 100
            shared_balance = balance

class Withdraw(threading.Thread):
    def run(self):
        for _ in xrange(1000000):
            global shared_balance
            balance = shared_balance
            balance -= 100
            shared_balance = balance

threads = [Deposit(), Withdraw()]

for thread in threads:
    thread.start()

for thread in threads:
    thread.join()

print shared_balance

Aqui, o código pode ser interrompida entre a leitura do estado compartilhado (balance = shared_balance) e escrever a parte de trás resultado alterado (shared_balance = balance), causando uma atualização perdida. O resultado é um valor aleatório para o estado compartilhado.

Para fazer as atualizações, executar métodos consistentes seria necessário para bloquear o estado compartilhado em torno das seções read-modify-write (dentro os loops) ou ter alguma maneira de detectar quando o estado compartilhado tinha mudado desde que foi lido .

Outras dicas

Não - o GIL apenas protege o interior do pitão de vários segmentos que alteram seu estado. Este é um baixo nível muito de bloqueio, suficiente apenas para manter próprias estruturas do Python em um estado consistente. Não abrange a aplicação nível de bloqueio que você precisa fazer para segurança do thread de cobertura em seu próprio código.

A essência do bloqueio é para garantir que um determinado bloco do código só é executado por uma thread. A GIL reforça esta para os blocos do tamanho de um único bytecode, mas geralmente você quer o bloqueio para abranger um bloco maior de código do que isso.

Somando-se a discussão:

Uma vez que o GIL existe, algumas operações são atômicas em Python e não precisa de um bloqueio.

http : //www.python.org/doc/faq/library/#what-kinds-of-global-value-mutation-are-thread-safe

Como afirmado por outras respostas, no entanto, você ainda necessidade de usar fechaduras sempre que a lógica de aplicação exige que eles (como em um / problema Produtor Consumidor).

A Global Interpreter Lock impede tópicos de acessar a intérprete simultaneamente (assim CPython nunca usa apenas um núcleo). No entanto, como eu o entendo, os fios são ainda interrompida e programado preventivamente , o que significa que ainda precisam de bloqueios em estruturas de dados compartilhados, para que os vossos tópicos pisar uns aos outros de dedos.

A resposta que eu encontrei uma e outra vez é que multithreading em Python é raramente vale a sobrecarga, por causa disto. Eu já ouvi coisas boas sobre o href="http://pyprocessing.berlios.de/" rel="noreferrer"> PyProcessing projeto , o que torna a execução de vários processos como "simples" como multithreading, com estruturas de dados compartilhados, filas, etc. (PyProcessing será introduzido na biblioteca padrão da próxima Python 2.6 como o multiprocessamento módulo .) Isto faz com que você em torno da GIL, uma vez que cada processo tem seu próprio intérprete.

Este post descreve o GIL em um alto nível bastante:

De particular interesse são estas citações:

A cada instruções dez (este padrão pode ser alterado), o núcleo libera o GIL para o segmento atual. Em que ponto, o sistema operacional escolhe um fio de todos os segmentos concorrentes para o bloqueio (Possivelmente escolhendo o mesmo fio que acaba de lançar o GIL - você não fazer tem qualquer controle sobre qual thread fica escolhido); esse segmento adquire a GIL e, em seguida, é executado por mais dez bytecodes.

e

Note cuidadosamente que o GIL única restringe código Python puro. extensões (Python externo bibliotecas geralmente escrita em C) que pode ser escrita libertar o bloqueio, o que permite então o interpretador Python para corrida separadamente a partir da extensão até a extensão readquire o bloqueio.

Parece que o GIL apenas fornece menos instâncias possíveis para uma mudança de contexto, e faz multi-core / sistemas de processador se comportam como um único núcleo, com respeito a cada instância interpretador Python, então sim, você ainda precisa de mecanismos de sincronização de uso .

Pense nisso desta maneira:

Em um computador único processador, multithreading acontece suspendendo um segmento e começando outro rápido o suficiente para torná-lo parecem estar em execução ao mesmo tempo. Isto é como Python com o GIL:. Único segmento está sempre funcionando realmente

O problema é que o segmento pode ser suspensa em qualquer lugar, por exemplo, se eu quiser computação b = (a + b) * 3, este produzir instruções podem algo como isto:

1    a += b
2    a *= 3
3    b = a

Agora, vamos dizer que está sendo executado em um fio e esse fio é suspenso a contar de uma linha 1 ou 2 e, em seguida, outro segmento nos chutes e corridas:

b = 5

Então, quando o outro thread retoma, b é substituído pelos antigos valores calculados, o que provavelmente não é o que era esperado.

Assim você pode ver que mesmo que eles não estão realmente funcionando, ao mesmo tempo, você ainda precisa de bloqueio.

Você ainda precisará usar fechaduras (seu código pode ser interrompido a qualquer momento para executar outro segmento e isso pode causar inconsistências de dados). O problema com GIL é que ele evita que o código Python de usar mais núcleos ao mesmo tempo (ou múltiplos processadores se eles estão disponíveis).

Locks ainda são necessários. Vou tentar explicar por que eles são necessários.

Qualquer operação / instrução é executada no interpretador. garante GIL que intérprete é realizada por uma única linha em um determinado instante de tempo . E o seu programa com vários segmentos trabalha em um único intérprete. Em qualquer instante de tempo particular, este intérprete é realizada por um único segmento. Isso significa que só discussão que está segurando o intérprete é executar em qualquer instante de tempo.

Suponha que existem dois tópicos, dizem t1 e t2, e ambos querem executar duas instruções que está lendo o valor de uma variável global e incrementá-lo.

#increment value
global var
read_var = var
var = read_var + 1

Como colocar acima, GIL só assegura que dois segmentos não é possível executar uma instrução simultaneamente, o que significa que ambos os fios não podem executar read_var = var em qualquer instante de tempo particular. Mas eles podem executar uma instrução após o outro e você ainda pode ter problema. Considere a seguinte situação:

  • read_var Suponha que é 0.
  • GIL é detido por t1 fio.
  • t1 executa read_var = var. Então, read_var em t1 é 0. GIL só irá garantir que esta operação de leitura não será executada por qualquer outro segmento neste instante.
  • GIL é dado a t2 fio.
  • t2 executa read_var = var. Mas read_var ainda é 0. Assim, read_var em t2 é 0.
  • GIL é dado a t1.
  • t1 executa var = read_var+1 e var se torna 1.
  • GIL é dado a t2.
  • t2 pensa read_var = 0, porque isso é o que ler.
  • t2 executa var = read_var+1 e var se torna 1.
  • A nossa expectativa era de que var deve tornar-se 2.
  • Assim, um bloqueio deve ser usado para manter leitura e incrementando como uma operação atômica.
  • resposta Will Harris explica que através de um exemplo de código.

Um pouco de actualização a partir do exemplo de Will Harris:

class Withdraw(threading.Thread):  
def run(self):            
    for _ in xrange(1000000):  
        global shared_balance  
        if shared_balance >= 100:
          balance = shared_balance
          balance -= 100  
          shared_balance = balance

Coloque uma declaração de verificação valor no se retirar e não vejo negativo mais e atualizações parece consistente. A minha pergunta é:

impede Se GIL apenas um thread pode ser executada a qualquer momento atômica, então onde estaria o valor obsoleto? Se nenhum valor obsoleto, por isso que precisamos de bloqueio? (Supondo que nós só falaremos do código python puro)

Se bem entendi, a verificação de condição acima não iria funcionar em um real ambiente enfiar. Quando mais de um tópicos estão executando simultaneamente, valor obsoleto pode ser criado, portanto, a inconsistência do estado share, então você realmente precisa de um bloqueio. Mas se python realmente permite que apenas um fio a qualquer momento (corte de threading tempo), então não deve não ser possível para o valor obsoleto de existir, certo?

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top