Como usar o Python Threading Lock para evitar condições de corrida – 03

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 countervariável ao mesmo tempo. O valor da countervariável depende de qual thread foi concluído por último.

Se o thread t1for 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 10Linguagem de código:  Python  ( python )

Como funciona.

Primeiro, importe Threada classe do threadingmódulo e a sleep()função do timemódulo:

from threading import Thread
from time import sleepLinguagem de código:  Python  ( python )

Segundo, defina uma variável global chamada countercujo valor seja zero:

counter = 0Linguagem de código:  Python  ( python )

Terceiro, defina uma função que aumente o valor da countervariá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 counterem 10 enquanto o segundo thread aumenta em counter20:

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 countervariá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 Lockclasse do threadingmódulo para criar um objeto de bloqueio:

Primeiro, crie uma instância da Lockclasse:

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 Lockobjeto 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 30Linguagem de código:  Python  ( python )

Como funciona.

  • Primeiro, adicione um segundo parâmetro à increase()função.
  • Segundo, crie uma instância da Lockclasse.
  • Terceiro, adquira um bloqueio antes de acessar a countervariá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 withinstruçã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 Counterclasse que é thread-safe usando o Lockobjeto:

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 withinstrução para facilitar a aquisição e liberação do bloqueio.

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *