Как принудительно завершить один из потоков запущенного многопоточного приложения
Как сделать многопоточный ответ программы python на ключевое событие Ctrl+C?
Edit: код выглядит так:
Я попытался удалить join () во всех потоках, но он все еще не работает. Это потому, что сегмент блокировки внутри процедуры run() каждого потока?
Edit: вышеуказанный код должен работать, но он всегда прерывается, когда текущая переменная находилась в диапазоне 5,000-6,000 и через ошибки, как ниже
Сделайте каждый поток, кроме основного, демоном ( t.daemon = True в 2.6 или лучше, t.setDaemon(True) в 2.6 или меньше, для каждого объекта потока t прежде чем вы начнете его). Таким образом, когда основной поток получает KeyboardInterrupt, если он не поймает его или не поймает, но все равно решит завершить, весь процесс завершится. См.документы.
редактировать: только что увидев код OP (не был первоначально опубликован) и утверждение, что "он не работает", кажется, я должен добавить.
конечно, если вы хотите, чтобы ваш основной поток оставался отзывчивым (например, для управления-C), не мешайте ему блокировать вызовы, такие как join ing другой поток - особенно не полностью бесполезно блокировка вызовов, таких как join ing демон потоки. Например, просто изменить последний цикл в главном потоке от текущей (utterless и повреждения):
к чему-то более разумному например:
если ваш main не имеет ничего лучшего, чем либо для всех потоков, чтобы закончить самостоятельно, либо для управления-C (или другого сигнала), который будет принят.
конечно, есть много других полезных шаблонов, Если вы предпочитаете, чтобы ваши потоки не заканчивались резко ( как могут демонические потоки) - если они тоже погрязли навсегда в безоговорочно-блокирующих звонках, тупиках и тому подобном;-).
есть два основных способа, один чистый и один простой.
чистый способ-поймать KeyboardInterrupt в главном потоке и установить флаг, который ваши фоновые потоки могут проверить, чтобы они знали, чтобы выйти; вот простая / слегка грязная версия с использованием глобального:
грязный, но простой способ-поймать KeyboardInterrupt и вызвать ОС._exit (), который немедленно завершает все потоки.
Я бы предпочел пойти с кодом, предложенным в этот блог:
Обычно поток завершается при выходе из потоковой функции или вследствие вызова функции pthread_exit(). Но существует возможность запросить из одного потока уничтожение другого. Это называется отменой, или принудительным завершением, потока.
Чтобы отменить поток, вызовите функцию pthread_cancel(), передав ей идентификатор требуемого потока. Далее можно дождаться завершения потока. Вообще-то, это обязательно нужно делать с целью освобождения ресурсов, если только поток не является отсоединенным. Отмененный поток возвращает специальное значение PTHREAD_CANCELED.
Во многих случаях поток выполняет код, который нельзя просто взять и прервать. Например, поток может выделить какие-то ресурсы, поработать с ними, а затем удалить. Если отмена потока произойдет где-то посередине, освободить занятые ресурсы станет невозможно, вследствие чего они окажутся потерянными для системы. Чтобы учесть эту ситуацию, поток должен решить, где и когда он может быть отменен.
С точки зрения возможности отмены поток находится в одном из трех состояний.
? Асинхронно отменяемый. Такой поток можно отменить в любой точке его выполнения.
? Синхронно отменяемый. Поток можно отменить, но не везде. Запрос на отмену помещается в очередь, и поток отменяется только по достижении определенной точки.
? Неотменяемый. Попытки отменить поток игнорируются. Первоначально поток является синхронно отменяемым.
Уничтожение (отмена) потока
Уничтожение (отмена) потока Корректное завершение выполняющегося потока «извне», из другого потока (то есть асинхронно относительно прерываемого потока), — задача отнюдь не тривиальная; она намного сложнее аналогичной задачи прерывания процесса. Это связано с
13.2.3. Отмена отображения областей
13.2.3. Отмена отображения областей После окончания отображения в памяти процесс может отменить отображение памяти с помощью munmap(). Это приводит к тому, что последующие доступы к этому адресу будут генерировать SIGSEGV (если только память не будет перераспределена) и сохраняет
Асинхронная отмена вызовов для InterBase 6.5
Асинхронная отмена вызовов для InterBase 6.5 Начиная с версии 6 5 Gemini ODBC-драйвер способен использовать новую возможность InterBase версии 6.5 - асинхронную отмену выполняющихся на сервере
Отмена деформаций, или инструмент Реконструкция
Отмена деформаций, или инструмент Реконструкция Отменить сделанные изменения в картинке можно нажав сочетание клавиш Ctrl+Z или нажав кнопку Отмена в окне Пластика. В последнем случае, помимо того что деформации будут отменены, будет еще и закрыто окно Пластика.Однако
Отмена нескольких последних действий
Отмена нескольких последних действий Отменим сделанные изменения, чтобы все клипы проекта располагались в одном списке в окне Project (Проект).1. Перейдите на вкладку History (История), расположенную в левом нижнем окне программы. На ней отображается список всех действий над
Отмена объектной привязки
Отмена объектной привязки Snap to None – режим отмены всех текущих и разовых режимов объектной
8.5. Отмена выполнения потоков
8.5. Отмена выполнения потоков Обсуждая листинг 8.4, мы обратили внимание на наличие проблемы, возникающей при отмене выполнения потока, заблокированного вызовом pthread_cond_wait. Выполнение потока может быть отменено в том случае, если какой-нибудь другой поток вызовет функцию
Отмена действия
Отмена действия Если вы выполнили ненужное или неправильное действие, например случайно удалили объект, то можете отменить ошибочное действие. Для этого предназначена кнопка Undo (Возврат) на главной панели инструментов. Каждый щелчок на ней позволяет последовательно
Отмена проверки соответствия правилам BP 1.1
Отмена проверки соответствия правилам BP 1.1 Чтобы полностью отключить проверку соответствия BP 1.1 для Web-сервиса XML, определите в соответствующем файле Web.соnfig элемент‹conformanceWarnings›. ‹configuration› ‹webServices› ‹conformanceWarnings› ‹remove name="BasicProfile1_1" /›
Отмена объектной привязки
Отмена объектной привязки Snap to None – режим отмены всех текущих и разовых режимов объектной
Отмена полномочий
Отмена полномочий Оператор REVOKE требуется для удаления полномочий, назначенных операторами GRANT. Согласно стандарту, REVOKE должен каскадом отменить все привилегии, полученные всеми пользователями как результат WITH GRANT OPTION от данного пользователя. Однако вам не следует на это
Отмена объектной привязки
Отмена объектной привязки Snap to None – режим отмены всех текущих и разовых режимов объектной
Отмена и повторение изменений
Отмена и повторение изменений Многие действия, произведенные над проектом, можно отменить. Выполняется это с помощью команды меню Монтаж ? Отменить <действие>. Вместо <действие> после слова Отменить в названии пункта меню записано, какое конкретно действие будет
Отмена открытого ключа.
Отмена открытого ключа. Предположим, что ваш секретный ключ и фраза пароля каким-то образом были скомпрометированы. Вы должны сообщить об этом миру, чтобы никто более не использовал ваш открытый ключ. Для этого вы должны выпустить удостоверение отмены ключа для отмены
Отмена ошибочных действий
Отмена ошибочных действий Если вы ошиблись при выполнении любой операции с текстом, не расстраивайтесь, даже если удалили большой фрагмент нужного текста. Word автоматически запоминает все выполняемые вами действия и позволяет отменить любые из них, вплоть до самого
Отмена выполненного действия
Прежде чем приступать к программированию потоков, следует ответить на вопрос, а нужны ли они вам. Мы уже знаем, насколько хорошо развиты в Linux средства межпроцессного взаимодействия. С помощью управления процессами в Linux можно решить многие задачи, которые в других ОС решаются только с помощью потоков. Потоки часто становятся источниками программных ошибок особого рода. Эти ошибки возникают при использовании потоками разделяемых ресурсов системы (например, общего адресного пространства) и являются частным случаем более широкого класса ошибок – ошибок синхронизации. Если задача разделена между независимыми процессами, то доступом к их общим ресурсам управляет операционная система, и вероятность ошибок из-за конфликтов доступа снижается. Впрочем, разделение задачи между несколькими независимыми процессами само по себе не защитит вас от других разновидностей ошибок синхронизации. В пользу потоков можно указать то, что накладные расходы на создание нового потока в многопоточном приложении ниже, чем накладные расходы на создание нового самостоятельного процесса. Уровень контроля над потоками в многопоточном приложении выше, чем уровень контроля приложения над дочерними процессами. Кроме того, многопоточные программы не склонны оставлять за собой вереницы зомби или «осиротевших» независимых процессов.
Первая подсистема потоков в Linux появилась около 1996 года и называлась, без лишних затей, – LinuxThreads. Рудимент этой подсистемы, который вы найдете в любой современной системе Linux, – файл /usr/include/pthread.h, указывает год релиза – 1996 и имя разработчика – Ксавье Лерой (Xavier Leroy). Библиотека LinuxThreads была попыткой организовать поддержку потоков в Linux в то время, когда ядро системы еще не предоставляло никаких специальных механизмов для работы с потоками. Позднее разработку потоков для Linux вели сразу две конкурирующие группы – NGPT и NPTL. В 2002 году группа NGPT фактически присоединилась к NPTL и теперь реализация потоков NPTL является стандартом Linux. Подсистема потоков Linux стремится соответствовать требованиям стандартов POSIX, так что новые многопоточные приложения Linux должны без проблем компилироваться на новых POSIX-совместимых системах.
Потоки и процессы
Тем, кто впервые познакомился с концепцией потоков, изучая программирование для Windows, модель потоков Linux покажется непривычной. В среде Microsoft Windows процесс, – это контейнер для потоков (именно этими словами о процессах говорит Джефри Рихтер в своей классической книге «Программирование приложений для Microsoft Windows»). Процесс-контейнер содержит как минимум один поток. Если потоков в процессе несколько, приложение (процесс) становится многопоточным. В мире Linux все выглядит иначе. В Linux каждый поток является процессом, и для того, чтобы создать новый поток, нужно создать новый процесс. В чем же, в таком случае, заключается преимущество многопоточности Linux перед многопроцессностью? В многопоточных приложениях Linux для создания дополнительных потоков используются процессы особого типа. Эти процессы представляют собой обычные дочерние процессы главного процесса, но они разделяют с главным процессом адресное пространство, файловые дескрипторы и обработчики сигналов. Для обозначения процессов этого типа, применяется специальный термин – легкие процессы (lightweight processes). Прилагательное «легкий» в названии процессов- потоков вполне оправдано. Поскольку этим процессам не нужно создавать собственную копию адресного пространства (и других ресурсов) своего процесса- родителя, создание нового легкого процесса требует значительно меньших затрат, чем создание полновесного дочернего процесса. Поскольку потоки Linux на самом деле представляют собой процессы, в мире Linux нельзя говорить, что один процесс содержит несколько потоков. Если вы скажете это, в вас тут же заподозрят вражеского лазутчика!Интересно рассмотреть механизм, с помощью которого Linux решает проблему идентификаторов процессов потоков. В Linux у каждого процесса есть идентификатор. Есть он, естественно, и у процессов-потоков. С другой стороны, спецификация POSIX 1003.1c требует, чтобы все потоки многопоточного приложения имели один идентификатор. Вызвано это требование тем, что для многих функций системы многопоточное приложение должно представляться как один процесс с одним идентификатором. Проблема единого идентификатора решается в Linux весьма элегантно. Процессы многопоточного приложения группируются в группы потоков (thread groups). Группе присваивается идентификатор, соответствующий идентификатору первого процесса многопоточного приложения. Именно этот идентификатор группы потоков используется при «общении» с многопоточным приложением. Функция getpid(2), возвращает значение идентификатора группы потока, независимо от того, из какого потока она вызвана. Функции kill() waitpid() и им подобные по умолчанию также используют идентификаторы групп потоков, а не отдельных процессов. Вам вряд ли понадобится узнавать собственный идентификатор процесса-потока, но если вы захотите это сделать, вам придется воспользоваться довольно экзотичной конструкцией. Получить идентификатор потока (thread ID) можно с помощью функции gettid(2), однако саму функцию нужно еще определить с помощью макроса _syscall. Работа с функцией gettid() выглядит примерно так:
Более подробную информацию вы можете получить на страницах man, посвященных gettid() и _syscall. Потоки создаются функцией pthread_create(3), определенной в заголовочном файле <pthread.h>. Первый параметр этой функции представляет собой указатель на переменную типа pthread_t, которая служит идентификатором создаваемого потока. Второй параметр, указатель на переменную типа pthread_attr_t, используется для передачи атрибутов потока. Третьим параметром функции pthread_create() должен быть адрес функции потока. Эта функция играет для потока ту же роль, что функция main() – для главной программы. Четвертый параметр функции pthread_create() имеет тип void *. Этот параметр может использоваться для передачи значения, возвращаемого функцией потока. Вскоре после вызова pthread_create() функция потока будет запущена на выполнение параллельно с другими потоками программы. Таким образом, собственно, и создается новый поток. Я говорю, что новый поток запускается «вскоре» после вызова pthread_create() потому, что перед тем как запустить новую функцию потока, нужно выполнить некоторые подготовительные действия, а поток-родитель между тем продолжает выполняться. Непонимание этого факта может привести вас к ошибкам, которые трудно будет обнаружить. Если в ходе создания потока возникла ошибка, функция pthread_create() возвращает ненулевое значение, соответствующее номеру ошибки.
Функция потока должна иметь заголовок вида:
- функция потока вызвала функцию pthread_exit(3);
- функция потока достигла точки выхода;
- поток был досрочно завершен другим потоком.
Функция pthread_exit() представляет собой потоковый аналог функции _exit(). Аргумент функции pthread_exit(), значение типа void *, становится возвращаемым значением функции потока. Как (и кому?) функция потока может вернуть значение, если она не вызывается из программы явным образом? Для того, чтобы получить значение, возвращенное функцией потока, нужно воспользоваться функцией pthread_join(3). У этой функции два параметра. Первый параметр pthread_join(), – это идентификатор потока, второй параметр имеет тип «указатель на нетипизированный указатель». В этом параметре функция pthread_join() возвращает значение, возвращенное функцией потока. Конечно, в многопоточном приложении есть и более простые способы организовать передачу данных между потоками. Основная задача функции pthread_join() заключается, однако, в синхронизации потоков. Вызов функции pthread_join() приостанавливает выполнение вызвавшего ее потока до тех пор, пока поток, чей идентификатор передан функции в качестве аргумента, не завершит свою работу. Если в момент вызова pthread_join() ожидаемый поток уже завершился, функция вернет управление немедленно. Функцию pthread_join() можно рассматривать как эквивалент waitpid(2) для потоков. Эта функция позволяет вызвавшему ее потоку дождаться завершения работы другого потока. Попытка выполнить более одного вызова pthread_join() (из разных потоков) для одного и того же потока приведет к ошибке.
Посмотрим, как все это работает на практике. Ниже приводится фрагмент листинга программы threads, (полный текст программы вы найдете в исходниках программы в файле threads.c).
Рассмотрим сначала функцию thread_func(). Как вы, конечно, догадались, это и есть функция потока. Наша функция потока очень проста. В качестве аргумента ей передается указатель на переменную типа int, в которой содержится номер потока. Функция потока распечатывает этот номер несколько раз с интервалом в одну секунду и завершает свою работу. В функции main() вы видите две переменных типа pthread_t. Мы собираемся создать два потока и у каждого из них должен быть свой идентификатор. Вы также видите две переменные типа int, id1 и id2, которые используются для передачи функциям потоков их номеров. Сами потоки создаются с помощью функции pthread_create().В этом примере мы не модифицируем атрибуты потоков, поэтому во втором параметре в обоих случаях передаем NULL. Вызывая pthread_create() дважды, мы оба раза передаем в качестве третьего параметра адрес функции thread_func, в результате чего два созданных потока будут выполнять одну и ту же функцию. Функция, вызываемая из нескольких потоков одновременно, должна обладать свойством реентерабельности (этим же свойством должны обладать функции, допускающие рекурсию). Реентерабельная функция, это функция, которая может быть вызвана повторно, в то время, когда она уже вызвана (отсюда и происходит ее название). Реентерабельные функции используют локальные переменные (и локально выделенную память) в тех случаях, когда их не-реентерабельные аналоги могут воспользоваться глобальными переменными.
Мы вызываем последовательно две функции pthread_join() для того, чтобы дождаться завершения обоих потоков. Если мы хотим дождаться завершения всех потоков, порядок вызова функций pthread_join() для разных потоков, очевидно, не имеет значения.
Для того, чтобы скомпилировать программу threads.c, необходимо дать команду:
Команда компиляции включает макрос _REENTERANT. Этот макрос указывает, что вместо обычных функций стандартной библиотеки к программе должны быть подключены их реентерабельные аналоги. Реентерабельный вариант библиотеки glibc написан таким образом, что вы, скорее всего, вообще не обнаружите никаких различий в работе с реентерабельными функциями по сравнению с их обычными аналогами. Мы указываем компилятору путь для поиска заголовочных файлов и путь для поиска библиотек /usr/include/nptl и /usr/lib/nptl соответственно. Наконец, мы указываем компоновщику, что программа должна быть связана с библиотекой libpthread, которая содержит все специальные функции, необходимые для работы с потоками.
У вас, возможно, возникает вопрос, зачем мы использовали две разные переменные, id1 и id2, для передачи значений двум потокам? Почему нельзя использовать одну переменную, скажем id, для обоих потоков? Рассмотрим такой фрагмент кода:
Конечно, в этом случае оба потока получат указатель на одну и ту же переменную, но ведь значение этой переменной нужно каждому потоку только в самом начале его работы. После того, как поток присвоит это значение своей локальной переменной loc_id, ничто не мешает нам использовать ту же переменную id для другого потока. Все это верно, но проблема заключается в том, что мы не знаем, когда первый поток начнет свою работу. То, что функция pthread_create() вернула управление, не гарантирует нам, что поток уже выполняется. Вполне может случиться так, что первый поток будет запущен уже после того, как переменной id будет присвоено значение 2. Тогда оба потока получат одно и то же значение id. Впрочем, мы можем использовать одну и ту же переменную для передачи данных функциям потока, если воспользуемся средствами синхронизации. Этим средствам будет посвящена следующая статья.
Досрочное завершение потока
Функции потоков можно рассматривать как вспомогательные программы, находящиеся под управлением функции main(). Точно так же, как при управлении процессами, иногда возникает необходимость досрочно завершить процесс, многопоточной программе может понадобиться досрочно завершить один из потоков. Для досрочного завершения потока можно воспользоваться функцией pthread_cancel(3). Единственным аргументом этой функции является идентификатор потока. Функция pthread_cancel() возвращает 0 в случае успеха и ненулевое значение в случае ошибки. Несмотря на то, что pthread_cancel() может завершить поток досрочно, ее нельзя назвать средством принудительного завершения потоков. Дело в том, что поток может не только самостоятельно выбрать порядок завершения в ответ на вызов pthread_cancel(), но и вовсе игнорировать этот вызов. Вызов функции pthread_cancel() следует рассматривать как запрос на выполнение досрочного завершения потока. Функция pthread_setcancelstate(3) определяет, будет ли поток реагировать на обращение к нему с помощью pthread_cancel(), или не будет. У функции pthread_setcancelstate() два параметра, параметр state типа int и параметр oldstate типа «указатель на int». В первом параметре передается новое значение, указывающее, как поток должен реагировать на запрос pthread_cancel(), а в переменную, чей адрес был передан во втором параметре, функция записывает прежнее значение. Если прежнее значение вас не интересует, во втором параметре можно передать NULL.Чаще всего функция pthread_setcancelstate() используется для временного запрета завершения потока. Допустим, мы программируем поток, и знаем, что при определенных условиях программа может потребовать его досрочного завершения. Но в нашем потоке есть участок кода, во время выполнения которого завершать поток крайне нежелательно. Мы можем оградить этот участок кода от досрочного завершения с помощью пары вызовов pthread_setcancelstate():
Первый вызов pthread_setcancelstate() запрещает досрочное завершение потока, второй – разрешает. Если запрос на досрочное завершение потока поступит в тот момент, когда поток игнорирует эти запросы, выполнение запроса будет отложено до тех пор, пока функция pthread_setcancelstate() не будет вызвана с аргументом PTHREAD_CANCEL_ENABLE. Что именно произойдет дальше, зависит от более тонких настроек потока. Рассмотрим пример программы (на диске вы найдете ее в файле canceltest.c)
Впрочем, мы можем выполнить досрочное завершение потока, не дожидаясь точек останова. Для этого необходимо перевести поток в режим немедленного завершения, что делается с помощью вызова pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); В этом случае беспокоиться о точках останова уже не нужно. Вызов pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); снова переводит поток в режим отложенного досрочного завершения.
Тема потоков практически неисчерпаема (простите за каламбур), но мы посвятим потокам только лишь еще одну статью, в которой рассмотрим вопросы синхронизации и атрибуты потоков.
Привет всем и снова вопрос про многопоточность, выкладываю исходный код тестового примера. И сразу к вопросу, есть код расчёта который работает в потоке мы можем его принудительно закрыть используя используя флаг stop по нажатию кнопки и в конце функции посылать сигнал emit finished() все работает. Но когда у нас в функции потока идет обращение к функции которая определена в dll которую мы воспринимаем как черный ящик, т.е в потоке идет обращение к ней, в её аргументы идут данные и внутри нее рассчитываются, в самой dll, то как же ее принудительно прервать и удалить поток? внутри нее нет флагов и сигналов о ее прерывании, она работает пока не досчитает. Считает внутри пару минут и мы ждем возвращающее значение и только по выходу из нее наш поток прерывается, а затем удаляется по нашему флагу и сигналу о завершении finished(). И повторюсь, так как прервать принудительно поток? даже если внутри функции dll еще идет расчёт, не дожидаясь возвращающего значения? выкладываю тестовый рабочий пример для ориентира.
833 1 1 золотой знак 8 8 серебряных знаков 22 22 бронзовых знакаСамым лучшим решением является вариант с модернизацией самой библиотеки с тем, чтобы она принимала и понимала сигналы от управляющего потока. Разумеется, если доступны исходники этой самой библиотеки. В обратном случае, когда контекст выполнения уходит в подключаемую библиотеку, власть над ним теряется, и всё, что возможно сделать, это или резко одёрнуть поводок, применив силу, или терпеливо ждать, когда же тот вернётся в лоно заботливого управления.
Применение силы добром оканчивается редко, но если решение принято сознательно и по причине особо острой необходимости, то вполне реально.
Для начала необходимо разрешить QThread принудительное и немедленное завершение контролируемого им потока:
Второе, как ни странно, непосредственно само завершение:
Следует учитывать, что код, который следует за строкой контекста выполнения в методе QThread::run() , после вызова terminate() выполнен не будет. Например:
Класс Worker согласно коду в вопросе перемещается в поток, контролируемый QThread . Помимо того, что всё последующее в Worker не выполнится, также не произойдёт и освобождение занятых ресурсов. Надо ли говорить, что может натворить сама библиотека, будучи подключенная к какому-нибудь внешнему ресурсу и производящая туда на момент экстренной остановки запись данных.
Если причиной немедленного останова потока является такая причина как то, что пользователь не может ждать, то скройте процесс работы соответствующей реакцией в графическом интерфейсе пользователя.
Например, если какая-нибудь кнопка предназначена для остановки потока, то пусть она покажет, что всё успешно выполнено, тогда как скрытый таким образом поток прозрачно продолжит свою работу и в конечном итоге завершит её корректно.
Если пользователь хочет немедленно выйти из программы, то необязательно его слушаться. Можно просто закрыть окно, не завершая сам процесс, который продолжит свою работу и также корректно завершит её.
Читайте также: