LINUX.ORG.RU

И таки как правильно потоки убивать?

 ,


1

3

Вчера полдня бился, так и не разгадал загадку pthreads.

Итак, у меня есть основной поток, который проверяет, не было ли запроса на создание окна GLUT и опрашивает события окон посредством glutMainLoopEvent(). Все бы хорошо, но чтобы другие потоки (которые могут изменять содержимое отображаемого изображения) имели возможность вклиниться между блокировкой/разблокировкой мьютекса, я вставляю паузу в 10мс. И вот в этой паузе и кроется проблема: при убивании этого основного потока с помощью pthread_cancel(GLUTthread) и последующем ожидании смерти (pthread_join(GLUTthread, NULL)) на pthread_join нет-нет, да происходит зависание. Я так понял, что pthread_cancel просто не срабатывает, если вызывается, когда поток находится в состоянии паузы.

Паузу реализовывал тремя способами. Сначала было просто

usleep(10000);
понятно, что этот вариант — самый корявый.

Потом сделал так:

struct timeval tv;
... 
tv.tv_sec = 0;
tv.tv_usec = 10000;
select(0, NULL, NULL, NULL, &tv);
а потом — вообще вот так:
pthread_cond_t fakeCond = PTHREAD_COND_INITIALIZER;
struct timespec timeToWait;
struct timeval now;
while(1){
	pthread_mutex_lock(&winini_mutex);
... // критическая секция
	gettimeofday(&now,NULL);
	timeToWait.tv_sec = now.tv_sec;
	timeToWait.tv_nsec = now.tv_usec * 1000UL + 10000000UL;
	pthread_cond_timedwait(&fakeCond, &winini_mutex, &timeToWait);
	pthread_mutex_unlock(&winini_mutex);
... // некритическая секция
}
И та же история: ни один из способов не спасает от зависания!

Как же с этим бороться?

Сейчас у меня пауза при помощи pthread_cond_timedwait, а еще я запихал в критическую секцию проверку

if(!initialized){
	DBG("!initialized");
	pthread_exit(NULL);
}
Функция убиения вот какая:
void clear_GL_context(){
	FNAME();
	if(!initialized) return;
	DBG("lock");
	pthread_mutex_lock(&winini_mutex);
	initialized = 0;
	DBG("locked");
	 // kill main GLUT thread
	pthread_cancel(GLUTthread);
	pthread_mutex_unlock(&winini_mutex);
	forEachWindow(killwindow_v);
	DBG("join");
	pthread_join(GLUTthread, NULL);
	pthread_mutex_unlock(&winini_mutex);
	DBG("main GL thread cancelled");
}
Пришлось разблокировать мьютекс после убиения потока, потому что, несмотря на то, что в мане пишут, что pthread_cond_timedwait при получении сигнала pthread_cancel разблокирует мьютекс, на деле это не так!

Вот как правильно поступить?

☆☆☆☆☆

По-моему, вы не совсем правильно понимаете, что такое Cancellation Point. Также вам, скорее всего, необходимо воспользоваться pthread_cleanup_push() / pthread_cleanup_pop() для разблокировки мутекса тем потоком, что этот мутекс захватил.

Sorcerer ★★★★★
()
Последнее исправление: Sorcerer (всего исправлений: 1)

Вот как правильно поступить?

явно не так:

чтобы другие потоки (которые могут изменять содержимое отображаемого изображения) имели возможность вклиниться между блокировкой/разблокировкой мьютекса

и не так:

при убивании этого основного потока

почему бы не сделать в основном потоке так:

GetGLUTMeessages();
LockMessageQueue();
AddMessage();
UnlockMessageQueue();

?

anonymous
()
Ответ на: Вот как правильно поступить? от anonymous

Не вижу смысла разделять на 2 потока, когда оно в одном нормально отрабатывается:

void *Redraw(_U_ void *arg){
	pthread_cond_t fakeCond = PTHREAD_COND_INITIALIZER;
	struct timespec timeToWait;
	struct timeval now;
	while(1){
		pthread_mutex_lock(&winini_mutex);
		if(!initialized){
			DBG("!initialized");
			pthread_exit(NULL);
		}
		if(wannacreate){ // someone asks to create window
			DBG("call for window creating, id: %d", wininiptr->ID);
			createWindow(wininiptr);
			DBG("done!");
			wininiptr = NULL;
			wannacreate = 0;
		}
		forEachWindow(redisplay);
		gettimeofday(&now,NULL);
		timeToWait.tv_sec = now.tv_sec;
		timeToWait.tv_nsec = now.tv_usec * 1000UL + 10000000UL;
		pthread_cond_timedwait(&fakeCond, &winini_mutex, &timeToWait);
		pthread_mutex_unlock(&winini_mutex);
		if(totWindows) glutMainLoopEvent(); // process actions if there are windows
	}
	return NULL;
}

явно не так
и не так

А как? Хочу иметь возможность убить этот поток, если он не нужен.

Eddy_Em ☆☆☆☆☆
() автор топика

ну и дела..

всё что угодно же может происходить в момент убивания потока..

..вероятность того что во время процесса-убивания-потока ни когда (ни когда ни когда ни когда) не будут происходить важные критические процессы. я бы не стал бы на это полагаться.

