Как вы говорите сами, вы можете использовать аннулирование потока, чтобы решить эту проблему.За исключением отмены потока, я не думаю, что есть «правильный» способ решить эту проблему в POSIX (пробуждение вызова poll
с помощью write
не совсем универсальный метод, который будет работать для всех ситуаций, в которыхпоток может быть заблокирован), потому что парадигма POSIX для создания системных вызовов и обработки сигналов просто не позволяет вам сократить разрыв между проверкой флага и потенциально длинным блокирующим вызовом.
void handler() { dont_enter_a_long_blocking_call_flg=1; }
int main()
{ //...
if(dont_enter_a_long_blocking_call_flg)
//THE GAP; what if the signal arrives here ?
potentially_long_blocking_call();
//....
}
muslБиблиотека libc использует сигналы для отмены потока (поскольку сигналы могут прервать вызовы с длительной блокировкой в режиме ядра) и использует их вместе с глобальными метками сборки, чтобы из обработчика установки флага SIGCANCEL это можно было сделать (концептуально,Я не вставляю их фактический код):
void sigcancel_handler(int Sig, siginfo_t *Info, void *Uctx)
{
thread_local_cancellation_flag=1;
if_interrupted_the_gap_move_Program_Counter_to_start_cancellation(Uctx);
}
Теперь, если вы изменили if_interrupted_the_gap_move_Program_Counter_to_start_cancellation(Uctx);
на if_interrupted_the_gap_move_Program_Counter_to_make_the_syscall_fail(Uctx);
и экспортировали функцию if_interrupted_the_gap_move_Program_Counter_to_make_the_syscall_fail
вместе с thread_local_cancellation_flag
.
затем вы можете использовать его, чтобы *:
- решить вашу проблему, надежно внедрив надежное подавление сигнала с любым сигналом без необходимостипоместить любой из этих
pthread_cleanup_{push,pop}
элементов в ваш уже работающий поточно-ориентированный однопоточный код - , чтобы обеспечить гарантированную реакцию в нормальном контексте на доставку сигнала в целевой поток, даже если сигнал обрабатывается.
По существу без такого расширения libc, если вы однажды kill()/pthread_kill()
обработаете процесс / поток с сигналом, который он обрабатывает, или если поместите функцию в таймер отправки сигнала, вы не сможете быть уверены в гарантированной реакции надоставка сигнала, так как цель вполне может получить сигнал в промежутке, как указано выше, и зависать бесконечно, вместо того чтобы отвечать на него.
Я реализовал такое расширение libc поверх musl libc и опубликовал его сейчас https://github.com/pskocik/musl. В каталоге SIGNAL_EXAMPLES также приведены некоторые примеры kill()
, pthread_kill
и setitimer()
, которые в условиях продемонстрированной гонки зависают с классическими libcs, но не имеют моего расширенного musl.Вы можете использовать этот расширенный мусл для четкого решения вашей проблемы, и я также использую его в своем личном проекте для надежного отмены потока без необходимости засорять мой код с помощью pthread_cleanup_{push,pop}
Очевидным недостатком этого подхода является то, что оннепереносимый, и у меня только это реализовано для мусульманин x86_64.Я опубликовал его сегодня в надежде, что кто-то (Cygwin, MacOSX?) Скопирует его, потому что я думаю, что это правильный способ сделать отмену в C.
В C ++ и с glibc, вы могли бы использовать тот факт,что glibc использует исключения для реализации отмены потока и просто использует pthread_cancel
(который использует сигнал (SIGCANCEL) внизу), но перехватывает его вместо того, чтобы позволить ему убить поток.
Примечание:
Я действительно использую два локальных флага потока - флаг прерывателя, который разрывает следующий системный вызов с помощью ECANCELED, если он установлен до ввода системного вызова (EINTR, возвращаемый из потенциально длинного блокирующего системного вызова, превращается в ECANCELED в измененном libc-обеспечивает упаковку syscall, если установлен флаг прерывания) и сохраненный флаг прерывания - в тот момент, когда был использован флаг прерывания, он сохраняется в сохраненном флаге прерывания и обнуляется, чтобы флаг прерывания не прерывался при потенциально длительных блокирующих системных вызовах.
Идея состоит в том, что отменяющие сигналы обрабатываются по одному (обработчик сигналов можно оставить с заблокированными всеми / большинством сигналов; затем код обработчика (если таковой имеется) может разблокировать их), и что правильная проверка кода начинает разматываться, т.е. , убирая при возврате ошибки, в тот момент, когда он видит ECANCELED. Тогда следующий потенциально длинный системный вызов блокировки может быть в коде очистки (например, код, который записывает </html>
в сокет), и этот системный вызов должен быть доступным (если флаг прерывания остается, он не будет). Конечно, с кодом очистки, содержащим, например, write(1,"</html>",...)
, он также может блокироваться неопределенно долго, но вы могли бы написать код очистки так, чтобы потенциально длинный системный вызов там выполнялся под таймером, когда очистка вызвана ошибкой (ECANCELED это ошибка). Как я уже упоминал, это расширение позволяет работать с надежными таймерами, не зависящими от состояния гонки, с сигналами.
Преобразование EINTR => ECANCELED происходит так, что зацикливание кода на EINTR знает, когда прекратить зацикливание (многие EINTR (= сигнал прервал системный вызов) не могут быть предотвращены, и код должен просто обработать их, повторив системный вызов. Я использую ECANCELED как «EINTR, после которого вы не должны повторять попытку».