Resumo : neste tutorial, você aprenderá sobre condições de corrida e como usar o objeto Lock de threading do Python para evitá-las.
O que é uma condição de corrida
Uma condição de corrida ocorre quando dois ou mais threads tentam acessar uma variável compartilhada simultaneamente, levando a resultados imprevisíveis.
Neste cenário, o primeiro thread lê o valor da variável compartilhada. Ao mesmo tempo, o segundo thread também lê o valor da mesma variável compartilhada.
Em seguida, ambos os threads tentam alterar o valor da variável compartilhada. como as atualizações ocorrem simultaneamente, cria-se uma corrida para determinar qual modificação do thread será preservada.
O valor final da variável compartilhada depende de qual thread conclui sua atualização por último. Qualquer thread que alterar o valor por último vencerá a corrida.
Exemplo de condição de corrida
O exemplo a seguir ilustra uma condição de corrida:
from threading import Thread
from time import sleep
counter = 0
def increase(by):
global counter
local_counter = counter
local_counter += by
sleep(0.1)
counter = local_counter
print(f'counter={counter}')
# create threads
t1 = Thread(target=increase, args=(10,))
t2 = Thread(target=increase, args=(20,))
# start the threads
t1.start()
t2.start()
# wait for the threads to complete
t1.join()
t2.join()
print(f'The final counter is {counter}')
Linguagem de código: Python ( python )
Neste programa, ambos os threads tentam modificar o valor da counter
variável ao mesmo tempo. O valor da counter
variável depende de qual thread foi concluído por último.
Se o thread t1
for concluído antes de thread t2
, você verá a seguinte saída:
counter=10
counter=20
The counter is 20
Linguagem de código: Python ( python )
Caso contrário, você verá a seguinte saída:
counter=20
counter=10
The final counter is 10
Linguagem de código: Python ( python )
Como funciona.
Primeiro, importe Thread
a classe do threading
módulo e a sleep()
função do time
módulo:
from threading import Thread
from time import sleep
Linguagem de código: Python ( python )
Segundo, defina uma variável global chamada counter
cujo valor seja zero:
counter = 0
Linguagem de código: Python ( python )
Terceiro, defina uma função que aumente o valor da counter
variável em um número:
def increase(by):
global counter
local_counter = counter
local_counter += by
sleep(0.1)
counter = local_counter
print(f'counter={counter}')
Linguagem de código: Python ( python )
Quarto, crie dois threads. O primeiro thread aumenta counter
em 10 enquanto o segundo thread aumenta em counter
20:
t1 = Thread(target=increase, args=(10,))
t2 = Thread(target=increase, args=(20,))
Linguagem de código: Python ( python )
Quinto, inicie os tópicos:
t1.start()
t2.start()
Linguagem de código: Python ( python )
Sexto, no thread principal, aguarde a conclusão dos threads t1 e t2:
t1.join()
t2.join()
Linguagem de código: Python ( python )
Por fim, mostre o valor final da counter
variável:
print(f'The final counter is {counter}')
Linguagem de código: Python ( python )
Usando uma trava de rosqueamento para evitar a condição de corrida
Para evitar condições de corrida, você pode usar uma trava de rosqueamento.
Um bloqueio de threading é uma primitiva de sincronização que fornece acesso exclusivo a um recurso compartilhado em um aplicativo multithread. Um bloqueio de thread também é conhecido como mutex , que é a abreviação de exclusão mútua.
Normalmente, um bloqueio de threading possui dois estados: bloqueado e desbloqueado. Quando um thread adquire um bloqueio, o bloqueio entra no estado bloqueado. O thread pode ter acesso exclusivo ao recurso compartilhado.
Outros threads que tentarem adquirir o bloqueio enquanto ele estiver bloqueado serão bloqueados e aguardarão até que o bloqueio seja liberado.
Em Python, você pode usar a Lock
classe do threading
módulo para criar um objeto de bloqueio:
Primeiro, crie uma instância da Lock
classe:
lock = Lock()
Linguagem de código: Python ( python )
Por padrão, o bloqueio é desbloqueado até que um thread o adquira.
Segundo, adquira um bloqueio chamando o acquire()
método:
lock.acquire()
Linguagem de código: Python ( python )
Terceiro, libere o bloqueio assim que o thread concluir a alteração da variável compartilhada:
lock.release()
Linguagem de código: Python ( python )
O exemplo a seguir mostra como usar o Lock
objeto para evitar a condição de corrida no programa anterior:
from threading import Thread, Lock
from time import sleep
counter = 0
def increase(by, lock):
global counter
lock.acquire()
local_counter = counter
local_counter += by
sleep(0.1)
counter = local_counter
print(f'counter={counter}')
lock.release()
lock = Lock()
# create threads
t1 = Thread(target=increase, args=(10, lock))
t2 = Thread(target=increase, args=(20, lock))
# start the threads
t1.start()
t2.start()
# wait for the threads to complete
t1.join()
t2.join()
print(f'The final counter is {counter}')
Linguagem de código: Python ( python )
Saída:
counter=10
counter=30
The final counter is 30
Linguagem de código: Python ( python )
Como funciona.
- Primeiro, adicione um segundo parâmetro à
increase()
função. - Segundo, crie uma instância da
Lock
classe. - Terceiro, adquira um bloqueio antes de acessar a
counter
variável e libere-o após atualizar o novo valor.
Usando o bloqueio de threading com a instrução with
É mais fácil usar o objeto lock com a with
instrução para adquirir e liberar o bloqueio dentro de um bloco de código:
import threading
# Create a lock object
lock = threading.Lock()
# Perform some operations within a critical section
with lock:
# Lock was acquired within the with block
# Perform operations on the shared resource
# ...
# the lock is released outside the with block
Linguagem de código: PHP ( php )
Por exemplo, você pode usar a instrução with sem a necessidade de chamar os métodos acquire()
and release()
no exemplo acima da seguinte maneira:
from threading import Thread, Lock
from time import sleep
counter = 0
def increase(by, lock):
global counter
with lock:
local_counter = counter
local_counter += by
sleep(0.1)
counter = local_counter
print(f'counter={counter}')
lock = Lock()
# create threads
t1 = Thread(target=increase, args=(10, lock))
t2 = Thread(target=increase, args=(20, lock))
# start the threads
t1.start()
t2.start()
# wait for the threads to complete
t1.join()
t2.join()
print(f'The final counter is {counter}')
Linguagem de código: PHP ( php )
Definindo a classe Counter segura para thread que usa o objeto Lock de threading
O seguinte ilustra como definir uma Counter
classe que é thread-safe usando o Lock
objeto:
from threading import Thread, Lock
from time import sleep
class Counter:
def __init__(self):
self.value = 0
self.lock = Lock()
def increase(self, by):
with self.lock:
current_value = self.value
current_value += by
sleep(0.1)
self.value = current_value
print(f'counter={self.value}')
def main():
counter = Counter()
# create threads
t1 = Thread(target=counter.increase, args=(10, ))
t2 = Thread(target=counter.increase, args=(20, ))
# start the threads
t1.start()
t2.start()
# wait for the threads to complete
t1.join()
t2.join()
print(f'The final counter is {counter.value}')
if __name__ == '__main__':
main()
Linguagem de código: Python ( python )
Resumo
- Uma condição de corrida ocorre quando dois threads acessam uma variável compartilhada ao mesmo tempo.
- Use um objeto de bloqueio de threading para evitar a condição de corrida
- Chame o
acquire()
método de um objeto lock para adquirir um bloqueio. - Chame o
release()
método de um objeto lock para liberar o bloqueio adquirido anteriormente. - Use um objeto de bloqueio de threading com a
with
instrução para facilitar a aquisição e liberação do bloqueio.