unix线程控制
线程属性
在创建线程时,可以用pthread_attr_t结构修改线程默认属性,并把这些属性与创建的线程联系起来。可以用pthread_attr_init函数初始化pthread_attr_t结构。
#include
int pthread_attr_init (pthread_attr_t* attr) ;
int pthread_attr_destroy (pthread_attr_t* attr) ;
线程属性包括:线程的分离状态属性、线程栈末尾的警戒缓冲区大小、线程栈的最低地址、线程栈的大小……
分离线程
如果对现有的某个线程的终止状态不感兴趣,可以使用pthread_detach函数让操作系统在线程退出时回收它所占用的资源。(分离:不与其他线程联系,即不把自己的终止状态返回给线程)
如果线程已经处于分离状态,pthread_join调用就会失败,返回einval。
如果在创建线程时就知道不需要了解线程的终止状态,则可以修改pthread_attr_t结构的相关属性,可以使用pthread_attr_setdetachstate函数把线程属性detachstate设置为:pthread_create_detached,以分离状态启动。或设置为pthread_create_joinable,正常启动线程,应用程序就可以获取线程的终止状态。
线程私有数据
有时候我们可能会有这种需求:需要一个在线程内的全局变量,即此变量只能让此线程使用,其他线程不能访问。(线程会调用多个模块,各模块之间可能需要一个全局变量)
(当然,我们也可以在线程函数中定义变量,把其地址传给各个子模块函数,这样做毕竟不方便,使函数的接口变的复杂)
在分配线程私有数据之前,需要创建与该数据关联的键。这个键将用于获取对线程私有数据的访问权。
#include
int pthread_key_create(pthread_key_t * keyp, void (*destructor)(void *)) ;
创建的键存放在keyp指向的内存单元,除创建键以外,pthread_key_create可以选择为该键关联析构函数,当线程调用pthread_exit或线程执行返回,正常退出时,析构函数就会被调用。但如果线程调用了exit、_exit、_exit、abort或出现其他非正常的退出时,就不会调用析构函数。
线程通常使用malloc为线程私有数据分配内存空间,析构函数通常释放已分配的内存。
键一旦创建,就可以通过调用pthread_setspecific函数把键和线程私有数据关联起来。可以通过pthread_getspecific函数获得线程私有数据的地址。
#include
int pthread_setspecific(pthread_key_t key, const void * value) ;
void* pthread_getspecific(pthread_key_t key) ;
如果没有线程私有数据值与键关联,pthread_getspecific将返回一个空指针,可以据此来确定是否需要调用pthread_setspecific
【示例】
//线程私有数据的例子
//
//
#include
#include
#include
#include
//线程私有数据的键 对所有线程是可见的(但只有创建它的线程是可以访问的)
static pthread_key_t key ;
//以下两个条件变量用于线程间的同步
struct
{
pthread_cond_t cond ;
pthread_mutex_t mutex ;
int nready ; //允许线程继续执行
} condforthread1 = {
pthread_cond_initializer ,
pthread_mutex_initializer
} ;
struct
{
pthread_cond_t cond ;
pthread_mutex_t mutex ;
int nready ; //允许线程继续执行
} condforthread2 = {
pthread_cond_initializer ,
pthread_mutex_initializer
} ;
void* thread1(void* arg);
void* thread2(void* arg);
void childfunc();
int
main(void)
{
pthread_t tid1, tid2 ;
pthread_create(&tid1, null, thread1, null) ;
pthread_create(&tid2, null, thread2, null) ;
pthread_join(tid1, null) ;
pthread_join(tid2, null) ;
return 0 ;
}
void*
thread1(void* arg)
{
char* privatebuf = null ;
int bufsize = 10 ;
int i = 0 ;
pthread_key_create(&key, free) ; //创建键
//获取键的关联数据的指针(检查此键是否已经关联了数据)
privatebuf = (char*)pthread_getspecific(key) ;
if (privatebuf == null)
{
privatebuf = malloc(bufsize) ;
if (privatebuf == null)
return ;
}
//设置 键与数据的关联
pthread_setspecific(key, privatebuf) ;
childfunc() ; //子模块访问线程的私有数据
//打印buf
for (i = 0; i < bufsize; i)
{
printf("%d,\n", privatebuf[i]) ;
}
//向线程2发信号 唤醒线程2
condforthread2.nready = 1 ;
pthread_cond_signal(&condforthread2.cond) ;
//挂起 等待来自线程1的信号
pthread_mutex_lock(&condforthread1.mutex) ;
pthread_cond_wait(&condforthread1.cond, &condforthread1.mutex) ;
pthread_mutex_unlock(&condforthread1.mutex) ;
}
void
childfunc()
{
int i = 0 ;
char* buf ;
buf = (char*)pthread_getspecific(key) ;
for (i = 0; i < 10; i)
{
buf[i] = i ;
}
}
void*
thread2(void* arg)
{
char* privatebuf = null ;
int bufsize = 10 ;
int i = 0 ;
//等待来自线程1的信号
pthread_mutex_lock(&condforthread2.mutex) ;
pthread_cond_wait(&condforthread2.cond, &condforthread2.mutex) ;
pthread_mutex_unlock(&condforthread2.mutex) ;
//获取键的关联数据的指针(会引发段错误,不允许其他线程访问key)
privatebuf = (char*)pthread_getspecific(key) ;
//打印buf
for (i = 0; i < bufsize; i)
{
printf("*%d,\n", privatebuf[i]) ;
}
//向线程1发信号
condforthread1.nready = 1 ;
pthread_cond_signal(&condforthread1.cond) ;
}
线程和信号
进程中的信号是递送到单个线程的,如果信号与硬件故障或计时器超时相关,该信号就被发送到引起该事件的线程中去,而其他的信号则被发送到任意一个线程。
每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的。
在多线程环境下设置信号屏蔽字:使用pthread_sigmask函数(因为sigprocmask的行为在多线程的进程中没有定义)
#include
int pthread_sigmask (int how, const sigset_t * set, sigset_t* oset) ;
此函数与sigprocmask函数基本相同。
线程等待信号
#include
int sigwait (const sigset_t * set, int * signop) ;
参数set指出了线程等待的信号集,参数signop为返回值是等到的是哪个信号
注意:
为了避免错误动作发生,线程在调用sigwait之前,必须阻塞那些它正在等待的信号。sigwait函数会自动取消信号集的阻塞状态,直到有新的信号被递送。在返回之前,sigwait将恢复线程的信号屏蔽字。
使用sigwait的好处在于它可以简化信号处理。
为了防止信号中断线程,可以把信号加到每个线程的信号屏蔽字中,然后安排专用线程做信号处理。因为sigwait会解除信号的阻塞状态,所以只有一个线程可以用于信号的接收。这使得对主线程进行编码时不必担心来自这些信号的中断。
【注意】闹钟定时器是进程资源,并且所有的线程共享相同的alarm。所以进程中的多个线程不可能互不干扰地使用闹钟定时器。
【示例】
//线程对信号的处理
//sigquit 对应键盘 ctrl \
//sigint 对应键盘 ctrl c
#include
#include
#include
#include
#include
sigset_t mask ; //信号集
int quitflag ; //收到退出信号的标记
pthread_cond_t waitcond = pthread_cond_initializer ;
pthread_mutex_t lock = pthread_mutex_initializer ;
void* thr_signal_handle(void* arg) ;
int
main(void)
{
int err ;
sigset_t oldmask ;
pthread_t tid ;
sigemptyset(&mask) ;
sigaddset(&mask, sigint) ;
sigaddset(&mask, sigquit) ;
//设置进程信号屏蔽字
if ((err = pthread_sigmask(sig_block, &mask, &oldmask)) != 0)
perror("sig_block error!\n") ;
//创建线程
if ((err = pthread_create(&tid, null, thr_signal_handle, null)) != 0)
perror("can't create thread!\n") ;
//等待线程返回消息
pthread_mutex_lock(&lock) ;
while (quitflag == 0)
pthread_cond_wait(&waitcond, &lock) ;
pthread_mutex_unlock(&lock) ;
quitflag = 0 ;
//恢复原进程信号屏蔽字
if (sigprocmask(sig_setmask, &oldmask, null) < 0)
perror("sig_setmask error!\n") ;
exit(0) ;
}
//专门处理信号的线程
void*
thr_signal_handle(void* arg)
{
int err, signo ;
for(;;)
{
if ((err = sigwait(&mask, &signo)) != 0)
perror("sigwait error!\n") ;
switch(signo)
{
case sigint:
printf("\ninterrupt\n") ;
break ;
case sigquit:
//向主线程发送通知
pthread_mutex_lock(&lock) ;
quitflag = 1 ;
pthread_mutex_unlock(&lock) ;
pthread_cond_signal(&waitcond) ;
return ;
default:
printf("unexpected signal %d\n", signo) ;
exit(1) ;
}
}//for(;;)
}
向线程发送信号
要把信号发送到进程,可以调用kill。要把信号发送到线程,可以调用pthread_kill。
#include
int pthread_kill (pthread_t tid, int signo) ;
【示例】
//线程间信号的发送
#include
#include
#include
#include
#include
sigset_t mask ; //信号集
pthread_t tidbuf[5] ; //存储进程中所有线程的id
int flag ;
pthread_cond_t waitcond = pthread_cond_initializer ;
pthread_mutex_t lock = pthread_mutex_initializer ;
void* thr1(void* arg) ;
void* thr2(void* arg) ;
int
main(void)
{
int err ;
sigset_t oldmask ;
pthread_t tid ;
sigemptyset(&mask) ;
sigaddset(&mask, sigusr1) ;
//阻塞要等待的信号
if ((err = pthread_sigmask(sig_block, &mask, &oldmask)) != 0)
perror("sig_block error!\n") ;
//创建线程
if ((err = pthread_create(&tidbuf[0], null, thr1, null)) != 0)
perror("can't create thread!\n") ;
if ((err = pthread_create(&tidbuf[1], null, thr2, null)) != 0)
perror("can't create thread!\n") ;
//等待线程结束
pthread_join(tidbuf[0], null) ;
pthread_join(tidbuf[1], null) ;
exit(0) ;
}
void*
thr1(void* arg)
{
int err, signo ;
//等待线程2起来
pthread_mutex_lock(&lock) ;
while(flag == 0)
pthread_cond_wait(&waitcond, &lock) ;
pthread_mutex_unlock(&lock) ;
//等待接收来自线程2的信号
if ((err = sigwait(&mask, &signo)) != 0)
perror("sigwait error!\n") ;
if (signo == sigusr1)
printf("\ni catched the signal from thread2!\n") ;
}
//
void*
thr2(void* arg)
{
int err, signo ;
//起来之后向线程1发送信号
pthread_mutex_lock(&lock) ;
flag = 1 ;
pthread_mutex_unlock(&lock) ;
pthread_cond_signal(&waitcond) ;
pthread_kill(tidbuf[0], sigusr1) ;
}
【注意】很多linux系统在编译时,没有自动链接线程库,需要在编译时指定链接:例 gcc a.c -l pthread