python. отладка тредов.
к какому объекту обращаются сразу 2 треданечего на них смотреть. mutex-ами закрывай. все.
всё закрыто вроде. Кода много. По каждой строчке гулять - нереально.
Прога валится с сообщением "программа выполнила недопустимую операцию и будет закрыта"Странно, это обычное WIN32 падение, но разве питон может так падать при каких-то обстоятельствах?
отладочный print - не вариант? Его можно через декоратор включать в классы и функции
Странно, это обычное WIN32 падение, но разве питон может так падать при каких-то обстоятельствах?треды без синхронизации приводят к затертой памяти (к обращению по невалидному адресу и от платформы, языка и т.д. - это не зависит.
треды без синхронизации приводят к затертой памяти (к обращению по невалидному адресу и от платформы, языка и т.д. - это не зависит.Интересно, и как, например, уронить программу на C#? Кажется, я не встречался с такими ошибками...
треды без синхронизации приводят к затертой памяти (к обращению по невалидному адресу и от платформы, языка и т.д. - это не зависитвообще-то, языки высокого уровня (скриптовые в частности) не должны допускать подобных падений. потоки в таких языках тоже высокоуровневые. падения возможны при написании расширений (.dll потому что они написаны на C\C++ (на низкоуровневом языке)
почему в питоне это не так?
почему в питоне это не так?проблемы cpython в работе с многопоточными приложениями общеизвестны. Можно попробовать тот же проект запустить под jython или под pypy
Интересно, и как, например, уронить программу на C#? Кажется, я не встречался с такими ошибками...поднять два (а лучше 20) треда и начать активно работать с одними и теми же данными.
через некоторое время программа падает по exception-у ExecutionEngineException.
на втором .net-е грохалось стабильно, особенно при наличие двух реальных процессорных ядер, на более поздних версиях не проверял.
вообще-то, языки высокого уровня (скриптовые в частности) не должны допускать подобных падений. потоки в таких языках тоже высокоуровневые.каким это магическим образом? с помощью специально обученных маленьких зеленых гномиков?
если серьезно, то способ не допустить падения - один: вставлять синхронизирующие примитивы чтобы такого не было
но для выполнениия требования "языки высокого уровня (скриптовые в частности) не должны допускать подобных падений" языки должны тогда сами во весь код вставлять блокировки, а это сразу роняет общую производительность, поэтому обычно блокировки автоматически не вставляются и отдаются на откуп человеку
соответственно, если читать адреса из затираемого массива - то есть вероятность, что считается невалидный адрес (т.к. пара байт от старого адреса, а пара от нового)
каким это магическим образом?ну мне кажется логичным, что падать должна сама запускаемая программа с подходящей ошибкой, а вовсе не виртуальная машина (например ява в которой эта некорректная программа запущена.
соответственно, если читать адреса из затираемого массива - то есть вероятность, что считается невалидный адрес (т.к. пара байт от старого адреса, а пара от нового)Считается, но ведь потом, скорее всего, выкинется обычное исключение, потому что все машины перед разыменованием проверяют валидность ссылки.
потому что все машины перед разыменованием проверяют валидность ссылки.плиз, пруфлинк.
afaik, VM ничего не проверяют(кроме null) - они опираются на гарантию что невалидных адресов не может быть в принципе.
afaik, VM ничего не проверяют(кроме null) - они опираются на гарантию что невалидных адресов не может быть в принципе.Если есть такая гарантия, то тогда как твой пример может упасть?
ну мне кажется логичным, что падать должна сама запускаемая программа с подходящей ошибкой, а вовсе не виртуальная машина (например ява в которой эта некорректная программа запущена.VM обычно падают с исключением - о том, что VM диагностировало невалидное состояние (которого не должно быть) - например, что обнаружен невалидный адрес, и поэтому продолжать работу не стоит
Если есть такая гарантия, то тогда как твой пример может упасть?то, что несколько одновременно работающих тредов могут эту гарантию нарушить, например: Array.Copy с параллельным чтением адресов из другого потока
Ну-ну.
То есть я, конечно, не исключаю возможности того, что тебе случайно удалось наткнуться на баг (который именно баг, а не by design). Причём он вовсе не был обязан иметь именно такую природу. Или может быть ты вообще случайно какой-нибудь unsafe код юзал, а то и вовсе п/инвоук!
Информация к размышлению, кстати: копирование указателя всегда атомарная операция. Потому что размер указателя есть размер машинного слова.
Back to topic: вообще говоря в чистом питонокоде, исполняющемся на CPython ничего подобного быть не может в принципе, так как в нём есть GIL (Global Interpreter Lock который превращает его в sequentially executed. То есть: раз в N (==200 по дефолту) байткодных инструкций интерпретатор отпускает и тут же пытается забрать обратно особый глобальный лок. При этом например доступ к элементу хэштейбла является одной инструкцией.
Но есть нюансы: extension classes пишутся на С, имеют прямой доступ ко всей памяти интерпретатора (то есть например ctypes позволяет завести массив на надцать интов и передать поинтер на него в дллку а так же любят порой отпустить GIL перед выполнением длительной операции (при работе с блокирующимся IO это делается всегда, например). Соответственно если ОП что-то такое использует, то там могут быть разные баги!
Информация к размышлению, кстати: копирование указателя всегда атомарная операция. Потому что размер указателя есть размер машинного слова.Допустим не выровнил ты свой массив, а копировать для скорости собрался выровненными машинными словами + байтики на границах.
Wait-wait-wait, то есть ты утверждаешь, что код, прошедший через верификатор, удовлетворяющий всем security policy etc, может посредством подобного нехитрого трюка получить указатель в произвольное место памяти?вообще-то в security policy запрещено создание тредов
Информация к размышлению, кстати: копирование указателя всегда атомарная операция. Потому что размер указателя есть размер машинного слова.это именно гарантируется? с учетом всех кэшей, в том числе и кэшей между разными процессорами и т.д.?
Копирование адреса из одного места в другое на реальной машине - это чтение и запись, что произойдёт между ними - не известно. Но, если адреса выровнены по размеру слова (строка кеша кратна размеру слова то и чтение, и запись, скорее всего, не смогут привести к получению левого значения, скомбинированного из двух разных значений.
Информация к размышлению, кстати: копирование указателя всегда атомарная операция. Потому что размер указателя есть размер машинного слова.и на amd64?
Да, и на amd64 чтение из памяти в регистр и наоборот — атомарная операция. То есть получить смесь двух пойнтеров физически невозможно. Да, между чтением и записью может пройти неопределённое время, да, чтение может забрать данные не из памяти, а из одного из кэшей, и запись — записать, но есть необоримый закон природы: кусок данных размером с машинный регистр читается или пишется атомарно.
Процессорам типа х86-64 наверное приходится специально предпринимать ухищрения, чтобы гарантировать это для nonaligned accesses (другие просто хардверный эксепшен кидают) хотя хм, я не могу подтвердить эти свои слова. Со своей стороны я пожалуй могу гарантировать, что массив пойнтеров в дотнетах is correctly aligned. Любая попытка обойти это через "массив байтиков", требует unsafe code со всеми вытекающими.
: ну, а если через предоставленные осью? То есть например Ctrl-C приходит в отдельный тред, файналайзеры вызываются из отдельного треда, это всё тоже всегда запрещено для verifiable code?
То есть например Ctrl-C приходит в отдельный тред,это что такое?
языки должны тогда сами во весь код вставлять блокировки, а это сразу роняет общую производительность, поэтому обычно блокировки автоматически не вставляются и отдаются на откуп человекуимхо высокоуровневые языки тем и привлекательны, что они что-то куда-то вставляют и не отдают это на откуп человеку. они в первую очередь ориентированы на удобство и быстроту разработки и только во вторую - на перформанс.
это что такое?SIGINT
SIGINTи как это сочетается с тем же .net-ом, про который, например, шла речь?
не думаю что они гарантируют атомарность если слово легло поперёк кэшлайнов. шерстить спеки лень.
шерстить спеки8.1.1 Guaranteed Atomic Operations
net 3.5 сходу уронить не получилось, но надо еще проверить на многопроцессорном 64-битном
Да, и на amd64 чтение из памяти в регистр и наоборот — атомарная операция.
Это неправда для x86 архитектур. Правда в том, что атомарными операциями будут чтение из aligned памяти в регистр общего назначения и наоборот.
Правда в том, что атомарными операциями будут чтение из aligned памяти в регистр общего назначения и наоборот.а movsd какой-нибудь?
а movsd какой-нибудь?
Не является атомарной.
Ну, слава богу. А у каких операций были проблемы у амд64 с атомарностью? Я что-то слышал краем уха, но не запомнил.
amd64 это название системы команд — синоним для x86-64
вообще-то в security policy запрещено создание тредов
а можно ссылку?
т.е. threading для прикола?
или это какая-то общая политика, а не питоновская?
или это какая-то общая политика, а не питоновская?это про .net речь шла
Back to topic: вообще говоря в чистом питонокоде, исполняющемся на CPython ничего подобного быть не может в принципе, так как в нём есть GIL (Global Interpreter Lock который превращает его в sequentially executed. То есть: раз в N (==200 по дефолту) байткодных инструкций интерпретатор отпускает и тут же пытается забрать обратно особый глобальный лок. При этом например доступ к элементу хэштейбла является одной инструкцией.
Почитал про это тут http://docs.python.org/c-api/init.html
(кстати, там пишут, что 100. )
Отпало много вопросов по поводу странных локов до этого.
В питон-тредах я окончательно разочаровался. Придётся на разные процессы разделять.
И для concurrency они тоже ужасно сосут на мультикорах, потому что наличие единственного CPU-bound thread означает, что все другие треды на соседних корах задолбаются у него отбирать GIL, потому что он его как правило забирает обратно раньше, чем другое ядро успевает проснуться. В 3.2 какой-то чувак сделал новый, более вменяемый механизм (но суть осталась та же но потом обнаружил, что всё равно хуита: теперь ИО треды нормально забирают ГИЛ обратно, но поскольку вообще каждая ИО операция, в том числе и те, которые в подавляющем большинстве случаев не блокируются — send, write, — его отпускают, получается, что они используют только небольшой кусочек своего таймслайса. После этого чувак прихачил поверх priority boost, вроде теперь работает нормально, но и когда оно в стейбл третьего питона попадёт, неизвестно, а уж когда его портируют обратно во второй, и портируют ли вообще (потому что если честно третий никому нафиг не сдался неизвестно.
Так что используй процессы, чё. Это в общем-то и правильней, глюков меньше и всё такое. Под виндой можно использовать Pool, чтобы не беспокоиться насчёт тормозов при создании процессов. Кстати, в subprocessing'e на самом деле огромнейшее количество полезных штук, прочитай мануал про него сначала, чтобы не изобретать велосипеды!
А ещё кстати: я недавно узнал, что я наврал про атомарность например доступа к элементу хэштейбла, нифига оно не атомарно для типов с переопределённым __eq__ и __hash__. То есть падать ничего не должно всё равно, но интересные баги теоретически могут появиться.
даже JavaScript-интерпретатор в мозилле и то по-нормальному thread-safe (используем на многоядерных машинах, как раз в том числе и для parallelism хотя сама мозилла (браузер) его в 99% случаев из одного потока использует...
Питон дико динамический, там практически всё можно подпатчить в рантайме. Вплоть до залезания немытыми руками в глобальный список импортированных модулей. Благодаря этому существует Psyco (JIT compiler Cython и забавные штуки вроде модуля, добавляющего goto и comefrom — всё на уровне приложения, без необходимости перекомпилировать интерпретатор.
У этого есть неприятное следствие: скорость доступа к внутренним структурам интерпретатора и вообще ко всему является основным тормозящим фактором. Потому что он к ним доступается постоянно. Ну то есть когда ты засовываешь объект в хэштейбл, оно доступается к методам __eq__ или __hash__ его класса и всех остальных родительских классов. Поэтому если синхронизировать каждый такой доступ, интерпретатор мгновенно затормаживается в разы.
Когда-то лет пять-восемь назад какой-то чувак подпатчил интерпретатор, чтобы он стал thread safe — получил двукратные тормоза на single core и более или менее такую же производительность, как у обычного CPython, в хорошо паралеллящихся задачах на квадкоре. Разумеется, смысла такое делать нет.
Более того, где-то год назад чуваки в гугле начали делать свою реимплементацию интерпретатора, Unladen Swallow, поверх LLVM. Одной из целей было избавиться от GIL. Ну и опять, несколько месяцев назад они заявили, что вообще оно у них работает теперь довольно быстро, но попытка избавиться от GIL привела к приблизительно тому же результату, так что забейте, чуваки, лучше использовать быстрый лок-фри интерпретатор в отдельных процессах.
И это правильно, по-моему. Дело даже не в том, что синхронизация тредов порождает столько трудноотлавливаемых ошибок, что уже поэтому её лучше избегать, а в том, что мы сейчас в довольно нетипичном времени с точки зрения архитектуры процессоров находимся. Типа, что есть возможность более или менее эффективно доступаться к shared memory с четырёх ядер, например. Я уверен, что для 32 ядер, а то и 16, такой возможности уже не будет, вообще. Программы для 32-ядерных процессоров будут программироваться исключительно через а) passing immutable messages, б) publishing immutable shared memory. Соответственно текущая питоновская модель отлично ляжет на это будущее железо, а все попытки её "исправить" окажутся пустой тратой времени, потому что не лягут.
Более того, где-то год назад чуваки в гугле начали делать свою реимплементацию интерпретатора, Unladen Swallow, поверх LLVM. Одной из целей было избавиться от GIL. Ну и опять, несколько месяцев назад они заявили, что вообще оно у них работает теперь довольно быстро, но попытка избавиться от GIL привела к приблизительно тому же результату, так что забейте, чуваки, лучше использовать быстрый лок-фри интерпретатор в отдельных процессах.Отсюда
Global Interpreter Lock
From an earlier draft of this document:
In addition, we intend to remove the GIL and fix the state of multithreading in Python. We believe this is possible through the implementation of a more sophisticated GC system, something like IBM's Recycler (Bacon et al, 2001).
Our original plans for dealing with the GIL centered around Recycler, a garbage collection scheme proposed by IBM researchers. This appeared at first blush to be an excellent match for Python, and we were excited about prospects for success. Further investigation and private communications revealed that safethread, an experimental branch of CPython, had also implemented Recycler, but with minimal success. The author relayed that he had demonstrated a speed-up on a dual-core system, but performance degraded sharply at four cores.
Accordingly, we are no longer as optimistic about our chances of removing the GIL completely. We now favor a more incremental approach improving the shortcomings pointed out by Dave Beazley, among others. In any case, work on the GIL should be done directly in mainline CPython, or on a very close branch of Python 3.x: the sensitive nature of the work recommends a minimal delta, and doing the work and then porting it from 2.x to 3.y (as would be the case for Unladen Swallow) is a sure-fire way of introducing exceedingly-subtle bugs.
Longer-term, we believe that CPython should drop reference counting and move to a pure garbage collection system. There is a large volume of classic literature and on-going research into garbage collection that could be more effectively exploited in a pure-GC system. Even without the GIL, the current volume of refcount update operations will make scaling effectively to many-core systems a challenge; we believe a pure garbage collection scheme would alleviate these pressures somewhat.
В питон-тредах я окончательно разочаровался. Придётся на разные процессы разделять.а как соотносятся питон-треды и PyQt треды? Одно реализовано через другое?
если передать в pyqt тред байт код, то тамбудет что-то типа
qt-тред
{
py_allow_threads
какая-то qt-хрень
py_disallow_threads
user-функция (которая в байт коде и юзает GIL)
py_allow_threads
другая qt-хрень
py_disallow_threads
}
Короче, gil по умолчанию залочен и в этом идея. И только иногда его можно разлочить на уровне с-вызовов (не знаю как это правильно назвать.)
т.е. любая фукнция на нижнем уровне написана так
function
{
py_allow_threads
делаем что-то, что точно уже не будет ни с чем конфликтовать (например, запись в файл)
py_disallow_threads
}
И это правильно, по-моему. Дело даже не в том, что синхронизация тредов порождает столько трудноотлавливаемых ошибок, что уже поэтому её лучше избегатьсогласен. С самого начала мучительно я думал, что от тредов надо уходить, но как-то не хватало веской причины. Тут даже несколько топиков было про это.
Хорошо, что оно сейчас упало, а не через пол-года.
всем ещё раз спасибо!
Новость вышла про PyPy 1.2, говорят даже быстро работает (перестал отставать от CPython)
Оставить комментарий
Phoenix
Как можно посмотреть, к какому объекту обращаются сразу 2 треда?Прога валится с сообщением "программа выполнила недопустимую операцию и будет закрыта"
Нашёл всякие info thread, frame, но как их вызвать-то, если прога уже сломалась.
Если запускать "python -m pdb myprog.py " - то тоже самое, что и без отладчика.