Страницы

Поиск по вопросам

среда, 1 января 2020 г.

Синхронизация потоков при наращивании count += 1 глобальной переменной

#python #многопоточность


Версия 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. Но почему-то всё нормально
срабатывает. Это в какой-то версии было исправлено или как? Объясните, кто знает.
    


Ответы

Ответ 1



Атомарные операции. Переключение нитей происходит только между отдельными байт-код операциями. Сами же операции неделимы. Посмотреть, как выглядит байт-код можно с помощью модуля dis Вот некоторые потокобезопасные операции: чтение или изменение одного атрибута объекта чтение или изменение одной глобальной переменной выборка элемента из списка модификация списка "на месте" (т.е. с помощью метода append) выборка элемента из словаря модификация словаря "на месте" (т.е. добавление элемента, или вызов метода clear)

Ответ 2



нужна ли синхронизация когда потоки изменяют общую глобальную переменную в один момент времени? Нужна. Вот ещё пример (запускаем 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 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 в конце.

Комментариев нет:

Отправить комментарий