Есть ли способ убить все разветвленные потоки в сеансе GHCi без перезапуска?

На основе моего предыдущего вопроса Я хотел бы спросить, есть ли способ убить все созданные пользователем потоки в сеансе GHCi?

Причиной этого является то, что когда функция выходит из GHCi, потоки, которые она породила, не прерываются автоматически, сохраняясь даже при перезагрузке кода. Перезапуск GHCi решает это, но поскольку мое приложение занимает некоторое время, чтобы загрузить, было бы здорово, если бы было возможно (даже хакерское) обходное решение.

Ответ 1

Нет, и фактически почти по тем же причинам, что и в Как я могу построить ThreadId, учитывая, что я знаю фактическое число?: Библиотека просто не дает вам все, чтобы получить ThreadId всех (все еще запущенных) потоков или любого другого объекта для работы с любыми потоками, которые не принадлежат вам.

Кроме того, вы не можете надежно угадать, какие потоки, порожденные с помощью forkIO, относятся к вашему сеансу GHCi (все оценки обычно изолированы в формате forkIO), базовом приложении yesod или в потоковом RTS (который имеет хотя бы один вызов до forkIO, что в основном гарантирует закрытие всех менеджеров событий). В настоящее время это не так уж плохо, поскольку GHCi работает в потоке main, и диспетчер ввода-вывода перезапускается, если он выключен в любом случае, но это может измениться в будущих версиях.

Итак, почему потоки даже собираются при завершении? hs_exit(). По существу, он вызывает ioManagerDie() (убивает всех менеджеров событий) и exitScheduler(..) (см. scheduler, который в основном убивает все потоки. Ни одна из этих функций не имеет соответствующей оболочки FFI.

В то время, когда вызывается hs_exit(), main из мира Haskell уже закончен. Поскольку ни одна из этих функций не имеет соответствующего эквивалента в модулях GHC.*, вы не можете вызывать их непосредственно в Haskell и, следовательно, в GHCi, так как нет соответствующей команды :#.

Итак, в целом, вы не можете. Если бы кто-то добавил команду перезапустить планировщик в GHCi, это было бы просто как пирог. Но, учитывая, что планировщик запускается в hs_init() и останавливается в hs_exit() в модели RTS, я сомневаюсь, что это было бы легко расширение.


В зависимости от того, что вы хотите сделать, вы можете обмануть. Вы можете написать свой собственный forkIOMem, который хранит ThreadId в глобальном MVar и заменяет все forkIO в источниках. Это может быть очень громоздким, особенно если вы работаете с библиотекой, так как вам нужно обеспечить замену forkIO повсюду.

Вы могли бы, конечно, вмешиваться в пакет base, но это, вероятно, безумие (все еще возможно), измените forkIO там и добавьте killAllforkIOs в Control.Concurrent. (Я уже говорил, что это, наверное, безумие, правда?)