LINUX.ORG.RU

Как выйти из системного вызова внутри потока?

 , , ,


2

3

Сабж. Основной процесс получает сигнал.

Однако потоки находятся в системных вызовах (accept, read, и т.д.). Собственно, как из них выйти?

Почитав документацию и stackoverflow я понимаю, что:

Сигналы - прерогатива процессов. И потому лишь главный поток (тот который в main()) чаще всего получает сигнал и его обрабатывает. Остальные потоки сигналы не получают

Собственно, мне нужно чтобы ВСЕ потоки получили сигнал и выполнили хендлер для того чтобы их «выбросило» из системных вызовов.

Демонстрационный код:

#define _BSD_SOURCE
#define _DEFAULT_SOURCE

#include <stdlib.h>
#include <signal.h>
#include <pthread.h>
#include <sys/stat.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <iso646.h>

#define n_threads 4
pthread_t threads[n_threads];
pthread_t main_thread;

static void signal_handler(int signo) {
	pthread_t mypid = pthread_self();
	if (mypid != main_thread) return;

	for (unsigned i = 0; i < n_threads; i++) {
		pthread_kill(threads[i], SIGINT);
	}
}

void *worker() {
	//sigset_t set;
	//sigemptyset(&set);
	//sigaddset(&set, SIGTERM);
	//sigaddset(&set, SIGINT);
	//pthread_sigmask(SIG_UNBLOCK, &set, NULL);

	char buffer;
	while(1) {
		if (read(STDIN_FILENO, &buffer, 1) < 0 and errno == EINTR) return NULL;
	}
	return NULL;
}

int main() {
	signal(SIGINT, signal_handler);
	signal(SIGTERM, signal_handler);
	main_thread = pthread_self();

	for (unsigned i = 0; i < n_threads; i++ ) {
		pthread_create(&threads[i], NULL, worker, NULL);
	}

	for (unsigned i = 0; i < n_threads; i++ ) {
		pthread_join(threads[i], NULL);
	}

	return EXIT_SUCCESS;
}

c99 test.c -lpthread
./a.out

После запуска пожмакайте Ctrl+C, результата не будет.

Убить процесс:
killall -SIGKILL a.out


P.S. Закомментированный код не работает.

РЕШЕНИЕ: Я лично выбрал вот это: Как выйти из системного вызова внутри потока? (комментарий)

★★★★★

Последнее исправление: reprimand (всего исправлений: 2)

Ответ на: комментарий от pathfinder

Для этого мозгляки из позикс и придумали походу точки отмены…

Сложно делать на низкоуровневом API - ну, не суйтесь, используйте boost::interrup или аналогичного уровня API в C# и иже с ним.

А таймауты, даже маленькие, это хоть и снижает риск возникновения «лага» - не нивелирует его(пример не сложно придумать думаю). И уж точно никак не упрощает процедуру корректного завершения потока выполняющего i/o.

pon4ik ★★★★★
()
Ответ на: комментарий от vlad9486

Ага, а если у тебя конвейер из таких вот процессиков которые каждый по секунде спят перед выходом. Даже десяток уже будет ощутим.

Уменьшить таймаут? Не, ну можно, нафига тогда спрашивается вообще блокирующий вызов использовать?

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

Это нужно только чтобы прервать сискол. Сискол отработал, а дальше никаких таймаутов, весь конвейер моментально доработает, потому что он уже знает что мы завершаем работу.

vlad9486
()
Ответ на: комментарий от pathfinder

Не.

Существует идея, что нити должны завершать свою работу сами.

Плохая это идея. Негодная. Просто потому, что поток может захватить какой-то ресурс и не завершиться, а упасть. Ну просто не дошёл он до завершения до конца. Завершаться начал, а не смог. И что тогда с таким заваченным ресурсом делать системе, если она ничего не знает о состоянии потока и захваченных им ресурсов?

Лучше уж пусть система сама пришибает нити по мере необходимости. Её просто необходимо корректно уведомить о том, что нить больше ненужна. И ресурсы можно освободить для дальнейшего использования.

Moisha_Liberman ★★
()
Ответ на: комментарий от vlad9486

Вообще-то, это зависит от конфигурации системы.

Таймаут может быть 1 секунду, или еще меньше. Если сделать меньше, то будет больше оверхед. Все треды будут останавливаться за это время, оно не суммируется. Программы наверное висят не на таймаутах, а сохраняют информацию, делают flush базы данных, шуршат диском.

