Влияние инкремента += в Python на возникновение Race Condition
Пытался воспроизвести Race Condition на Python в разных вариантах. Вот один из примеров:
import threading, time
count = 0
def counter():
global count
c = count # 100 потоков одновременно изменили переменную c, присвоив ей значение count + 1, то есть 0+1 = 1
time.sleep(0.1) # потоки заснули
count = c + 1 # Потоки проснулись и просто присвоили переменной count значение 0+1 100 раз.
print(count) # печать count после повторного присвоения единицы.
for t in range(100):
thr = threading.Thread(target=counter)
thr.start()
(Изменено)
Здесь 100 потоков входят в функцию counter
, присваивают значение count
переменной c
, а затем засыпают на 0.1 секунды. В момент засыпания потоки освобождают GIL из-за чего в последствии таковые единовременно пытаются инкрементировать count = c + 1
. Однако проблема не в том, что потоки захватывают одно и то же состояние count = 0
. На самом деле в приведенном примере переменная count не может ни коим образом измениться, потому что переменная c
неизменно равна 1 и каждый поток, заходя в 135 строчку
просто присваивает переменной count
двойку, что и становится следствием того, что count = 2
всегда.
Race Condition здесь не происходит - GIL успешно блокирует захват блокировки более чем одним потоком. Получаем следующий вывод:
#...
# 1
# 1
# 1
# 1
# 1
# 1
# 1
# 1
Затем я попробовал немного изменить функцию:
import threading, time
count = 0
def counter():
global count
time.sleep(0.1)
count = count + 1
print(count)
for t in range(100):
thr = threading.Thread(target=counter)
thr.start()
Здесь единственная разница по сравнению с предыдущим случаем в том, что мы вместо того, чтобы создавать промежуточную переменную, инкрементируем count
саму с собой. В остальном всё работает так же - 100 потоков заходят в функцию, засыпают на 0,1 секунды, из-за чего освобождают GIL, и инкрементируют переменную count = count + 1
. Однако Race Condition не возникает! Вывод:
#...
# 94
# 95
# 96
# 97
# 98
# 99
# 100
Переменная count равна 100. Почему так происходит? Выглядит так, будто операция инкремента += атомарная. То есть можно было бы предположить, что в данном примере из-за атомарности операции на уровне байт-кода, GIL не дает нескольким потокам работать в один момент времени.