APUE 学习笔记——线程控制

来源:互联网 发布:js div滚动条回到顶部 编辑:程序博客网 时间:2024/05/08 04:33

第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);

}

0 0