线程总结
理解并行和并发
- 并行:指两个或两个以上事件或活动在同一时刻发生,在多道程序环境下,并行使多个程序同一时刻可在不同CPU上同时执行。两个人喂两个小孩子吃饭
- 并发:在同一个cpu上同时(不是真正的同时,而是看来是同时,因为CPU要在多个程序之间切换)运行多个程序。就像一个人(CPU)喂两个小孩(程序)吃饭,表面上是两个小孩在吃饭,实际是一个人在喂。
理解进程与线程(进程可以看成是主线程,占用一个线程号)
- Linux下的多线程遵循POSIX线程接口,称为pthread
- 进程和线程都是一个时间段的描述,是cpu工作时间段的描述
- 名词是对一个客观事物的指代,形容词是对客观事物的描述。
- 线程:操作系统分配cpu时间的基本单位
- 在用户看来,多个线程是同时执行的,但是从操作系统的调度来看,各个线程是交替执行的。系统不停地在各个线程之间切换,每个线程只有在系统分配给他的时间片内才能取得cpu的控制权,执行线程中的代码。(单cpu单核)
- 进程:资源(CPU、内存等)分配的基本单位。
- 它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。
- Linux系统函数
fork()
可以在父进程中创建一个子进程,这样的话,在一个进程接到来自客户端新的请求时就可以复制出一个子进程让其来处理,父进程只需负责监控请求的到来,然后创建子进程让其去处理,这样就能做到并发处理。
- 线程是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。
区别:
- 进程是资源分配的最小单位,线程是程序执行的最小单位。
- 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
- 通信机制:
- 正因为进程之间互不干扰,相互独立,进程的通信机制相对很复杂,譬如管道,信号,消息队列,共享内存,套接字等通信机制。
- 而线程由于共享数据段所以通信机制很方便。线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据。
- 但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间.
- 属于同一个进程的所有线程共享该进程的所有资源,包括文件描述符。而不同过的进程相互独立。
- 线程又称为轻量级进程,进程有进程控制块,线程有线程控制块;
- 线程必定也只能属于一个进程,而进程可以拥有多个线程而且至少拥有一个线程;
- 体现在程序结构上,举一个简明易懂的列子:当我们使用进程的时候,我们不自主的使用if else嵌套来判断pid,使得程序结构繁琐,但是当我们使用线程的时候,基本上可以甩掉它,当然程序内部执行功能单元需要使用的时候还是要使用,所以线程对程序结构的改善有很大帮助。
- 线程有自己独立的东西:
- 线程号(thread ID)
- 寄存器
- 堆栈
- 信号掩码
- 优先级
- 线程私有的
- 存储空间
- 编写多线程呢个应用程序时,要使用头文件pthread.h,连接时要库libpthrear.a。所以在写的程序中用到这个函数的时候,需要gcc -o … … -lpthread
创建线程
创建线程的基本函数
获取本线程的线程ID:
pthread_t pthread_self(void);
成功返回本线程的线程ID
在LinuxThreads中,每个线程都用一个pthread_descr结构来描述,其中包含了线程状态、线程ID等所有需要的数据结构,此函数的实现就是在线程栈帧中找到本线程的pthread_descr结构,然后返回其中的p_tid项。
pthread_t类型在LinuxThreads中定义为无符号长整型
- 判断两个线程ID是否指向同一个线程
int pthread_equal(pthread_t thread1,pthread_t thread2);
相等返回非0,不等返回0
- 用来保证init_routine线程函数在进程中仅执行一次
在多线程环境中,有些事只需要运行一次。同化成那个当初始化应用程序时,可以比较容易的将其放在main函数中,但是当你写一个库时,就不能在main里面初始化了,你可以用这个函数
int pthread_once(pthread_once_t *once_control,void(*init_routine)(void))
once_control初始值为PTHREAD_ONCE_INIT来保证init_routine函数在本进程执行序列中仅执行一次。
在多线程编程环境下,尽管pthread_once()调用会出现在多个线程中,init_routine()函数仅执行一次,究竟在哪个线程中执行是不定的,是由内核调度来决定。
Linux Threads使用互斥锁和条件变量保证由pthread_once()指定的函数执行且仅执行一次,而once_control表示是否执行过。
如果once_control的初值不是PTHREAD_ONCE_INIT(Linux Threads定义为0),pthread_once() 的行为就会不正常。
在LinuxThreads中,实际”一次性函数”的执行状态有三种:NEVER(0)、IN_PROGRESS(1)、DONE (2),如果once初值设为1,则由于所有pthread_once()都必须等待其中一个激发”已执行一次”信号,因此所有pthread_once ()都会陷入永久的等待中;如果设为2,则表示该函数已执行过一次,从而所有pthread_once()都会立即返回0。
- 线程创建函数
int pthread_create (pthread_t *thread,pthread_attr_t *attr,void*(*start_routine)(void *),void *arg);
线程与进程内的线程共享程序代码,一段代码可以同时被多个线程控制
- 线程创建函数
参数:
- thread:一个指针,当线程创建成功时,用来返回创建的线程ID(传出参数)
- attr:指定线程的属性,NULL表示使用默认属性。
- start_routine:该参数为一个函数指针,指向线程创建后要调用的函数。这个被线程调用的函数也称为线程函数。
- arg:传递给线程函数的参数
返回值
- 创建成功返回0,新创建的线程开始运行第三个参数所指向的函数,原来的线程继续运行。
- 创建失败返回不为0:
- 错误代码:
- EAGAIN:系统限制创建的进程
- EINVAL:第二个参数代表的线程属性值非法
实例:
#include<stdlib.h> #include<stdio.h> #include<unistd.h> #include<pthread.h> int * thread(void *arg) { pthread_t newthid; newthid=pthread_self(); printf("this is a new thread,thread ID is:%lu\n",newthid); return NULL; } int main(void) { pthread_t thid; printf("main thread,ID is %lu\n",pthread_self());//打印主线程的ID if(pthread_create(&thid,NULL,(void *)thread,NULL)!=0) { printf("thread creation failed\n"); exit(1); } sleep(1); exit(0); }
![2018-08-06 11-10-49 的屏幕截图](/home/lala/图片/2018-08-06 11-10-49 的屏幕截图.png)
某个线程函数执行次数限制为一次:pthread_once
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
pthread_once_t once=PTHREAD_ONCE_INIT;
void run(void)
{
printf("Function run is running in thread %lu\n",pthread_self());
}
void * thread1(void *arg)
{
pthread_t thid=pthread_self();
printf("Current thread's ID is %lu\n",thid);
pthread_once(&once,run);
printf("thread1 ends\n");
}
void * thread2(void *arg)
{
pthread_t thid=pthread_self();
printf("Current thread's ID is %lu\n",thid);
pthread_once(&once,run);
printf("thread2 ends\n");
}
int main(void)
{
pthread_t thid1,thid2;
pthread_create(&thid1,NULL,thread1,NULL);
pthread_create(&thid2,NULL,thread2,NULL);
sleep(3);
printf("main thread exit!\n");
exit(0);
}
线程终止
线程中只有两种方法:
return 从线程函数返回
调用函数pthread_exit()使线程退出。
pthread_exit():
#include<pthread.h> void pthread_exit(void *retval);
两种特殊情况:
- 在主线程中,如果从main函数返回或是调用了exit函数退出主线程,整个进程终止,所有线程终止。
- 在主线程中调用pthread_exit函数,仅仅是主线程消亡,进程不会结束,进程内的其他线程不会终止,直到所有线程结束,进程才会结束。
线程终止的最重要的问题就是资源释放的问题,特别是临界资源。临界资源在一段时间内只能能被一个线程所持有,当线程要使用临界资源时要提出请求,如果资源未被使用则申请成功,否则等待。临界资源使用完后要释放。临界资源为一个线程所独占,当一个线程终止时,如果不释放临界资源,该资源会被认为还被已退出的线程所使用,因此永远不会释放。如果一个线程在等待使用这个临界资源,他就可能无限的等待下去,形成死锁。
- eg:某个线程要写一个文件,在写文件时一般不允许其他线程也对该文件执行写操作的,否组会导致文件数据混乱。这里的文件就是一种临界资源。
提供了一对函数:自动释放资源(成对出现且位于程序的统一代码中才能通过编译)
- pthread_cleanup_push(),pthread_cleanup_pop()
- 从调用点pthread_cleanup_push()到pthread_cleanup_pop()之间的程序段中的终止动作(如调用pthread_exit)都将执行pthread_cleanup_push()所制定的清理函数。
- 两个函数是以宏形式提供的
#include<pthread.h> #define pthread_cleanup_push(routine,arg); { struct _pthread_cleanup_buffer; _pthread_cleanup_push(&buffer,(routine),(srg)); #define pthread_cleanup_pop _pthread_cleanup_pop(&buffer,(exeute)); }
系统同步的问题
- 一般情况下,进程的各个线程的运行是相互独立的,线程的终止并不会相互通知,也不会影响其它线程,终止的线程占用的资源不会随着线程的终止而归还系统,而是仍为线程所在的进程持有。
#include<pthread.h> void pthread_exit(void *retval); int pthread_join(pthread_t th,void *thread_return); int pthread_detach(pthread_t th);
作用:用来等待一个线程的结束
pthread_join()的调用者将被挂起并等待th线程终止,如果thread_return不是NULL,则 thread_return=retval
一个线程仅允许一个线程使用pthread_join()等待他的终止,并且被等待的线程应该处于可join的状态,即非DETACHED状态,这个状态是指某个线程执行pthread_detach()后其所处的状态。处于DETACHED状态的线程无法由pthread_join()同步。
一个可“join”的线程所占用的内存仅当有现成对其执行了pthread_join()后才会释放,因此为了避免内存泄漏,所有线程的终止时,要么已经被设为DETACHED,要么使用pthread_join()啦回收资源。
一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join()的线程返回错误代码ESRCH
关于pthread_exit和pthread_join区别:
pthread_join一般是主线程来调用,用来等待子线程退出。因为是等待,所以是阻塞的。一般主线程会依次join所有它创建的子线程。(主线程会堵塞住等到子线程进行完后在执行主线程)
pthread_exit一般是子线程调用,用来结束当前线程。
子线程可以通过pthread_exit传递一个返回值,而主线程通过pthread_join获得该返回值,从而判断子线程的退出是正常还是异常的。
线程的退出可以是调用pthread_exit或者是该线程的例程结束。也就是说,一个线程可以隐式的退出,也可以显式的调用pthread_exit函数来退出
pthread_exit函数的唯一参数retval是函数返回的代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将传给thread_return
这个线程退出的返回值的格式是void*,无论是什么格式都要强转成void*才能返回出来主线程(pthread_exit((void*)tmp);),而这个时候pthread_join就去接这个值,我们传进去一个void*的地址也就是&(void*),传地址进去接值是接口类函数常用的做法,有同样效果的做法是引用&,但是这个做法一来值容易被误改,二来不规范,所以定义一个类型然后把地址传进去修改value。回到题目,这里返回的void*是一个指针类型,必须强转成对应的指针才能用。
举个例子,如果是char* = “mimida”;传出来的tmp,必须(char*)tmp一下。
而如果是int* a = new(3888);这种类型返回的tmp,必须(int)tmp一下才能用。
最重要的一点,你定义的类型和最后出来的类型一定要一致,不然很容易出现问题。也就是你定义了int*,最后强转出来的一定是(int)。
别void* a = (void*)10;这种诡异的格式(我就中过招),一开始是什么就转成什么!(这个规则同时也适用于线程数据里的set和get
#include<pthread.h> #include<stdlib.h> #include<unistd.h> #include<stdio.h> void assisthread(void *arg) { printf("i am helping to do some jobs\n"); sleep(3); pthread_exit((void *)1);//注意这里 } int main(void) { pthread_t assistthid; int status; pthread_create(&assistthid,NULL,(void *)assisthread,NULL); pthread_join(assistthid,(void *)&status);//数以后面status的格式 printf("assistthid's exit is caused %d\n",status); return 0; }
![2018-08-07 11-22-51 的屏幕截图](/home/lala/图片/2018-08-07 11-22-51 的屏幕截图.png)
私有数据
原因:在多线程环境下,进程内的所有线程共享进程的数据空间,因此全局变量为所有线程共有。eg:errno它返回标准的出错代码。errno不应该是一个局部变量,每个函数都要访问它;但又不能是一个全局变量,否则在一个线程里有可能输出的是另一个线程的出错信息。所以可以创建线程的私有数据。(TSD)
方法理论:线程私有数据采用了“一键多值的技术,即一个键对应多个值”,访问数据时是通过键值来访问的,好像是对一个变量进行访问,其实是在访问不同的数据。使用线程私有数据时,首先要为每个线程数据创建一个相关联的键。在各个线程内部,都使用这个公用的键来指代线程数据,但是在不同的线程中,这个键代表的数据是不同的。
操作线程私有数据的函数:#include
线程同步
互斥锁
同一时刻只能有一个线程得到互斥锁
生动的比喻:
- 互斥锁:厕所的隔间
- 门开着谁都可以进,但一次只能进一个人,进去之后就锁上了,厕所里面有人的时候其他人就要排队,直到里面的人出来。
作用:实现线程间的同步
mutex的本质:锁
几个基本概念:
- 互斥:一个进程进入工作区后,如果有其他进程要进入工作区,他就会进入等待状态,要等待工作区内的线程结束后才可以进入。
- 互斥提供线程间资源的独占访问控制。它是一个简单的锁,只有持有它的线程才可以释放那个互斥。它确保了它们正在访问的共享资源的完整性,因为在同一时刻只允许一个线程访问它。
- 互斥操作,就是对某段代码或某个变量修改的时候只能有一个线程在执行这段代码,其它线程不能同时进入这段代码或同时修改该变量。这个代码或变量称为临界资源。通过锁机制实现线程间的同步,同一时刻只允许一个线程执行一个关键部分的代码。
有两种方式创建互斥锁,静态方式和动态方式。:
在默认情况下,Linux下的同一线程无法对同一互斥锁进行递归加锁,否则将发生死锁。所谓递归加锁,就是在同一线程中试图对互斥锁进行两次或两次以上的行为。解决问题的方法就是显示地在互斥变量初始化时将其设置成recursive属性。
静态赋值法:
pthread_mutex_t mutex;
mutex=PTHREAD_MUTEX_INITIALIZER;
动态法:(通过函数)
pthread_mutex_t mutex;
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *mutexattr);
mutexattr:互斥锁的属性,NULL为默认属性
-
属性值 意义 PTHREAD_MUTEX_TIMED_NP 这是缺省值,也就是普通锁,当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁,这种锁策略保证了资源分配的公平性 PTHREAD_MUTEX_RECURSIVE_NP 嵌套锁,允许线程多次加锁,不同线程,解锁后重新竞争 PTHREAD_MUTEX_ERRORCHECK_NP 检错,如果该互斥量已经被上锁,那么后续的上锁将会失败而不会阻塞,否则与PTHREAD_MUTEX_TIMED_NP类型相同,这样就保证当不允许多次加锁时不会出现最简单情况下的死锁 PTHREAD_MUTEX_ADAPTIVE_NP, 适应锁,动作最简单的锁类型,仅等待解锁后重新竞争
pthread_mutex_destroy函数:销毁(注销)线程互斥锁;销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。
int pthread_mutex_destroy(pthread_mutex_t *mutex);
若锁处于锁定状态,函数返回EBUSY。
函数执行成功返回0(互斥锁不占用内存)
pthread_mutex_lock:互斥锁被锁定,如果这个互斥锁被一个线程锁定和拥有,那么另一个线程要调用这个函数就会进入阻塞状态(即等待状态),直到互斥锁被释放为止;互斥量一旦被上锁后,其它线程如果想给该互斥量上锁,那么就会阻塞在这个操作上,如果在此之前该互斥量已经被其它线程上锁,那么该操作将会一直阻塞在这个地方,直到获得该锁为止。
int pthread_mutex_lock(pthread_mutex_t *mutex);
pthread_mutex_trylock:
int pthread_mutex_trylock(pthread_mutex_t *mutex);
如果mutex已经被加锁,则立即返回,返回的错误代码为EBUSY,而不是阻塞等待。
- pthread_mutex_unlock:释放互斥锁;在操作完成后,必须调用该函数给互斥量解锁,这样其它等待该锁的线程才有机会获得该锁,否则其它线程将会永远阻塞。
int pthread_mutex_unlock(pthread_mutex_t *mutex);
条件变量
条件变量的实质是:等待
条件变量利用线程间共享的全局变量进行同步的一种机制。条件变量宏观上类似于if语句,符合条件就能执行某段程序,否则只能等待条件成立。
与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。条件变量分为两部分:条件和变量。条件本身是由互斥量保护的。线程在改变条件状态前先要锁住互斥量。条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待“条件变量的条件成立”而挂起;另一个线程使“条件成立”(给出条件成立信号)。条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两线程共享可读写的内存,条件变量可以被用来实现这两线程间的线程同步。
互斥锁一个明显的缺点是它只有两种状态,锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般来说,条件变量被用来进行线程间的同步。条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出。线程被唤醒后,它将重新检查判断条件是否满足,如果还不满足,一般说来线程应该仍阻塞在这里,被等待被下一次唤醒。这个过程一般用while语句实现。
条件变量用pthread_cond_t结构体来表示。
条件变量的初始化:
静态赋值法:
pthread_cond_t cond;
cond=PTHREADCOND_INITIALIZER;
利用函数:
pthread_cond_t cond;
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
pthread_cond_init:初始化一个条件变量。第二个参数一般为:NULL。不能由多个线程同时初始化一个条件变量。当需要重新初始化或释放一个条件变量时,应用程序必须保证这个条件变量未被使用。
等待条件成立有两个函数:
pthread_cond_wait:
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
作用:释放由mutex指向的互斥锁,同时使当前线程关于cond指向的条件变量阻塞,直到条件被信号唤醒。通常条件表达式在互斥锁的保护下求值,如果条件表达式为假,那么线程给予条件变量阻塞。当一个线程改变条件变量的值时,条件变量获得一个信号,使得等待条件变量的线程退出阻塞状态。
阻塞在条件变量上,函数将解锁第二个参数指向的互斥锁,并使当前线程阻塞在第一个参数指向的条件变量上。被阻塞的线程可以被pthread_cond_signal、pthread_cond_broadcast函数唤醒,也可能在被信号中断后被唤醒。
一般一个条件表达式都是在一个互斥锁的保护下被检查。当条件表达式未被满足时,线程将仍然阻塞在这个条件变量上。当另一个线程改变了条件的值并向条件变量发出信号时,等待在这个条件变量上的一个线程或所有线程被唤醒,接着都试图再次占有相应的互斥锁。阻塞在条件变量上的线程被唤醒以后,直到pthread_cond_wait函数返回之前,条件的值都有可能发生变化。所以函数返回以后,在锁定相应的互斥锁之前,必须重新测试条件值。最好的测试方法是循环调用pthread_cond_wait函数,并把满足条件的表达式置为循环的终止条件。阻塞在同一个条件变量上的不同线程被释放的次序是不一定的。
pthread_cond_wait函数是退出点,如果在调用这个函数时,已有一个挂起的退出请求,且线程允许退出,这个线程将被终止并开始执行善后处理函数,而这时和条件变量相关的互斥锁仍将处在锁定状态。
pthread_cond_timewait:
int pthread _cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex,const struct timespec *abstime);
阻塞直到指定时间。函数到了一定的时间,即使条件未发生也会解除阻塞。这个时间由第三个参数指定
线程被条件变量阻塞后,可通过一下函数激活:
pthread_cond_signal:
int pthread_cond_signal(pthread_cond_t *cond);
解除在条件变量上的阻塞。此函数被用来释放被阻塞在指定条件变量上的一个线程。一般在互斥锁的保护下使用相应的条件变量,否则对条件变量的解锁有可能发生在锁定条件变量之前,从而造成死锁。唤醒阻塞在条件变量上的所有线程的顺序由调度策略决定。
pthread_cond_broadcast:
int pthread_cond_broadcast(pthread_cond_t *cond);
释放阻塞的所有线程。函数唤醒所有被pthread_cond_wait函数阻塞在某个条件变量上的线程。当没有线程阻塞在这个条件变量上时,此函数无效。此函数唤醒所有阻塞在某个条件变量上的线程,这些线程被唤醒后将再次竞争相应的互斥锁。
pthread_cond_destroy:
int pthread_cond_destroy(pthread_cond_t *cond);
释放条件变量。条件变量占用的空间未被释放。
pthread_cond_wait和pthread_cond_timewait一定要在mutex的锁定区域内使用;而pthread_cond_signal和pthread_cond_broadcoast无需考虑调用线程是否是mutex的拥有者,可以在lock与unlock以外的区域调用。
一个特定条件只能有一个互斥对象,而且条件变量应该表示互斥数据“内部”的一种特殊的条件更改。一个互斥对象可以有许多条件变量,但每个条件变量只能有一个互斥对象。
异步信号
线程是在内核外实现的,进程实在内核中实现的
信号可以被进程用来相互通信,一个进程通过信号通知另一个进程发生了某事情。线程同进程一样也可以接受和处理信号,信号也是一种线程间同步的手段。
信号(SIGINT和SIGIO)与任何线程都是异步的,也就是说信号到达线程的时间是不定的。如果有多个线程可以接受异步信号,只有一个被选中。如果并发的多个同样的信号被送到一个进程,每一个将被不同的线程处理。如果所有的线程都屏蔽该信号,则这些信号将被挂起,直到有信号解除屏蔽来处理他们。
有三个函数处理异步信号:
int pthread_kill(pthread_t threadid,int signo); int pthread_sigmask(int how,const sigset_t *newmask,sigset_t *oldmask); int sigwait(const sigset_t *set,int *sig);
- pthread_kill用来向特定的线程发送信号signo
- pthread_sigmask用来设置线程的信号屏蔽码,但对于屏蔽的Cancel信号和不允许响应的Restart信号进行了保护。
- sigwait用来阻塞线程,等待set中指定的信号之一一到达,并将到达的信号存入*sig中。