Пытаясь понять, как анализировать проблемы с выпуском python GIL - PullRequest
0 голосов
/ 17 января 2020

Мне часто приходится анализировать проблемы с многопоточностью в python коде, и до сих пор я думал, что понимаю, как выяснить, может ли фрагмент кода высвободить GIL и, следовательно, вызвать условия гонки. Однако, проанализировав следующий фрагмент кода, я думаю, что, должно быть, упустил что-то, чтобы выяснить, когда GIL может быть выпущен.

import sys

for a, b in list(sys.modules.items()):
    do_something()

Это, очевидно, может привести к редкому RuntimeError: dictionary changed size during iteration из-за того, что sys.modules изменено в другой теме. Сейчас мы столкнулись с этим несколько раз из сотен серий, поэтому воспроизвести его не так просто. Несмотря на исправление, которое я считаю поточнобезопасным (sys.modules.copy().items()), я пытался понять, какая часть утверждения позволила изменить dict, пока мы перебираем представление dict. Во время анализа того, какая часть утверждения может фактически быть проблемой, я понял, что могу упустить некоторое понимание GIL.

Мои шаги анализа следующие:

1.) Упростите проблема в минимальном примере:

import dis

a = {}
def f():
    for i, j in list(f.items()):
        pass

def g():
    for i, j in f.copy().items():
        pass

print("Function f:")
dis.dis(f)

print("Function g:")
dis.dis(g)

, который показывает, что функция f может сбрасывать GIL между вызовом элементов и вызовом списка.

Function f:
  5           0 SETUP_LOOP              24 (to 26)
              2 LOAD_GLOBAL              0 (list)
              4 LOAD_GLOBAL              1 (f)
              6 LOAD_METHOD              2 (items)
              8 CALL_METHOD              0
             10 CALL_FUNCTION            1
             12 GET_ITER
        >>   14 FOR_ITER                 8 (to 24)
             16 UNPACK_SEQUENCE          2
             18 STORE_FAST               0 (i)
             20 STORE_FAST               1 (j)

  6          22 JUMP_ABSOLUTE           14
        >>   24 POP_BLOCK
        >>   26 LOAD_CONST               0 (None)
             28 RETURN_VALUE
Function g:
  9           0 SETUP_LOOP              24 (to 26)
              2 LOAD_GLOBAL              0 (f)
              4 LOAD_METHOD              1 (copy)
              6 CALL_METHOD              0
              8 LOAD_METHOD              2 (items)
             10 CALL_METHOD              0
             12 GET_ITER
        >>   14 FOR_ITER                 8 (to 24)
             16 UNPACK_SEQUENCE          2
             18 STORE_FAST               0 (i)
             20 STORE_FAST               1 (j)

 10          22 JUMP_ABSOLUTE           14
        >>   24 POP_BLOCK
        >>   26 LOAD_CONST               0 (None)
             28 RETURN_VALUE

2.) Удивлен, что изменение диктат после создания объекта представления может вызвать исключение. Я проверил это предположение, но оказалось, что оно работает нормально:

a = {1: 1}
b = a.items()
a[2] = 2
for i, j in list(b):
    print(i)

1
2

3.) Следующим шагом является попытка увидеть, что на самом деле происходит в cpython. Фактическое создание списка из представления dict происходит в Objects/listobject.c:list_extend, который создает итерацию с PyObject_GetIter(iterable) и после некоторых вызовов использует прямые вызовы C для запуска через итератор путем вызова tp_iternext для объекта итератора без освобождения GIL.

В list_extend единственная функция, которая выглядит так, как будто она может выпустить GIL в какой-то момент, - это PyObject_LengthHint, которая на самом деле вызывает _PyObject_CallNoArg, но отладка показывает, что код на самом деле принимает кодовый путь _PyObject_HasLen и, следовательно, не должен выпускать GIL.

Если я не понимаю, что GIL может быть сброшен в коде C, только если мы на самом деле вызываем функцию с помощью одного из методов PyObject_Call*, это неправильно, я не понимаю, как приведенный выше код может фактически вызвать исключение.

Мои два вопроса после анализа этого пути к коду: какое из моих предположений о GIL неверно, и если есть лучший способ выяснить, какие методы python не являются сбросив GIL.

...