Есть ядра с вытеснением, но для серверов собирают ядра как NO_PREEMPTIVE. Как раз именно для того, чтобы по возможности не прерывать процесс или поток в самом неподходящем для этого месте и не переключать контекст задачи. Ибо дорого это.

А Вы предлагаете ещё таймауты прикрутить до кучи.

Moisha_Liberman ★★
()
Ответ на: комментарий от pon4ik

Да.

Для этого мозгляки из позикс и придумали походу точки отмены…

Да. И по тексту остального комментария тоже «да, всё так».

Moisha_Liberman ★★
()
Ответ на: Не. от Moisha_Liberman

И что тогда с таким заваченным ресурсом делать системе

Автор топика пишет не систему, а приложение. Если потоки приложения падают, то нужно чинить баг, чтобы не падали. Приложение не может продолжать работать если какой-то его поток падает, потому если во время выполнения главный поток обнаружит что какой-то поток упал, то он тоже должен поскорее завершиться, например, упасть тоже.

vlad9486
()
Ответ на: Вообще-то, это зависит от конфигурации системы. от Moisha_Liberman

Экономия на спичках. Уход из сискола по таймауту это не переключение контекста. Ну да, там будет переключение, но в рамках одного процесса даже кэш TBL не нужно сбрасывать, раз на секунду можно и переключить.

vlad9486
()
Последнее исправление: vlad9486 (всего исправлений: 1)
Ответ на: комментарий от vlad9486

И "да" и "нет".

Автор топика пишет не систему, а приложение. Если потоки приложения падают, то нужно чинить баг, чтобы не падали.

Конечно автор пишет приложение. Но в его приложении потоки реализованы как и во всей системе в общем и целом. Т.е., если потоки падают, то чинить надо их работу в приложении, а не в системе. Другие-то потоки не падают же почему-то.

Приложение не может продолжать работать если какой-то его поток падает, потому если во время выполнения главный поток обнаружит что какой-то поток упал, то он тоже должен поскорее завершиться, например, упасть тоже.

Приложение должно хотя бы в сислог про ошибку сообщение кинуть, а потом уже определиться (если по-хорошему) имеет ли смысл заново восстанавливать поток или да, лучше завершить своё исполнение.

Мы уже забыли о принципах robust programming оно же fault tolerance programming. К сожалению, это так.

Moisha_Liberman ★★
()
Ответ на: комментарий от vlad9486

Не соглашусь.

С одной стороны Вы, вроде как, и правы. Но на мой взгляд предлагаемый Вами подход усложнит реализацию такой системы. А это всегда плохо. И тут приходится жертвовать в пользу удешевления стоимости переключения контекста. Чем дешевле и проще, тем лучше. Собственно, в этом-то и есть простота реализации. Сравнительная.

Moisha_Liberman ★★
()
Ответ на: комментарий от vlad9486

Сискол это уже инвалидация кэша в следствии переключения в режим ядра. Если такой вызов происходит раз в таймаут, вместо того чтобы уснуть до возникновения ввода/вывода это выглядит как минимум расточительно, учитывая, что это делается ради обработки ситуации которая происходит на порядки реже чем сам вывод. Это могло бы быть оправданно в отсутствии альтернативного механизма, но этот механизм есть, он специально предназначен для этого самого «прерывания сискола».

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

А, ну и ещё в копилочку:

Мьютекс тоже будешь везде по таймауту захватывать, чтобы можно было потоки из сигнала корректно погасить? А бывают ли i/o потоки в многопоточном приложении без мьютексов/семафоров(не ну так-то бывают, но одепты локфри за подобное подёргивание контекста наверное первыми начнут фикалии кидать в автора)?

pon4ik ★★★★★
()

Однако потоки находятся в системных вызовах (accept, read, и т.д.). Собственно, как из них выйти?

Я бы установил переменную в значение «Идем на выход», которую поток-потомок читает у себя на входе и затем отправил из главного, родительского процесса пачку пустых данных в поток-потомок, который этих данных ждет. После принятия данных потомок читает переменную, установленную в родителе, и завершает свою работу. Суть подхода в том, что блокирующее чтение не греет процессор почем зря и потомки завершают работу, подчищая за собой.

Enthusiast ★★
()
Ответ на: комментарий от Enthusiast

