Linux程序设计学习笔记----异步信号与线程属性控制

来源:互联网 发布:c语言字符运算 编辑:程序博客网 时间:2024/05/16 05:16

转载请注明出处:http://blog.csdn.net/suool/article/details/38584263

多线程异步信号基本概念

线程并没有自己独立的异步信号管理机制,有你次需要依赖所在的进程,每个线程仅仅能管理自己私有的信号屏蔽集合.因此线程在信号操作时具有以下特点:

1)每个线程都可以向其他线程发送信号.

2)每个线程可以设置自己的信号屏蔽集合,而不影响同进程下其他线程,但是初值需要从创建线程中继承,如果在原线程有任何未决信号并不被新线程继承.

3)同进程下所有线程共享对某信号的处理方法,即受到某个信号后,执行相同的信号处理函数.

4)向某个进程发送终止信号,则该进程下所有线程都将终止

在Linux的多线程中使用信号机制,与在进程中使用信号机制有着根本的区别,可以说是完全不同。在进程环境中,对信号的处理是,先注册信号处理函数,当信号异步发生时,调用处理函数来处理信号。它完全是异步的(我们完全不知到信号会在进程的那个执行点到来!)。然而信号处理函数的实现,有着许多的限制;比如有一些函数不能在信号处理函数中调用;再比如一些函数read、recv等调用时会被异步的信号给中断(interrupt),因此我们必须对在这些函数在调用时因为信号而中断的情况进行处理(判断函数返回时 enno 是否等于 EINTR)。

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

基本管理操作

sigwait函数

   // sigwait - wait for a signal    #include <signal.h>    int sigwait(const sigset_t *set, int *sig);/*    Description    The sigwait() function suspends execution of the calling thread until the delivery of one     of the signals specified in the signal set set. The function accepts the signal (removes     it from the pending list of signals), and returns the signal number in sig.    The operation of sigwait() is the same as sigwaitinfo(2), except that:    * sigwait() only returns the signal number, rather than a siginfo_t structure describing       the signal.    * The return values of the two functions are different.    Return Value    On success, sigwait() returns 0. On error, it returns a positive error number.*/

从上面的man sigwait的描述中,我们知道:sigwait是同步的等待信号的到来,而不是像进程中那样是异步的等待信号的到来。sigwait函数使用一个信号集作为他的参数,并且在集合中的任一个信号发生时返回该信号值,解除阻塞,然后可以针对该信号进行一些相应的处理。
记住:
 在多线程代码中,总是使用sigwait或者sigwaitinfo或者sigtimedwait等函数来处理信号。而不是signal或者sigaction等函数。因为在一个线程中调用signal或者sigaction等函数会改变所以线程中的信号处理函数。而不是仅仅改变调用signal/sigaction的那个线程的信号处理函数。

pthread_sigmask函数

