Python - один из языков программирования, который поддерживает многопроцессорность и многопоточность, и это довольно круто.

В конце концов, python не является одним из лучших для параллельного программирования, но давайте посмотрим, как мы можем использовать и решить общую проблему: условия гонки

Прежде чем я покажу вам пример, сначала мы должны познакомиться с этой концепцией: взаимное исключение, блокировка, условия гонки.

Взаимное исключение: когда два или более процессов / потоков хотят получить доступ к общему ресурсу, и после того, как поток получил доступ к этому ресурсу, они блокируют все остальные одновременно.

Блокировка: поток перед доступом к общему ресурсу, должен построить график или получить блокировку, на этом этапе все другие параллельные участники ждут снятия блокировки. Блокировка снимается после того, как поток завершил время своего выполнения с использованием этого ресурса.

Условия гонки: для этого используется взаимное исключение, чтобы разрешить условия гонки.

Состояние гонки возникает, когда два или более параллельных потока одновременно обращаются к общему ресурсу и обновляют его, то есть он не защищен блокировкой.

from threading import Thread,Lock
x = 1 # Shared resource
def fibonaci(n):
    if n<=2 : return 1
    return fibonaci(n-1) + fibonaci(n-2)
def firstFibonaci():
    global x
    print("firstFibonaci resource is {} \n".format(x) )
    while x <= 10:
        print(fibonaci(x))
        x+=1
def secondFibonaci():
    global x
    print("secondFibonaci resource is {} \n".format(x) )
    while x <= 10:
        print(fibonaci(x))
        x+=1
Parallel_1 = Thread(target=firstFibonaci)
Parallel_2 = Thread(target=secondFibonaci)
Parallel_1.start()
Parallel_2.start()
Parallel_1.join()
Parallel_2.join()

Я создал два метода firstFibonaci и secondFibonaci, и для каждого из них общий ресурс x находится в глобальной области видимости. Общий ресурс - x, и потоки к нему будут обращаться параллельно.

Технически второй метод не должен обновлять общий ресурс, поскольку он увеличивается до 11 методом firstFibonaci, но так ли это?

Это ожидаемый правильный ответ:

$ /usr/bin/python race_condition.py
firstFibonaci resource is 1
1
1
2
3
5
8
13
21
34
55
secondFibonaci resource is 11

Но посмотрите, как на самом деле генерируется ответ:

$ /usr/bin/python race_condition.py
firstFibonaci resource is 1
1
secondFibonaci resource is 1
1
12
35
8
13
34
2155

и другой :

$ /usr/bin/python race_condition.py
firstFibonaci resource is 1
1
1
2
3
5
secondFibonaci resource is 6
88
13
2134
55

Почему так случилось?

Это потому, что общий ресурс x не защищен блокировкой. Буксируемые потоки одновременно получили доступ к этому ресурсу, обновив состояние. Теперь, чтобы разрешить это состояние гонки, мы перейдем к другому классу потоковой передачи пакетов, а именно Lock.

Этот класс имеет основной метод буксировки:

получить: Это означает, что блокировка отображается потоком, и все параллельные операции блокируются в ожидании освобождения.

release: Это означает, что блокировка свободна и готова для другого потока.

Посмотрите, как это работает.

from threading import Thread,Lock
x = 1 #Shared resource
#Initialize an instance of object Lock
lock_race = Lock()
def fibonaci(n):
    if n<=2 : return 1
    return fibonaci(n-1) + fibonaci(n-2)
def firstFibonaci():
    global x
    try:
        lock_race.acquire() # Graph a lock
        print("firstFibonaci resource is {} \n".format(x) )
        while x <= 10:
            print(fibonaci(x))
            x+=1
    finally:
        # Release the lock, now it is free, and can be graphed by 
        # the second thread
        lock_race.release() 
def secondFibonaci():
    global x
    try:
        lock_race.acquire()
        print("secondFibonaci resource is {} \n".format(x) )
        while x <= 10:
            print(fibonaci(x))
            x+=1
    finally:
        lock_race.release()
Parallel_1 = Thread(target=firstFibonaci)
Parallel_2 = Thread(target=secondFibonaci)
Parallel_1.start()
Parallel_2.start()
Parallel_1.join()
Parallel_2.join()

Теперь у нас должен быть правильный и исключенный ответ:

$ /usr/bin/python race_condition.py
firstFibonaci resource is 1
1
1
2
3
5
8
13
21
34
55
secondFibonaci resource is 11

Как мы видим, это сработало очень хорошо, и к общему ресурсу сначала обращается первый параллельный поток, который изобразил блокировку, блокирующую других участников, таким образом, общий ресурс x Не дорабатывалась при этом буксировкой разных потоков.

Я просто хочу кое-что прояснить, как можно использовать блокировку:

lock_race = Lock()
try:
    lock_race.acquire()
finally:
    lock_race.release()
# Is the same as 
with lock_race:
    pass

Заключение

Проблема с условиями гонки может привести к катастрофическим последствиям для кода, поэтому параллельное выполнение потоков следует использовать с осторожностью. Библиотеки Python, такие как threading, lock, могут помочь нам решить эту проблему.

Спасибо