Приложение открывает соединение не с самим собой, а с другим приложением, на другом компьютере. Как это будет работать, если у меня tcp сокет соединен с другим компьютером в интернете и заблокирован на recvfrom? Как туда отправить что-то? Это было бы круто если было бы возможно.

vlad9486
()
Ответ на: комментарий от pon4ik

Да, согласен. Но я хочу чтобы поток доработал сам, мне нужно чтобы поток отправил по tcp сообщение что мы будет отключаться. И перед этим, возможно, еще какие-то сообщения. И это все должно быть зашифровано. То есть, мне нужно выполнить свой код для завершения. Штатное решение дает такую возможность? Если да, то я готов попробовать.

Читаю ман, похоже что все это возможно. https://man7.org/linux/man-pages/man3/pthread_cleanup_push.3.html

vlad9486
()
Последнее исправление: vlad9486 (всего исправлений: 1)
Ответ на: комментарий от vvviperrr

Man eventfd хотя бы

Ну и чего ты хотел сказать? Этих четырех слов недостаточно, чтобы тебя понять, а угадыванием я заниматься не хочу.

Да и вообще, я не люблю писать синхронный в/в с засыпанием на таймаутах. Я предпочитаю асинхронный на базе poll/epoll. Там таких проблем нет. И да, я использовал eventfd() в своем коде, хотя не так много.

pathfinder ★★★★
()
Ответ на: комментарий от vlad9486

Кстати это один из способов(при использовании асинхронного ввода вывода, замечу лучший чем таймауты) реализовать корректное завершение для систем, где нет механизма типа точек отмены (вроде это винда например). Не знаю как сейчас, но насколько помню раньше(1.3x-1.4x) boost именно так и реализовывал thread_interrupted исключение под винду. Сокет конечно был unix, а все асинхронные очереди помимо ожидания настоящего ввода вывода ожидали ещё и события от этого чуда сокета чтобы проснуться и помереть по человечьи.

pon4ik ★★★★★
()
Ответ на: комментарий от vlad9486

Как это будет работать, если у меня tcp сокет соединен с другим компьютером в интернете и заблокирован на recvfrom? Как туда отправить что-то? Это было бы круто если было бы возможно.

Главному процессу-родителю необходимо «представиться» потоку-потомку якобы другим компьютером и передать ему данные в том виде, в котором потомок их ожидает.

Enthusiast ★★
()
Ответ на: комментарий от vvviperrr

Я к тому, что с eventfd таймауты в мультиплексирующих вызова не нужны

Ну не нужны. Ты это к чему?

Дело как раз в том, что люди не хотят всякие мультиплексирующие вызовы. Они хотят есть свой синхронный многопоточный кактус и пользоваться либо pthread_cancel, либо таймаутами. Дискуссия идет вокруг последней дилеммы.

pathfinder ★★★★
()
Последнее исправление: pathfinder (всего исправлений: 3)
Ответ на: комментарий от Enthusiast

Главному процессу-родителю необходимо «представиться» потоку-потомку якобы другим компьютером и передать ему данные в том виде, в котором потомок их ожидает.

В обычном listen/accept цикле не выйдет. Сокет с tcp-соединением создан acceptом, объединяет поток и удаленный компьютер, и вклиниться туда нельзя, разве что послать данные удалённому компьютеру и надеяться что он ответит. Если же снова коннектиться на порт приложения, то разблокируется другой поток, который в accept сидит.

khrundel ★★★★
()
Ответ на: комментарий от pathfinder

отказался от библиотеки и сам реализовал протокол FastCGI

Реализация проприетарная или её можно посмотреть/потыкать? :)

reprimand ★★★★★
() автор топика
Ответ на: комментарий от reprimand

Реализация проприетарная или её можно посмотреть/потыкать? :)

Увы, но это часть продукта который я делаю на своей работе. Там нет ничего особого, но мой работодатель выкладывание исходников не оценит.

pathfinder ★★★★
()
Ответ на: комментарий от cobold

Нет тут решения. И pthread_kill и pthread_cancel могут прервать поток совсем не в том месте, где хочется. Если в потоке исполняется библиотечный код, который к этому не готов, жди беды.

iliyap ★★★★★
()
Ответ на: комментарий от iliyap

Если в потоке исполняется библиотечный код, который к этому не готов, жди беды.

О, первый адекват.

pathfinder ★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.