每个线程均有自己的信号屏蔽集(信号掩码),可以使用pthread_sigmask函数来屏蔽某个线程对某些信号的
响应处理,仅留下需要处理该信号的线程来处理指定的信号。实现方式是:利用线程信号屏蔽集的继承关系
(在主进程中对sigmask进行设置后,主进程创建出来的线程将继承主进程的掩码
// pthread_sigmask - examine and change mask of blocked signals    #include <signal.h>    int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);/* Compile and link with -pthread.    DESCRIPTION    The pthread_sigmask() function is just like sigprocmask(2), with the difference that     its use in multithreaded programs is explicitly specified by POSIX.1-2001.    Other differences are noted in this page.    For a description of the arguments and operation of this function, see sigprocmask(2).    RETURN VALUE    On success, pthread_sigmask() returns 0; on error, it returns an error number.    NOTES    A new thread inherits a copy of its creator's signal mask.    (from man sigprocmask: )    The behavior of the call is dependent on the value of how, as follows.    SIG_BLOCK    The set of blocked signals is the union of the current set and the set argument.    SIG_UNBLOCK    The signals in set are removed from the current set of blocked signals.     It is permissible to attempt to unblock a signal which is not blocked.    SIG_SETMASK    The set of blocked signals is set to the argument set.    If oldset is non-NULL, the previous value of the signal mask is stored in oldset.    If set is NULL, then the signal mask is unchanged (i.e., how is ignored), but the current     value of the signal mask is nevertheless returned in oldset (if it is not NULL).*/ 

pthread_kill函数

在多线程程序中,一个线程可以使用pthread_kill对同一个进程中指定的线程(包括自己)发送信号。注意在多线程中  
  一般不使用kill函数发送信号,因为kill是对进程发送信号,结果是:正在运行的线程会处理该信号,如果该线程没有
 注册信号处理函数,那么会导致整个进程退出。
#include <signal.h>int pthread_kill(pthread_t thread, int sig);/*    Compile and link with -pthread.    DESCRIPTION    The pthread_kill() function sends the signal sig to thread, another thread in     the same process as the caller. The signal is asynchronously directed to thread.    If sig is 0, then no signal is sent, but error checking is still performed;     this can be used to check for the existence of a thread ID.    RETURN VALUE    On success, pthread_kill() returns 0; on error, it returns an error number, and no signal is sent.    ERRORS    ESRCH No thread with the ID thread could be found.    EINVAL An invalid signal was specified.*/
记住:调用sigwait同步等待的信号必须在调用线程中被屏蔽,并且通常应该在所有的线程中被屏蔽(这样可以保证信号绝不会被送到除了调用sigwait的任何其它线程),这是通过利用信号掩码的继承关系来达到的

线程信号使用示例

以下是一个线程信号使用示例,该示例中创建了两个线程

线程1安装了USR1信号,阻塞除USR2信号外的所有信号,进入死循环,等待信号

线程2安装信号USR2,不阻塞任何信号,然后进入循环,等到信号

主线程首先向线程1发送USR1 USR2信号,然后向线程2发送USR1和USr2 信号,最后发送SIGKILL信号.

由线程处理信号策略可知,在两个线程中安装的信号可以共用,因此:

线程2 可以接受USR1 和USR2信号后执行处理函数

线程1阻塞所有USR2外的信号,故接受到USR1将阻塞,收到USR2将执行处理函数

最后收到SIGKILL,没有安装此信号,执行结束进程.

运行结果如下


#include<stdio.h>#include<pthread.h>#include<stdlib.h>#include<unistd.h>#include<signal.h>void *sigone_program(void *arg);void *sigtwo_program(void *arg);     // 定义信号处理函数void report(int);pthread_t thread_one,thread_two;      // 定义线程int main(int argc,char *argv[]){        int i;        void *status;if(pthread_create(&thread_one,NULL,sigone_program,NULL)!=0)   //创建线程        {                fprintf(stderr,"pthread_create failure\n");                exit(EXIT_FAILURE);        }        if(pthread_create(&thread_two,NULL,sigtwo_program,NULL)!=0)      // 创建线程        {                fprintf(stderr,"pthread_create failure\n");                exit(EXIT_FAILURE);        }        sleep(1);        printf("this is parent ,send SIGUSR1,SIGUSR2 to thread %u\n",thread_one);  // 提示信息        if(pthread_kill(thread_one,SIGUSR1)!=0)   // 向进程one发送usr1信号        {                perror("pthread_kill");                exit(EXIT_FAILURE);        }        if(pthread_kill(thread_one,SIGUSR2)!=0)  // ......usr2信号        {                perror("pthread_kill");                exit(EXIT_FAILURE);        }        printf("this is parent ,send SIGUSR1,SIGUSR2  to thread %u\n",thread_two);  // 提示信息        if(pthread_kill(thread_two,SIGUSR1)!=0)           // 向进程two发送usr1信号        {                perror("pthread_kill");                exit(EXIT_FAILURE);        }        if(pthread_kill(thread_two,SIGUSR2)!=0)         // ......usr2        {                perror("pthread_kill");                exit(EXIT_FAILURE);        }sleep(1);if(pthread_kill(thread_one,SIGKILL)!=0)              // 向进程one发送SIGKILL信号        {                perror("pthread_kill");                exit(EXIT_FAILURE);        }        printf("the end\n");        pthread_join(thread_two,NULL);        pthread_join(thread_one,NULL);          // 进程等待        return 0;}void *sigone_program(void *arg)                  // 进程one执行函数{        int i;        __sigset_t set;signal(SIGUSR1,report);                 // 按装信号usr1        sigfillset(&set);        sigdelset(&set,SIGUSR2);               // 安装信号集pthread_sigmask(SIG_SETMASK,&set,NULL);   // 阻塞usr2之外所有信号        for(i=0;i<5;i++)        {                printf("this is set mask %u thread\n",pthread_self());        pause();}}void report(int sig){        printf("\nin signal ,the sig=%d\t,the thread id=%u\n",sig,pthread_self());}void *sigtwo_program(void *arg){        int i;        signal(SIGUSR2,report);        for(i=0;i<5;i++)        {                printf("this is no set mask %u thread\n",pthread_self());        pause();}}

线程属性

线程具有属性,用pthread_attr_t表示,在对该结构进行处理之前必须进行初始化,在使用后需要对其去除初始化。我们用pthread_attr_init函数对其初始化,用pthread_attr_destroy对其去除初始化。

名称:pthread_attr_init/pthread_attr_destroy
功能:对线程属性初始化/去除初始化
#include <pthread.h>int pthread_attr_init(pthread_attr_t *attr);              int pthread_attr_destroy(pthread_attr_t *attr);
参数:Attr 线程属性变量
返回值:若成功返回0,若失败返回-1。调用pthread_attr_init之后,pthread_t结构所包含的内容就是操作系统实现支持的线程所有属性的默认值。
如果要去除对pthread_attr_t结构的初始化,可以调用pthread_attr_destroy函数。如果pthread_attr_init实现时为属性对象分配了动态内存空间,pthread_attr_destroy还会用无效的值初始化属性对象,因此如果经pthread_attr_destroy去除初始化之后的pthread_attr_t结构被pthread_create函数调用,将会导致其返回错误。
线程属性结构如下:
typedef struct{int detachstate; 线程的分离状态int schedpolicy; 线程调度策略struct sched_param schedparam; 线程的调度参数int inheritsched; 线程的继承性int scope; 线程的作用域size_t guardsize; 线程栈末尾的警戒缓冲区大小  int stackaddr_set;void * stackaddr; 线程栈的位置size_t stacksize; 线程栈的大小}pthread_attr_t;
每个个属性都对应一些函数对其查看或修改。下面我们分别介绍。

线程的分离状态

线程的分离状态决定一个线程以什么样的方式来终止自己在默认情况下线程是非分离状态的,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。

 而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。程序员应该根据自己的需要,选择适当的分离状态。所以如果我们在创建线程时就知道不需要了解线程的终止状态,则可以pthread_attr_t结构中的detachstate线程属性,让线程以分离状态启动。
2.名称:pthread_attr_getdetachstate/pthread_attr_setdetachstate
功能:获取/修改线程的分离状态属性
#include <pthread.h>int pthread_attr_getdetachstate(const pthread_attr_t * attr,int *detachstate);int pthread_attr_setdetachstate(pthread_attr_t *attr,int detachstate);
参数:Attr 线程属性变量, Detachstate 线程的分离状态属性
返回值:若成功返回0,若失败返回-1。
 可以使用pthread_attr_setdetachstate函数把线程属性detachstate设置为下面的两个合法值之一:设置为PTHREAD_CREATE_DETACHED,以分离状态启动线程;或者设置为PTHREAD_CREATE_JOINABLE,正常启动线程。可以使用pthread_attr_getdetachstate函数获取当前的datachstate线程属性。
(1) 以分离状态创建线程
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <pthread.h>void *child_thread(void *arg){    printf(“child thread run!\n”);}int main(int argc,char *argv[ ]){    pthread_t tid;    pthread_attr_t attr;    pthread_attr_init(&attr);    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);    pthread_create(&tid,&attr,fn,arg);    pthread_attr_destroy(&attr);    sleep(1);}

