Скопируй и запусти две разных версии кода.
Без Lock
from threading import *
def work(i):
for _ in range(100):
print(f"hello i'm a thread #{i}")
t1 = Thread(target=work, args=(1,))
t2 = Thread(target=work, args=(2,))
t1.start()
t2.start()
t1.join()
t2.join()
С Lock
from threading import *
lock = Lock()
def work(i):
for _ in range(100):
with lock:
print(f"hello i'm a thread #{i}")
t1 = Thread(target=work, args=(1,))
t2 = Thread(target=work, args=(2,))
t1.start()
t2.start()
t1.join()
t2.join()
Как можешь заметить, первый вариант иногда печатает две строки на одной, а иногда печатает пустые строки
Ещё примеры
Без Lock
from threading import *
from time import sleep
class GlobalState:
def __init__(self, x):
self.x = x
def set_x(self, x):
self.x = x
def reader(state: GlobalState):
if state.x % 2 == 0:
sleep(0.01) # simulate OS context switch
print(f"{state.x=} is even")
else:
print(f"{state.x=} is odd")
def changer(state: GlobalState):
state.set_x(state.x + 1)
state = GlobalState(2)
t1 = Thread(target=reader, args=(state,))
t2 = Thread(target=changer, args=(state,))
t1.start()
t2.start()
t1.join()
t2.join()
С Lock
from threading import *
from time import sleep
class GlobalState:
def __init__(self, x):
self.x = x
self.lock = Lock()
def set_x(self, x):
self.x = x
def reader(state: GlobalState):
with state.lock:
if state.x % 2 == 0:
sleep(0.01) # simulate OS context switch
print(f"{state.x=} is even")
else:
print(f"{state.x=} is odd")
def changer(state: GlobalState):
with state.lock:
state.set_x(state.x + 1)
state = GlobalState(2)
t1 = Thread(target=reader, args=(state,))
t2 = Thread(target=changer, args=(state,))
t1.start()
t2.start()
t1.join()
t2.join()
Ну и совсем упоротый пример для тех, кто говорит, что list — threadsafe (что фактически является истиной, но логически не всегда) и не нужно использовать Lock:
Открыть
from threading import *
from random import *
class GlobalState:
def __init__(self):
self.x = []
def do_something_changing(self):
if random() < 0.5:
self.x.append(1)
elif self.x:
self.x.pop()
def reader(state: GlobalState):
for _ in range(10000000):
if len(state.x) % 2 == 0:
if len(state.x) % 2 != 0: # wtf how it's possible?
print(f"length {len(state.x)} was even before context switch")
def changer(state: GlobalState):
for _ in range(10000000):
state.do_something_changing()
state = GlobalState()
t1 = Thread(target=reader, args=(state,))
t2 = Thread(target=changer, args=(state,))
t1.start()
t2.start()
t1.join()
t2.join()
И напоследок. Хватит программировать (или пытаться) на тредах. Это сложно и никому не нужно. Давно существуют куда более удачные реализации использования всех ядер процессора (
csp например в golang). А если треды используются для IO (а в питоне они в 99.9% используются именно для IO), то давно есть и довольно юзабельный asyncio.