#python #multithreading #locking
#python #многопоточность #блокировка
Вопрос:
Я почти новичок в потоковой обработке Python. Это мой код, смоделированный на примере кода, найденного на GeeksForFeeks, который объясняет поведение потоков с использованием блокировки. Но результат — для меня — противоречит здравому смыслу.
import threading
# global variable x
x = 0
# creating a lock
lock = threading.Lock()
def increment():
global x
x = 1
print("thread1:", x)
def decrement():
global x
x -= 1
print("thread2:", x)
def plus():
global lock
for _ in range(100000):
lock.acquire()
increment()
lock.release()
def minus():
global lock
for _ in range(100000):
lock.acquire()
decrement()
lock.release()
def main_task():
global x
# setting global variable x as 0
x = 0
# creating threads
t1 = threading.Thread(target=plus)
t2 = threading.Thread(target=minus)
# start threads
t1.start()
t2.start()
# wait until threads finish their job
t1.join()
t2.join()
if __name__ == "__main__":
main_task()
Я ожидал бы такого результата:
thread1: 1
thread2: 0
thread1: 1
thread2: 0
thread1: 1
thread2: 0
thread1: 1
...
но вместо этого я получаю
thread1: 1
thread1: 2
thread1: 3
thread1: 4
thread1: 5
thread1: 6
thread1: 7
...
thread1: 151
thread2: 150
thread2: 149
thread2: 148
thread2: 147
thread2: 146
thread2: 145
thread2: 144
thread2: 143
thread2: 142
thread2: 141
...
Почему thread2 не может получать блокировку каждый раз, когда он освобождается от thread1?
Чего мне не хватает?
Ответ №1:
Это происходит по крайней мере по двум причинам:
-
GIL
Это единственная блокировка самого интерпретатора, которая добавляет правило, согласно которому для выполнения любого байт-кода Python требуется получение блокировки интерпретатора. -
Более общий:
Lock
объект не совсем подходит для вас. Да, он синхронизирует доступ к переменной, но послеlock
освобождения и до момента ее получения другой код может выполнять несколько таких итераций.
Для чередующейся синхронизации лучше использовать Condition
объект, который позволяет коду ждать, пока другой код уведомит (по сути, разбудит его), что первый может выполняться:
# creating a lock
lock = threading.Condition()
# ...
def plus():
global lock
with lock:
for _ in range(100000):
increment()
lock.notify()
lock.wait()
lock.notify() # notify to finish companion thread
def minus():
global lock
with lock:
for _ in range(100000):
decrement()
lock.notify()
lock.wait()
lock.notify() # notify to finish companion thread
...
thread1: 1
thread2: 0
thread1: 1
thread2: 0
thread1: 1
thread2: 0
Комментарии:
1. Спасибо. Почему
Condition
объект не позволяет запускать другие вещи один раз после освобождения ресурса, в то времяLock
как объект разрешает, пожалуйста?2. Потому что в случае Condition мы ждем (метод wait() ), чтобы другой поток заработал, и уведомляем (метод notify() ) нас об этом. Это обеспечивает синхронизацию. В случае блокировки сразу после ее освобождения мы пытаемся получить ее снова.
3. В случае, если у нас есть несколько потоков, созданных из потока, обрабатывающего данные очереди, вам все еще нужен
Condition
объект, пожалуйста?