线程的继承性

函数pthread_attr_setinheritsched和pthread_attr_getinheritsched分别用来设置和得到线程的继承性,这两个函数的定义如下:
3.名称:pthread_attr_getinheritsched /pthread_attr_setinheritsched
功能:获得/设置线程的继承性
#include <pthread.h>int pthread_attr_getinheritsched(const pthread_attr_t *attr,int *inheritsched);int pthread_attr_setinheritsched(pthread_attr_t *attr,int inheritsched);
参数:attr 线程属性变量, inheritsched 线程的继承性
返回值:若成功返回0,若失败返回-1。
 这两个函数具有两个参数,第1个是指向属性对象的指针,第2个是继承性或指向继承性的指针。继承性决定调度的参数是从创建的进程中继承还是使用在schedpolicy和schedparam属性中显式设置的调度信息。Pthreads不为inheritsched指定默认值,因此如果你关心线程的调度策略和参数,必须先设置该属性。
    继承性的可能值是PTHREAD_INHERIT_SCHED(表示新现成将继承创建线程的调度策略和参数)和PTHREAD_EXPLICIT_SCHED(表示使用在schedpolicy和schedparam属性中显式设置的调度策略和参数)。如果你需要显式的设置一个线程的调度策略或参数,那么你必须在设置之前将inheritsched属性设置为PTHREAD_EXPLICIT_SCHED.

线程的调度策略

 函数pthread_attr_setschedpolicy和pthread_attr_getschedpolicy分别用来设置和得到线程的调度策略。

