Почему Python предоставляет механизмы блокировки, если он подчиняется GIL?
Я знаю, что потоки Python могут выполнять только байт-код по одному за раз, так почему библиотека потоков обеспечивает блокировки? Я предполагаю, что условия гонки не могут возникнуть, если одновременно выполняется только один поток.
Библиотека предоставляет блокировки, условия и семафоры. Является ли единственной целью этого синхронизировать выполнение?
Обновить:
Я выполнил небольшой эксперимент:
from threading import Thread
from multiprocessing import Process
num = 0
def f():
global num
num += 1
def thread(func):
# return Process(target=func)
return Thread(target=func)
if __name__ == '__main__':
t_list = []
for i in xrange(1, 100000):
t = thread(f)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print num
По сути, я должен был запустить 100k потоков и увеличить их на 1. Возвращенный результат был 99993.
а) Как результат не может быть 99999, если есть GIL-синхронизация и избегание условий гонки? б) Можно ли даже запустить 100k потоков ОС?
Обновление 2, после просмотра ответов:
Если GIL на самом деле не обеспечивает способ выполнения простой операции, такой как атомарный инкремент, какова цель его наличия? Это не помогает с неприятными проблемами параллелизма, так почему это было введено в действие? Я слышал сценарии использования C-расширений, кто-нибудь может привести пример?
2 ответа
GIL синхронизирует операции с байт-кодом. Только один байт-код может выполняться одновременно. Но если у вас есть операция, которая требует более одного байт-кода, вы можете переключать потоки между байт-кодами. Если вам нужно, чтобы операция была атомарной, то вам нужна синхронизация выше и за пределами GIL.
Например, увеличение целого числа - это не один байт-код:
>>> def f():
... global num
... num += 1
...
>>> dis.dis(f)
3 0 LOAD_GLOBAL 0 (num)
3 LOAD_CONST 1 (1)
6 INPLACE_ADD
7 STORE_GLOBAL 0 (num)
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
Здесь потребовалось четыре байтовых кода для реализации num += 1
, GIL не гарантирует, что x увеличивается атомарно. Ваш эксперимент демонстрирует проблему: вы потеряли обновления, потому что потоки переключались между LOAD_GLOBAL и STORE_GLOBAL.
Цель GIL состоит в том, чтобы счетчики ссылок на объекты Python увеличивались и уменьшались атомарно. Это не предназначено, чтобы помочь вам с вашими собственными структурами данных.
Собственные потоки Python работают на уровне байт-кода. То есть после каждого байт-кода (ну, на самом деле, я считаю, что число байт-кодов настраивается), поток может передавать управление другому потоку.
Любая операция с общим ресурсом, которая не является ни одним байт-кодом, требует блокировки. И даже если данная операция в определенной версии CPython представляет собой один байт-код, это может быть не так в каждой версии каждого интерпретатора, поэтому в любом случае лучше использовать блокировку.
По той же причине, для начала вам нужны блокировки, кроме как на уровне виртуальной машины, а не на аппаратном уровне.