第12章 线程控制
1. 线程属性
线程属性结构体:pthread_arrr_t
int pthread_attr_init(pthread_attr_t *attr); //初始化线程属性
int pthread_attr_destroy(pthread_attr_t*attr); //释放属性对象内存
线程属性主要有:
(1)线程的分离状态属性detachstate,
(2)线程栈末尾的警戒缓冲区大小guardsize,
(3)线程栈的最低地址statckaddr,
(4)线程栈的大小stacksize。
其中常用的是分离状态属性:
int pthread_attr_getdetachstate(pthread_attr_t*attr, int *detachstate);//获取当前的分离状态属性
int pthread_attr_setdetachstate(pthread_attr_t*attr, intdetachstate); //设置分离状态属性
detatchstate取值:
(1)PTHREAD_CREATE_DETACHED分离状态启动,
(2)PTHREAD_CREATE_JOINABLE正常启动,应用程序可以获取线程的终止状态。
2. 同步属性
1) 互斥量属性
进程共享属性和类型属性两种
i. 进程共享属性
PTHREAD_PROCESS_PRIVATE,默认,进程间不共享,进程私有。用于进程中的线程同步。
PTHREAD_PROCESS_SHARED,进程间共享,用于进程的同步。
int pthread_mutexattr_init(pthread_mutexattr_t*attr); //初始化
int pthread_mutexattr_destroy(pthread_mutexattr_t*attr); //回收
int pthread_mutexattr_getpshared(constpthread_mutexattr_t *restrictattr,int *restrictpshared); //查询进程共享属性
int pthread_mutexattr_setpshared(pthread_mutexattr_t*attr,intpshared); //设置进程共享属性
ii. 类型属性
int pthread_mutexattr_gettype(constpthread_mutexattr_t *restrictattr,int*restricttype); //查询类型属性
int pthread_mutexattr_settype(pthread_mutexattr_t*attr, inttype); //设置类型属性
| ||||
互斥量类型 | 用途 | 没有解锁时再次加锁 | 不占用时解锁 | 在已解锁时解锁 |
PTHREAD_MUTEX_NORMAL | 标准类型,不做任何检查 | 死锁 | 未定义 | 未定义 |
PTHREAD_MUTEX_ERRORCHECK | 进程错误检查 | 返回错误 | 返回错误 | 返回错误 |
PTHREAD_MUTEX_RECURSIVE | 避免死锁 | 允许 | 返回错误 | 返回错误 |
PTHREAD_MUTEX_DEFFAULT | 请求默认语义 | 未定义 | 未定义 |
PTHREAD_MUTEX_RECURSIVE类型:递归锁,允许在同一个线程在互斥量解锁之前对该互斥量进程多次加锁,解锁次数和加锁次数不同时不会释放锁。
2) 读写锁属性
只有一个:进程共享属性,与互斥量类似。
int pthread_rwlockattr_init(pthread_rwlockattr_t*attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t*attr);
int pthread_rwlockattr_getpshared(constpthread_rwlockattr_t *restrict attr, int *restrict pshared);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t*attr,int pshared);
3) 条件变量属性
与读写锁属性类似。
3. 线程私有数据(线程局部数据)
多线程编程时,遇到最多的就是多线程读写同一变量的问题,由于线程中的局部变量只能在函数体内使用,不能跨函数使用,而线程的全局变量又被进程内的所有线程所共享,因此存在线程数据安全的需求。大多情况下遇到这类问题都是通过锁机制来处理,但这对程序的性能带来了很大的影响。
在一个线程内部的各个函数调用都能访问,但其它线程不能访问的变量,称之为线程特有数据(TSD: Thread-SpecificData)或者线程局部存储(TLS:Thread-Local Storage)。这一类型的数据,在程序中每个线程都会分别维护一份变量的副本,并且长期存在于该线程中,对此类变量的操作不影响其他线程。
其实现机制是:
进程内分配一个全局数组,数组中的每个元素是一个结构体,其包含两个变量:标记该键是否在用的标志变量、线程局部变量的析构函数的函数指针。当调用pthread_key_create()创建一个键时,得到的实际上是该数组的一个索引值。系统根据“在用”标志查找一个未用的元素,将“在用”标志置为used,然后存入该变量的析构函数指针,最后返回该数组的索引值。
另外,在每个线程内,线程还分别维护一个数组,存放每个线程的线程特有数据块的地址。pthreadAPI为每个函数都维护指向线程局部数据块的指针数组,其中每个数组元素都与进程内全局pthread_keys中元素一一对应。当调用pthread_setspcific()函数时,将线程特有数据块的指针放入与该Key相对应的数组元素位置。这样,也就将线程私有数据绑定(关联)到了该键上。调用pthread_getspecific() 即可取出所存储的数据。
表面上看起来这是一个全局变量(Key),所有线程都可以使用它,而它的值在每一个线程中又是单独存储的,都是各个线程私有的。
下面说一下线程私有数据的具体用法。
1)首先创建一个类型为pthread_key_t 类型的变量key。
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
第一个参数就是上面声明的pthread_key_t 变量,
第二个参数是一个清理函数,用来在线程释放该线程存储的时候被调用。该函数指针可以设成 NULL,这样系统将调用默认的清理函数。
2)当线程中需要存储特殊值的时候,调用pthread_setspcific() 关联私有数据。
int pthread_setspecific(pthread_key_tkey, const void *value);
第一个为前面声明的pthread_key_t 变量key,
第二个为void* 变量,指向线程私有数据块,可以存储任何类型的值。
3)当需要使用私有数据时,调用 pthread_getspecific() 。
void *pthread_getspecific(pthread_key_tkey);
传入参数为键,返回void* 类型指针,指向要使用的线程私有数据。因此若要使用该数据,还需要一个间接访问操作。
4)调用pthread_key_delete函数取消键与私有数据的关联。
int pthread_key_delete(pthread_key_tkey);
键所占用的内存将被释放。注意,与该键关联的线程私有数据所占用的内存并不被释放。因此,线程数据的释放必须在释放键之前完成。
写了一个小例子,定义了一个进程全局变量,即键,创建了两个线程,每个线程内各定义了一个线程私有数据tsd,在线程内可视为全局变量,可被线程内所有函数使用,但在线程之间是局部变量,是为线程所私有的。
#include"apue.h"
#include<pthread.h>
pthread_key_tmykey;//定义一个键,线程全局可见
voidprint_tsd(void * arg)
{
int *tsd_p=(int*)pthread_getspecific(mykey);
printf("%sprint_tsd : %d\n",(char*)arg,*tsd_p);//打印线程局部变量tsd的值
}
void* tid2_func(void * arg)
{
printf("thread2 starting in %s ,tid= %d\n",(char*)arg,(int)pthread_self());
int tsd=1;//线程2私有数据
pthread_setspecific(mykey,&tsd);
printf("tid2 tsd's arr is%ld\n",(long int)pthread_getspecific(mykey));
print_tsd("thread2");
}
void* tid1_func(void * arg)
{
printf("thread1starting in %s , tid= %d\n",(char*)arg,(int)pthread_self());
int tsd=0;//线程1私有数据
pthread_setspecific(mykey,&tsd);//与键关联
printf("tid1tsd's arr is %ld\n",(long int)pthread_getspecific(mykey));//打印局部变量地址
print_tsd("thread1");//在另一个函数中打印线程局部变量的值
}
voidmydestr(void* arg)
{
printf("destroyTSD memory\n");
}
intmain(void)
{
pthread_ttid1,tid2;
pthread_key_create(&mykey,mydestr);//创建键
pthread_create(&tid1,NULL,tid1_func,"tid1_func");
sleep(3);
pthread_create(&tid2,NULL,tid2_func,"tid2_func");
sleep(3);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_key_delete(mykey);
exit(0);
}
4. 线程和IO
pread和pwrite函数
偏移量的设定和数据操作为原子操作,可以解决并发线程对同一文件的读写操作问题。
5. 线程和信号
信号的处理时进程中所有线程共享的,当线程修改了与某个信号相关的处理行为后,所有线程必须共享这个处理行为的改变。
多线程中的信号机制,与进程的信号机制有着根本的区别:
在进程环境中,对信号的处理是:先注册信号处理函数,当信号异步发生时,调用处理函数来处理信号。它完全是异步的(我们完全不知道信号会在进程的那个执行点到来!)。
在多线程中处理信号的原则却完全不同,它的基本原则是:将对信号的异步处理,转换成同步处理,也就是说用一个线程专门的来“同步等待”信号的到来,而其它的线程可以完全不被该信号中断/打断(interrupt)。这样就在相当程度上简化了在多线程环境中对信号的处理。而且可以保证其它的线程不受信号的影响。这样我们对信号就可以完全预测,因为它不再是异步的,而是同步的(我们完全知道信号会在哪个线程中的哪个执行点到来而被处理!)。
每个线程都可以拥有自己的信号屏蔽集,可以使用pthread_sigmask函数设置该线程的屏蔽信号集。为防止信号中断线程,可以把信号加到每个线程的信号屏蔽字中,然后安排一个专用的线程作信号处理。实现方式是:利用线程信号屏蔽集的继承关系(在主线程中使用pthread_sigmask进行设置后,主线程创建出来的线程将继承主线程的掩码)。
int pthread_sigmask (int how,const sigset_t *set,sigset_t *oset);//设置线程屏蔽字
成功返回0,出错返回错误编号。
信号处理线程可以调用sigwait等待指定信号发生:
int sigwait(const sigset_t *set, int *sig);
成功返回0,出错返回错误编号。
set指定线程等待的信号集,sig返回发生的信号值。
线程在调用sigwait之前,必须阻塞它正在等待的信号,sigwait调用后会自动取消其阻塞状态,然后线程挂起直到等待的信号被递送,然后函数在返回之前,重新恢复线程信号屏蔽字。而如果调用sigwait之前没有阻塞要等待的信号,则有可能在sigwait调用之前的时间窗口内到来信号。
在多线程程序中,一个线程可以使用pthread_kill对同一个进程中指定的线程(包括自己)发送信号。注意在多线程中一般不使用kill函数发送信号,因为kill是对进程发送信号,结果是:正在运行的线程会处理该信号,如果该线程没有
注册信号处理函数,那么会导致整个进程退出。
int pthread_kill(pthread_t thread, intsig);
例:线程信号处理程序框架
#include“apue.h”
#include<pthread.h>
sigset_tmask;
void*sig_handler(void *arg)
{
int sig;
for( ; ; )
{
if(sigwait(&mask, &sig) != 0)
err_sys(“sigwait error\n”);
switch(sig)
{
case SIGINT:
{
/* SIGINT信号处理语句 */
printf(“SIGINT\n”);
break;
}
caseSIGUSR1:
{
/* SIGUSR1信号处理语句 */
printf(“SIGUSR1\n”);
break;
}
caseSIGUSR2:
{
/* SIGUSR2信号处理语句 */
printf(“SIGUSR2\n”);
break;
}
default:
{
/* 其他语句 */
printf(“unexpected signal: %d\n”,sig);
break;
}
}
}
}
intmain(void)
{
sigset_t oldmask;
pthread_t tid;
sigemptyset(&mask);
sigaddset(&mask,SIGINT);
sigaddset(&mask,SIGUSR1);
sigaddset(&mask,SIGUSR2);
if(pthread_sigmask(SIG_BLOCK, &mask,&oldmask)!=0)//设置线程信号屏蔽字
err_sys(“pthread_sigmask error\n”);
if(pthread_create(&tid, NULL,sig_handler,0)!=0)//创建信号处理专用线程
err_sys(“pthread_create error\n”);
/* 其他代码:主线程主要执行语句 */
sleep(30);
if(sigprocmask(SIG_SETMASK, &oldmask,NULL)<0)
err_sys(“sigprocmask error\n”);
exit(0);
}