名称:pthread_attr_getschedpolicy \pthread_attr_setschedpolicy
功能:获得/设置线程的调度策

#include <pthread.h>int pthread_attr_getschedpolicy(const pthread_attr_t *attr,int *policy);int pthread_attr_setschedpolicy(pthread_attr_t *attr,int policy);
参数:attr 线程属性变量, policy 调度策略
返回值:若成功返回0,若失败返回-1。
     这两个函数具有两个参数,第1个参数是指向属性对象的指针,第2个参数是调度策略或指向调度策略的指针。调度策略可能的值是先进先出(SCHED_FIFO)、轮转法(SCHED_RR),或其它(SCHED_OTHER)。
   (1) SCHED_FIFO策略允许一个线程运行直到有更高优先级的线程准备好,或者直到它自愿阻塞自己。在SCHED_FIFO调度策略下,当有一个线程准备好时,除非有平等或更高优先级的线程已经在运行,否则它会很快开始执行。
   (2) SCHED_RR(轮循)策略是基本相同的,不同之处在于:如果有一个SCHED_RR策略的线程执行了超过一个固定的时期(时间片间隔)没有阻塞,而另外的SCHED_RR或SCHBD_FIPO策略的相同优先级的线程准备好时,运行的线程将被抢占以便准备好的线程可以执行。
    当有SCHED_FIFO或SCHED_RR策赂的线程在一个条件变量上等持或等持加锁同一个互斥量时,它们将以优先级顺序被唤醒。即,如果一个低优先级的  SCHED_FIFO线程和一个高优先织的SCHED_FIFO线程都在等待锁相同的互斥且,则当互斥量被解锁时,高优先级线程将总是被首先解除阻塞。

线程的调度参数

函数pthread_attr_getschedparam 和pthread_attr_setschedparam分别用来设置和得到线程的调度参数。
名称:pthread_attr_getschedparam \pthread_attr_setschedparam
功能:获得/设置线程的调度参数
头文件:#include <pthread.h>
函数原形:int pthread_attr_getschedparam(const pthread_attr_t *attr,struct sched_param *param);
int pthread_attr_setschedparam(pthread_attr_t *attr,const struct sched_param *param);

参数:attr 线程属性变量, param sched_param结构
返回值:若成功返回0,若失败返回-1。
    这两个函数具有两个参数,第1个参数是指向属性对象的指针,第2个参数是sched_param结构或指向该结构的指针。结构sched_param在文件/usr/include /bits/sched.h中定义如下:

struct sched_param{    int sched_priority;};
 结构sched_param的子成员sched_priority控制一个优先权值,大的优先权值对应高的优先权。系统支持的最大和最小优先权值可以用sched_get_priority_max函数和sched_get_priority_min函数分别得到。
注意:如果不是编写实时程序,不建议修改线程的优先级。因为,调度策略是一件非常复杂的事情,如果不正确使用会导致程序错误,从而导致死锁等问题。如:在多线程应用程序中为线程设置不同的优先级别,有可能因为共享资源而导致优先级倒置。
名称:sched_get_priority_max \sched_get_priority_min
功能:获得系统支持的线程优先权的最大和最小值
头文件:#include <pthread.h>
函数原形:int sched_get_priority_max(int policy); int sched_get_priority_min(int policy);

参数:policy 系统支持的线程优先权的最大和最小值
返回值:若成功返回0,若失败返回-1。
    下面是上面几个函数的程序例子:
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <pthread.h>#include <sched.h>void *child_thread(void *arg){    int policy;    int max_priority,min_priority;    struct sched_param param;    pthread_attr_t attr;    pthread_attr_init(&attr); /*初始化线程属性变量*/    pthread_attr_setinheritsched(&attr,PTHREAD_EXPLICIT_SCHED); /*设置线程继承性*/    pthread_attr_getinheritsched(&attr,&policy); /*获得线程的继承性*/    if(policy==PTHREAD_EXPLICIT_SCHED)    printf(“Inheritsched:PTHREAD_EXPLICIT_SCHED\n”);    if(policy==PTHREAD_INHERIT_SCHED)    printf(“Inheritsched:PTHREAD_INHERIT_SCHED\n”);    pthread_attr_setschedpolicy(&attr,SCHED_RR);/*设置线程调度策略*/    pthread_attr_getschedpolicy(&attr,&policy);/*取得线程的调度策略*/    if(policy==SCHED_FIFO)    printf(“Schedpolicy:SCHED_FIFO\n”);    if(policy==SCHED_RR)    printf(“Schedpolicy:SCHED_RR\n”);    if(policy==SCHED_OTHER)    printf(“Schedpolicy:SCHED_OTHER\n”);    sched_get_priority_max(max_priority);/*获得系统支持的线程优先权的最大值*/    sched_get_priority_min(min_priority);/* 获得系统支持的线程优先权的最小值*/    printf(“Max priority:%u\n”,max_priority);    printf(“Min priority:%u\n”,min_priority);    param.sched_priority=max_priority;    pthread_attr_setschedparam(&attr,¶m);/*设置线程的调度参数*/    printf(“sched_priority:%u\n”,param.sched_priority);/*获得线程的调度参数*/    pthread_attr_destroy(&attr);}int main(int argc,char *argv[ ]){    pthread_t child_thread_id;    pthread_create(&child_thread_id,NULL,child_thread,NULL);    pthread_join(child_thread_id,NULL);}

