Версия Python 3.5, использую модуль threading. Нужна ли синхронизация, когда потоки изменяют общую глобальную переменную в один момент времени? В книгах пишут, что нужно использовать Lock(), но и без него не возникает не каких проблем. Пример кода из книги:
import threading, time count = 0 def adder(): global count count += 1 time.sleep(0.5) count += 1 threads = [] for i in range(100): thread = threading.Thread(target=adder, args=()) thread.start() threads.append(thread) for thread in threads: thread.join() print(count)
Вот здесь, судя по книге, должно каждый раз выдавать разные числа, так как одновременно 100 потоков в один момент времени меняют переменную count. Но почему-то всё нормально срабатывает. Это в какой-то версии было исправлено или как? Объясните, кто знает.
Атомарные
операции.
Переключение нитей происходит только между отдельными байт-код операциями. Сами же операции неделимы. Посмотреть, как выглядит байт-код можно с помощью модуля dis
Вот некоторые потокобезопасные операции:
- чтение или изменение одного атрибута объекта
- чтение или изменение одной глобальной переменной
- выборка элемента из списка
- модификация списка «на месте» (т.е. с помощью метода append)
- выборка элемента из словаря
- модификация словаря «на месте» (т.е. добавление элемента, или вызов
метода clear)
нужна ли синхронизация когда потоки изменяют общую глобальную переменную в один момент времени?
Нужна. Вот ещё пример (запускаем 100 потоков, увеличиваем глобальную переменную niters
раз):
#!/usr/bin/env python3 from concurrent.futures import ThreadPoolExecutor as Pool niters = 100000 count = 0 def increment(): global count count += 1 with Pool(100) as pool: for _ in range(niters): pool.submit(increment) assert count == niters, count
Этот код может приводить к AssertionError
:
Traceback (most recent call last): File "/usr/lib/python3.5/runpy.py", line 184, in _run_module_as_main "__main__", mod_spec) File "/usr/lib/python3.5/runpy.py", line 85, in _run_code exec(code, run_globals) File "./__main__.py", line 17, in <module> assert count == niters, count AssertionError: 99981
Код легко сделать threadsafe, добавлением lock:
#!/usr/bin/env python3 import threading from concurrent.futures import ThreadPoolExecutor as Pool niters = 100000 count = 0 def increment(lock=threading.Lock()): global count with lock: count += 1 with Pool(100) as pool: for _ in range(niters): pool.submit(increment) assert count == niters, count
Только одна +=
операция выполняется в любое время (что делает потоки бессмысленными в этом примере. См. видео Thinking about Concurrency, Raymond Hettinger, Python core developer), поэтому гарантируется, что count == niters
в конце.