2

docs asyncio чтения:Как можно Asyncio никогда не быть потокобезопасным, учитывая GIL?

Большинство asyncio объектов не являются поточно. Вы должны только беспокоиться, если вы обращаетесь к объектам вне цикла событий.

Может ли кто-нибудь объяснить это или показать пример того, как неправильное использование asyncio может привести к несинхронизированной записи в объект, разделяемый между потоками? Я думал, что GIL означает, что только один поток может запускать интерпретатор за раз, и поэтому события, которые происходят в интерпретаторе, такие как чтение и запись объектов Python, тривиально синхронизированы между потоками.

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

Я думаю, что нить всегда могла бы вызвать хаос, выпустив GIL и решив писать объекты Python в любом случае, но это не относится к asyncio, поэтому я не думаю, что это то, о чем здесь говорится в документах.

Возможно ли это из-за того, что асинхронные PEP резервируют опцию для определенных объектов asyncio, чтобы они не были потокобезопасными, хотя на данный момент реализация в CPython просто так потокобезопасна?

+2

Некоторые операции требуют нескольких инструкций для синхронизации, в которых между Python может быть интерпретирован другой поток. GIL никогда * тривиально не синхронизирует * программу Python, не связанную с asyncio. - Он просто гарантирует, что объекты Python являются потокобезопасными на уровне C, а не на уровне Python. –

ответ

1

На самом деле, нет, каждая нить именно это, новая нить интерпретатора.

Это настоящая нить, управляемая ОС, а не внутренняя управляемая нить только для кода Python в виртуальной машине Python.

GIL необходим, чтобы предотвратить потоковое использование потоковых объектов Python на основе ОС.

Представьте себе один поток на одном CPU, а другой на другом. Чистые параллельные потоки, написанные на сборке. Оба в то же время пытаются изменить значение реестра. Нежелательное обстоятельство вообще. Инструкции по сборке для доступа к одной и той же позиции памяти в конечном итоге сводятся к тому, что нужно перемещать туда и обратно. В итоге результат такого действия может легко привести к ошибке сегментации. Ну, если мы пишем в C, C контролируем эту часть, чтобы этого не произошло в коде C. GIL делает то же самое для кода Python на уровне C. Таким образом, код, реализующий объекты Python, не теряет своей атомарности при их изменении. Представьте, что поток вставляет значение в список, который просто сдвинут вниз в другом потоке, потому что этот другой поток удалил из него некоторые элементы. Без GIL это сбой.

GIL ничего не говорит об атомарности вашего кода в потоках. Это просто для управления внутренней памятью.

Даже если у вас есть потокобезопасные объекты, такие как deque(), если вы выполняете несколько операций одновременно, без дополнительной блокировки, вы можете получить результат из другого потока, вставленного где-то посередине. И кричит, проблема возникает!

Скажем, одна нить берет объект из стека, проверяет что-то об этом, и если условие правильное, его удаляет.

stack = [2,3,4,5,6,7,8] 
def thread1(): 
    while 1: 
     v = stack[0] 
     sleep(0.001) 
     if v%2==0: del stack[0] 
     sleep(0.001) 

Конечно, это глупо и должно быть сделано с помощью stack.pop (0), чтобы этого избежать. Но это пример.

И пусть есть еще один поток, который добавляет в стек каждый 0.002 секунд:

def thread2(): 
    while 1: 
     stack.insert(0, stack[-1]+1) 
     sleep(0.002) 

Теперь, если вы делаете:

thread(thread2,()) 
sleep(1) 
thread(thread1,()) 

Там будет момент, хотя и маловероятно, где thread2() пытается складывают новый предмет точно между thread1() 'ы извлечения и удаление. Таким образом, thread1() удалит недавно добавленный элемент вместо проверяемого. Результат не соответствует нашим пожеланиям. Таким образом, GIL не контролирует то, что мы делаем в наших потоках, только то, что потоки делают друг с другом в более общем смысле.

Представьте, что вы написали сервер для покупки билетов на какое-то мероприятие. Два пользователя подключаются и пытаются купить один и тот же билет одновременно. Если вы не будете осторожны, пользователи могут перестать сидеть один над другим.

Объект, защищенный потоками, является объектом, который выполняет действие, и он не разрешает выполнять другое действие до завершения первого.

Например, если вы выполняете итерацию через deque() в одном потоке, а посредине другой поток пытается добавить что-то, append() будет блокироваться до тех пор, пока первый поток не будет выполняться над ним. Это поточно-безопасный.

+0

'GIL не контролирует то, что мы делаем в наших потоках, только то, что потоки делают друг с другом' - эта линия - чистое золото. – xyres

Смежные вопросы