线程的作用域

 函数pthread_attr_setscope和pthread_attr_getscope分别用来设置和得到线程的作用域,这两个函数的定义如下:
名称:pthread_attr_setscope\pthread_attr_getscope
功能:获得/设置线程的作用域
头文件:#include <pthread.h>
函数原形:int pthread_attr_setscope(pthread_attr_t *attr,int scope);
int pthread_attr_getscope(const pthread_attr_t *attr,int *scope);

参数:attr 线程属性变量, scope 线程的作用域
返回值:若成功返回0,若失败返回-1。
 这两个函数具有两个参数,第1个是指向属性对象的指针,第2个是作用域或指向作用域的指针,作用域控制线程是否在进程内或在系统级上竞争资源,可能的值是PTHREAD_SCOPE_PROCESS(进程内竞争资源),PTHREAD_SCOPE_SYSTEM.(系统级上竞争资源)。

线程堆栈的大小

 函数pthread_attr_setstacksize和pthread_attr_getstacksize分别用来设置和得到线程堆栈的大小,这两个函数的定义如下所示:
名称:pthread_attr_getdetstacksize\pthread_attr_setstacksize
功能:获得/修改线程栈的大小
头文件:#include <pthread.h>
函数原形:int pthread_attr_getstacksize(const pthread_attr_t *restrict attr,size_t *restrict stacksize);

int pthread_attr_setstacksize(pthread_attr_t *attr ,size_t *stacksize);
参数:attr 线程属性变量,stacksize 堆栈大小  
返回值:若成功返回0,若失败返回-1。
 这两个参数具有两个参数,第1个是指向属性对象的指针,第2个是堆栈大小或指向堆栈大小的指针.如果希望改变栈的默认大小,但又不想自己处理线程栈的分配问题,这时使用pthread_attr_setstacksize函数就非常有用

线程堆栈的地址

函数pthread_attr_setstackaddr和pthread_attr_getstackaddr分别用来设置和得到线程堆栈的位置,这两个函数的定义如下:
名称:pthread_attr_setstackaddr\pthread_attr_getstackaddr
功能:获得/修改线程栈的位置
头文件:#include <pthread.h>
函数原形:int pthread_attr_getstackaddr(const pthread_attr_t *attr,void **stackaddf);

int pthread_attr_setstackaddr(pthread_attr_t *attr,void *stackaddr);
参数:attr 线程属性变量,stackaddr 堆栈地址
返回值:若成功返回0,若失败返回-1。
这两个函数具有两个参数,第1个是指向属性对象的指针,第2个是堆栈地址或指向堆栈地址的指针。

线程栈末尾的警戒缓冲区大小

函数pthread_attr_getguardsize和pthread_attr_setguardsize分别用来设置和得到线程栈末尾的警戒缓冲区大小,这两个函数的定义如下:
名称:pthread_attr_getguardsize/pthread_attr_setguardsize
功能:获得/修改线程栈末尾的警戒缓冲区大小
头文件:#include <pthread.h>
函数原形:int pthread_attr_getguardsize(const pthread_attr_t *restrict attr,size_t *restrict guardsize);
int pthread_attr_setguardsize(pthread_attr_t *attr ,size_t *guardsize);

参数:
返回值:若成功返回0,若失败返回-1。
 线程属性guardsize控制着线程栈末尾之后以避免栈溢出的扩展内存大小。这个属性默认设置为PAGESIZE个字节。可以把guardsize线程属性设为0,从而不允许属性的这种特征行为发生:在这种情况下不会提供警戒缓存区。同样地,如果对线程属性stackaddr作了修改,系统就会假设我们会自己管理栈,并使警戒栈缓冲区机制无效,等同于把guardsize线程属性设为0。

0 0
原创粉丝点击