а почему нельзя чтобы поток сам умирал бы? зачем его насильно убивать? пошли ему сообщение о том чтобы он корерктно завершил себя (ну и дальше join и подчисть за ним)

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

В принципе, да, можно и так попробовать.

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

if(totWindows) glutMainLoopEvent(); // process actions if there are windows

заиспользуй например glutTimerFunc — для того чтобы выходить из этого блокирующего вызова раз в какой-то момент (раз в 100 милисекунд, например)...

... и проверять нужно ли уже убивать себя или ещё пока что нет :-)

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

Все гениальное просто!

Я действительно слишком перемудрил. Ведь все равно у меня в потоке была проверка флага. Ошибкой было то, что я забыл перед pthread_exit разблокировать мьютекс — вот он и висел заблокированным, если поток умирал по флагу, а не по сигналу pthread_cancel. Сейчас сделал так:

void *Redraw(_U_ void *arg){
	while(1){
		pthread_mutex_lock(&winini_mutex);
		if(!initialized){
			DBG("!initialized");
			pthread_mutex_unlock(&winini_mutex);
			pthread_exit(NULL);
		}
		if(wannacreate){ // someone asks to create window
			DBG("call for window creating, id: %d", wininiptr->ID);
			createWindow(wininiptr);
			DBG("done!");
			wininiptr = NULL;
			wannacreate = 0;
		}
		forEachWindow(redisplay);
		pthread_mutex_unlock(&winini_mutex);
		if(totWindows) glutMainLoopEvent(); // process actions if there are windows
		usleep(10000);
	}
	return NULL;
}

void clear_GL_context(){
	FNAME();
	if(!initialized) return;
	pthread_mutex_lock(&winini_mutex);
	initialized = 0;
	pthread_mutex_unlock(&winini_mutex);
	forEachWindow(killwindow_v);
	DBG("join");
	pthread_join(GLUTthread, NULL); // wait while main thread exits
	DBG("main GL thread cancelled");
}
Т.е. вообще не вызываю pthread_cancel, а жду, пока он сам отомрет. Все работает.

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

дык я же написал как. хочешь в одном потоке, хочешь — в двух.

блокируй не выполнение, а доступ к данным.

anonymous
()
Ответ на: Все гениальное просто! от Eddy_Em

Ведь все равно у меня в потоке была проверка флага. Ошибкой было то, что я забыл перед pthread_exit разблокировать мьютекс — вот он и висел заблокированным

забираешь у потока признак инициализации initialized :-) ?

..норм, да :) .. «нарекаю тебя более не инециализированным!» :-)

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

вообще не вызываю pthread_cancel, а жду, пока он сам отомрет

это на мой взгляд отлично!

...хотя вот в первом сообщении товарищ И таки как правильно потоки убивать? (комментарий) — предложил pthread_cleanup_push() / pthread_cleanup_pop() ..

..и поидее это было бы правильно.. но лично я склонен полагать что где-нибудь в каком-нибудь одном месте этот pthread_cleanup_push() будет забыт быть вызванным какой-нибудь внутренней служебной функцией (от поставщика третьего лица).. и попробуй найди где это было забыто :-) .. думаю что утечка ресурсов или зависание — будет обеспечено в редком случае.

так что , лично я — очень НЕ люблю хоть где-то использовать pthread_cancel :-)

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

забираешь у потока признак инициализации initialized :-) ?

Да, этот флаг просто говорит о том, что основной поток работает. Для учета инициализации GLUT и иксовых потоков стоит другой флаг — статическая локальная переменная.

pthread_cleanup_push() / pthread_cleanup_pop()

Излишнее усложнение на мой взгляд.

Eddy_Em ☆☆☆☆☆
() автор топика
Ответ на: комментарий от user_id_68054

Просто использовать pthread_cancel() нужно не везде, а там, где это удобно. Если потоки выполняют короткие задачи, то нет смысла городить огород с их отменой, - проще дождаться завершения очередной задачи и сигнализировать о необходимости завершения, например, переменной. А если поток выполняет длительную задачу, с функциями-подзадачами, в том числе вложенными, то оперативное прерывание такого потока гораздо удобнее может быть реализовано с помощью pthread_cancel(). Чтобы избежать утечек в сторонних функциях, нужно пользоваться pthread_setcancelstate(), отключив возможность отмены, когда не надо. Ну и понавставлять pthread_testcancel() где нужно.

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

нужно пользоваться pthread_setcancelstate(), отключив возможность отмены, когда не надо. Ну и понавставлять pthread_testcancel() где нужно

это интресно, однако!

user_id_68054 ★★★★★
()

1) Нить должна завершиться сама. Убивать её извне - признак дурного тона (как ты гарантируешь корректное завершение и освобождение ресурсов случае принулительного убийства?).
2) Если ты используешь sleep для синхронизации, ты что-то делаешь не так. (Никогда не используйте слип для синхронизации)

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

1)

так и сделал

2)

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

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

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

Вы только посмотрите на этого гонщика.

anonymous
()
Ответ на: Все гениальное просто! от Eddy_Em

Т.е. вообще не вызываю pthread_cancel, а жду, пока он сам отомрет. Все работает.

Плохо, что ты не прочёл JLS на тему start()/stop() в классе Thread и Что я должен использовать вместо Thread.stop?

Зато сам дошёл. ;)

iZEN ★★★★★
()
Последнее исправление: iZEN (всего исправлений: 3)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.