线程间同步机制----信号

线程拥有与信号相关的私有数据——线程信号掩码,这就决定了线程在信号操作时具有以下特性:

(1)每个线程可以先别的线程发送信号。pthread_kill()函数用来完成这一操作。

(2)每个线程可以设置自己的信号阻塞集合。pthread_sigmask()函数用来完成这一操作,其类似于进程的sigprocmask()函数。

(3)每个线程可以设置针对某个信号的处理方式,但同一进程中对某信号的处理方式只能有一个有效,即最后一次处置的处理方式。

(4)如果别的进程向当前进程中发送一个信号,那么由哪个线程处理则是未知的。


一、线程信号管理


1、pthread_kill发送信号


<span style="font-family:Microsoft YaHei;font-size:18px;">#include  <signal.h>
int  pthread_kill(pthread_t  thread, int signo);</span><pre name="code" class="cpp"><span style="font-family:Microsoft YaHei;font-size:18px;">//返回值:若成功,返回0;否则,返回错误编号</span>

 
 


pthread_kill()函数用于请求将信号传送给线程。调用进程中,信号将被异步定向到线程。


第一个参数thread是要向其传送信号的线程。

第二个参数signo是要传送给线程的信号。如果signo为0,就会进行错误检查而不发送信号。


2、pthread_sigmask调用线程的信号掩码


<span style="font-family:Microsoft YaHei;font-size:18px;">#include  <signal.h>
int  pthread_sigmask(int  how, const  sigset_t  *restrict set, sigset_t *restrict oset);
//返回值:若成功,返回0;否则,返回错误编号</span>

pthread_sigmask()函数用来检查(或更改)调用线程的信号掩码。


第一个参数how定义如何更改调用线程的信号掩码。合法值包括:

线程间同步机制----信号_第1张图片

如果set是空指针,则参数how的值没有意义,且不会更改线程的阻塞信号集,因此该调用可用于查询当前受阻塞的信号。


需要注意的是,要阻塞SIGKILL或SIGSTOP信号时不可能。这是由系统强制执行的,而不会导致错误。


3、sigwait等待信号


<span style="font-family:Microsoft YaHei;font-size:18px;">#include  <signal.h>
int  signal(const sigset_t, sigset_t *restrict set, int *restrict signop);
//返回值:若成功,返回0;否则,返回错误编号。</span>

第一个参数set指定了信号等待的信号集。

第二个参数是函数返回时,signop指向的整数将包含发送信号的数量。


sigwait函数一直阻塞直到*sigmask指定的任何一个信号被挂起为止,然后从挂起信号集中删除那个信号,并解除对它的阻塞。当sigwait返回时,从挂起信号集中删除的信号的个数被存储在signo指定的那个位置中。在返回之前,sigwait将恢复线程的信号屏蔽字。如果信号在sigwait被调用的时候没有被阻塞,那么在线程完成对sigwait的调用之前会出现一个时间窗,在这个时间窗中信号就可以被发送给线程。这里的sigwait是阻塞本进程,去等待指定的信号。等到了就会解除阻塞。



在多线程中处理信号的原则却完全不同,它的基本原则是:将对信号的异步处理,转换成同步处理,也就是说用一个线程专门的来“同步等待”信号的到来,而其它的线程可以完全不被该信号中断/打断(interrupt)。这样就在相当程度上简化了在多线程环境中对信号的处理。而且可以保证其它的线程不受信号的影响。这样我们对信号就可以完全预测,因为它不再是异步的,而是同步的我们完全知道信号会在哪个线程中的哪个执行点到来而被处理!。而同步的编程模式总是比异步的编程模式简单。其实多线程相比于多进程的其中一个优点就是:多线程可以将进程中异步的东西转换成同步的来处理。使用sigwait的好处在于它可以简化信号处理,允许把异步产生的信号用同步的方式处理。为了防止信号中断线程,可以把信号加到每个线程的信号屏蔽字中,然后可以安排专用线程处理信号。


信号是向进程发送的软件通知,通知进程有事件发生。引发信号的事件发生时,信号就被生成了。进程根据信号采取行动时,信号就被传递了。信号的寿命就是信号的生成和传递之间的时间间隔。已经生成但还未被传递的信号被称为挂起的信号。在信号生成和信号传递之间可能会有相当长的时间。

挂起就是未决pending(未处理的)

信号类似于处理事务
1。有事情发生了(信号产生)
2。该事情通知了有关部门,他们已登记在一个未处理事件的本子上(信号传递给目标进程,目标进程登记该信号到本进程的task_struct结构)
3。有关部门定期检查未处理事件的本子,发现有事情没处理时,就注销该事件(目标进程会检测是否有信号等待处理,如果有未决信号等待处理且该信号没有被进程阻塞,则在运行相应的信号处理函数前,进程会把信号在未决信号链中占有的结构卸掉)
4。最后有关部门去实施事件的处理。(进程注销信号后,立即执行相应的信号处理函数,执行完毕后,信号的本次发送对进程的影响彻底结束。)


二、线程信号应用实例


<span style="font-family:Microsoft YaHei;font-size:18px;">#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

int quitflag;
sigset_t mask;

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t waitloc = PTHREAD_COND_INITIALIZER;

void *thr_fn(void *arg)
{
	int err, signo;
	for ( ; ;)
	{
		err = sigwait(&mask, &signo);
		if (err != 0)
		{
			perror("sigwait failed");
			exit(EXIT_FAILURE);
		}
		switch (signo)
		{
			case SIGINT:
				printf("\ninterrupt\n");
				break;

			case SIGQUIT:
				pthread_mutex_lock(&lock);
				quitflag = 1;
				pthread_mutex_unlock(&lock);
				pthread_cond_signal(&waitloc);
				return(0);

			default:
				printf("unexpected signal %d\n", signo);
				exit(1);
		}
	}
}

int main(void)
{
	int err;
	sigset_t oldmask;
	pthread_t tid;
	int res;

	sigemptyset(&mask);
	sigaddset(&mask, SIGINT);
	sigaddset(&mask, SIGQUIT);
	if ((err = pthread_sigmask(SIG_BLOCK, &mask, &oldmask)) != 0)
	{
		perror("SIG_BLOCK error");
		exit(EXIT_FAILURE);
	}
	err = pthread_create(&tid, NULL, thr_fn, 0);
	if (err != 0)
	{
		perror("can't create thread");
		exit(EXIT_FAILURE);
	}
	pthread_mutex_lock(&lock);
	while(quitflag == 0)
		pthread_cond_wait(&waitloc, &lock);
	pthread_mutex_unlock(&lock);

	quitflag = 0;

	if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
	{
		perror("SIG_SETMASK error");
		exit(EXIT_FAILURE);
	}
	exit(0);
}
</span>

你可能感兴趣的:(线程间同步机制----信号)