Здравствуйте, Философ, Вы писали:
Ф>Всё было бы просто, если бы задачи были совсем независимы, по данным, но это не про реальную жизнь: в реальной жизни независимых задач крайне мало, и с ними всё решается достаточно просто, например, с помощью Parallel.ForEach(), который в составе параметров принимает в том числе и CancellationToken.
Ф>Повторюсь: убивать поток — плохая идея. Даже с процессом может плохо получиться: Ф> при грубом завершении потока тебе нужно как минимум файлы закрыть (и, кстати, закрывать ли — тот ещё вопрос), а ещё лучше — объекты синхронизации в правильное состояние вернуть — вот это уже совсем не простая задача, которая в общем случае не решается.
Ф>Для прекращения выполнения кода существуют другие методы. Самый удачный, на мой взгляд — реакция самой задачи на CancellationToken.
Ф>Может быть ты знаешь какую-то магию, которую я не знаю в этой области? Как эта проблема решается в Erlang'е?
потоки должны быть изолированы по данным. в true FP языках с их once-assign это решается автоматически, в других — при минимальных усилиях со стороны программиста
правда, есть ещё всякие memory buffers & files, они могут либо принадлежать специальному потоку, тогда алгоритм выглядит так — убиваем использующие их потоки, затем даём ему команду освободить ресурсы; либо поток должен завершаться структурированно и тогда их можно как обычно освободить обычным деструктором в потоке-владельце
далее, поток можно убить либо низкоуровнево — просто перестать отдавать ему тайм-слайсы, либо структурированно — возбудить в нём исключение. в эрланге и других интерпретаторах можно просто вставить проверку в цикл интерпретации, когда система находится в заведомо консистентном состоянии. в ghc, нативном компиляторе — в момент выделения памяти, благо что в once-assign languages она распределяется постоянно. в результате этой проверки в ghc возбуждается асинхронное исключение, которое дальше обрабатывается обычным структурированным образом
при неструктурированном убийстве и автоматическом переключении потоков первая проблема — возвращение памяти, принадлежащей переменным внутри этого потока. её можно решить, используя GC. вторая — неконсистентность разделяемых данных, её можно решить, используя в убиваемых потоках только message passing, и оставляя использование разделяемых данных на долю структурированно завершаемых потоков
наконец, в С++ можно структурированно убивать потоки, возложив весь message passing между ними на специально написанную библиотеку, такую как tbb/ppl. в ней GetMessage()/PutMessage() должен просто возбуждать исключение при необходимости убийства потока, таким образом любые проблемы в других потоках не отразятся на нашем — он просто доработает до очередной точки синхронизации самостоятельно и дальше умрёт если что-то у соседей пошло не так. я сейчас использую такой подход (правда с своей реализацией вместо tbb/ppl) и вроде всё работает