1 线程的创建、终止
1.1 创建线程
通过pthread_create()函数创建线程,函数定义如下:
int pthread_create(pthread_t * thread , pthread_attr_t const* attr , void * (*start_routine)(void *) , void * arg) ;
返回值:若是成功建立线程返回0,否则返回错误的编号
参数:thread 要创建的线程的线程id指针
attr 创建线程时的线程属性,线程的属性将在后面的章节中描述,如果没有特殊的要求,可以直接用NULL,系统将采用默认参数
start_routine 返回值是void类型的指针函数
arg 传递start_routine 的函数参数
下面是示例:
线程函数:
void * MyThread( void* para )
{
int iCount;
iCount=*((int*)para);
printf("count=%d\r\n",iCount);
while(1)
{
sleep(iCount);
printf("onlytest\r\n");
}
pthread_exit(NULL);
}
创建线程的示例
pthread_t tid;
int count=1;
pthread_create(&tid,NULL,MyThread,&count);
1.2线程终止
void pthread_exit (void * retval );
线程通过调用pthread_exit函数终止执行,并将retval 返回给线程的创建者,在线程内部调用pthread_exit 和线程中return 的结果是一样的。
例如,在前面的MyThread函数中,调用pthread_exit(NULL);和调用return NULL;是等效的。但是如果是主线程中,这两个调用是有区别的,在主线程中调用pthread_exit(NULL);只结束当前主线程,如果调用return 则结束整个进程。
int pthread_cancel(pthread_t thread);
发送终止信号给thread线程,如果成功则返回0,否则为非0值。但发送成功并不意味着thread会终止。线程是否终止时通过cancelstate来确定的,这个state有两个值PTHREAD_CANCEL_ENABLE(缺省)和 PTHREAD_CANCEL_DISABLE,分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行。线程可以通过int pthread_setcancelstate(int state, int *oldstate)来设置。int state表示当前要设置的状态,int *oldstate用来保存之前的状态,以便于恢复。
pthread_cancel和pthread_exit 的区别在于pthread_exit 是在线程内部调用,结束当前线程,这个函数是一定可以结束当前线程。而pthread_cancel是在其他的线程中调用,结束线程id为thread,但是能否结束thread指定的线程需要取决于thread线程的属性。
1.3 等待线程终止
int pthread_join(pthread_t th, void **thread_return) ;
pthread_join()的调用者将挂起并等待th线程终止,retval是th线程调用pthread_exit()或者调用return 的返回值。
在线程的创建和终止需要注意的事项:
1 调用pthread_create创建线程的,所创建的线程并不一定会在马上执行,所以一定要注意在这段时间中传进来的参数不要改变了。例如如果在上面的例子中,我们需要创建多个MyThread线程。如果用下面的语句
int i;
pthread_t tid[8];
for(i=0;i<8;i++)
{
pthread_create(&tid[i],NULL,MyThread,&i);
}
我们期待每个线程会依次打印出count=0 count=1 count=2 .....count=7.
但是实际运行中,可能打印出来的是 count=2 count=2 count=3 ...count=8.
这是因为pthread_create后,线程并不一定马上执行,而主线程的for循环继续进行,当线程执行时,i的数值已经改变了。
所以要解决这个问题,我们需要给每个线程的参数一个独立的变量,并保证不要再其他地方修改。或者利用信号量来进行同步,保证线程已经处理了参数再继续后面的创建动作(后面将详细描述)。
2 线程创建后,要确保进程不要马上结束了,否则线程也就终止了。
例如
void * MyThread( void* para )
{
int iCount;
iCount=*((int*)para);
printf("count=%d\r\n",iCount);
while(1)
{
sleep(iCount);
printf("onlytest\r\n");
}
pthread_exit(NULL);
}
int main(int argc, char** argv)
{
int i;
int para[8];
pthread_t tid[8];
for(i=0;i<8;i++)
{
para[i]=i;
pthread_create(&tid[i],NULL,MyThread,¶[i]);
}
return 0 ;
}
这段代码运行起来,我们会看到onlytest的信息,一个也不会打印,因为这个时候main函数结束了,进程终止,线程要就被终止了。
所以如果我们在return 0的前面加上while(1){sleep(100)};或者pthread_exit(0)用return 0;(主线程终止,但是进程还在,所以我们创建的线程还是一样可以运行)。就可以看到不断有onlytest的信息出来了。
但是这样虽然可以让线程正常运行了,但是主线程并不知道线程是否结束了,线程结束的时的状态。例如如果我们的线程是不是while(1)一直循环下去,而是运行后根据某些条件退出、或者会被其他的线程发信号终止,那么主线程却不知道线程结束了。这时候我们就需要用到pthread_join。这个函数会挂起主线程,直到等待的线程结束,并且通过第二个参数带回线程的返回值。
代码如下:
int main(int argc, char** argv)
{
int i;
void *status;
int para[8];
pthread_t tid[8];
for(i=0;i<8;i++)
{
para[i]=i;
pthread_create(&tid[i],NULL,MyThread,¶[i]);
}
for(i=0;i<8;i++)
{
pthread_join(tid[i],&status);
if(status)
printf(" NO %d ThreadTest return %d",i,((int)status));
else
printf(" NO %d ThreadTest return id NULL",i);
}
return 0 ;
}
主线程就会等到线程结束再结束了。
pthread_join获得的返回值就是pthread_exit的参数,或者线程return的值(没有调用pthread_exit的话)。
例如,如果我们结束线程时调用pthread_exit(5),那么status的值就是5 (注意,不是*status的值是5)。
如果我们不关心线程的返回值,可以直接用pthread_join(tid[i],NULL);也不会有问题的。
最后需要注意的是,当pthread_join返回的时候,线程函数很可能已经结束了,这时如果线程返回的是局部变量的地址,程序很可能会崩溃。例如
void * MyThread( void* para )
{
int iCount;
int ret ;
iCount=*((int*)para);
printf("count=%d\r\n",iCount);
sleep(iCount);
printf("onlytest\r\n");
if( iCount<100)
{
ret = 0;
}
else
{
ret = 1;
}
pthread_exit((void*)(&ret));
}
这个时候。线程结束,执行到
if(status)
printf(" NO %d ThreadTest return %d",i,((int)status));
else
printf(" NO %d ThreadTest return id NULL",i);
程序就崩溃了。
3 pthread_cancel仅仅发送终止信号给thread线程,发送成功并不意味着线程会终止。 线程可以调用int pthread_setcancelstate(int state, int *oldstate) 设置本线程对Cancel信号的反应,state有两种值:pthread_CANCEL_ENABLE(缺省)和pthread_CANCEL_DISABLE,分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行;old_state如果不为NULL则存入原来的Cancel状态以便恢复。
4 在android的pthread库中没有实现 pthread_cancel的接口。所以不能调用pthread_cancel来结束线程,否则编译不过的。
那如果在android系统中我们想要结束一个线程怎么办呢?我们可以通过pthread_kill来处理,不要被名字误导,这个函数不是杀死一个线程的,而是给指定的线程发送信号的。
pthread_kill函数的原型如下:
int pthread_kill(pthread_t thread, int sig);
参数:pthread_t thread:线程id 。
int sig:要发送的信号
该函数的功能是向thread指定的线程发送信号,成功返回0。下面看看示例代码,如何结束线程。
void stopfunction(int sig)
{
printf("stopfunction call back\r\n" );
pthread_exit((void*)7);
}
void * MyThread( void* para )
{
int iCount;
struct sigaction sighandler;
memset(&sighandler,0,sizeof(struct sigaction));
sighandler.sa_handler = stopfunction;
sigaction(SIGUSR1,&sighandler,NULL);
iCount=*((int*)para);
printf("count=%d\r\n",iCount);
while(1)
{
sleep(iCount);
printf("onlytest\r\n");
}
pthread_exit(NULL);
}
int main(int argc, char** argv)
{
int i;
void *status;
int para[8];
pthread_t tid[8];
for(i=0;i<8;i++)
{
para[i]=i;
pthread_create(&tid[i],NULL,MyThread,¶[i]);
}
for(i=0;i<8;i++)
{
sleep(15);
pthread_kill(tid[i], SIGUSR1);
}
for(i=0;i<8;i++)
{
pthread_join(Id[i],&status);
if(status)
printf(" NO %d ThreadTest return %d",i,((int)status));
else
printf(" NO %d ThreadTest return id NULL",i);
}
return 0 ;
}
运行上面的程序,我们可以依次会打印8 次stopfunction call back,然后打印
NO 0 ThreadTest return 7
NO 1 ThreadTest return 7
NO 2 ThreadTest return 7
。。。。。
NO 7 ThreadTest return 7
使用pthread_kill要注意两点,第一点是一定要为创建的线程函数中注册处理函数,如果没有注册处理函数,那么整个进程退出。
第二点,注册的处理函数是对所有线程有有效的,而且对于每一个信号,处理函数是唯一的,也就是有两个线程分别注册了同一个信号的处理函数,那么后注册的将覆盖线注册的。
例如,如果我们在上面的代码中在增加一个线程
void * MyThread2( void* para )
{
while(1);
pthread_exit(0);
}
同时把main函数改为
int main(int argc, char** argv)
{
int i;
void *status;
int para[8];
pthread_t tid[8];
pthread_t tid2 ;
for(i=0;i<8;i++)
{
para[i]=i;
pthread_create(&tid[i],NULL,MyThread,¶[i]);
}
sleep(10);
pthread_create(&tid2,NULL,MyThread2,NULL);
sleep(10);
pthread_kill(tid2, SIGUSR1);
pthread_join(tid2,&status);
printf("thread2 return \r\n");
for(i=0;i<8;i++)
{
sleep(15);
pthread_kill(tid[i], SIGUSR1);
}
for(i=0;i<8;i++)
{
pthread_join(Id[i],&status);
if(status)
printf(" NO %d ThreadTest return %d",i,((int)status));
else
printf(" NO %d ThreadTest return id NULL",i);
}
return 0 ;
}
运行上面的程序,大约20秒后,我们可以看到 thread2 return的调试信息。在这里虽然MyThread2里面虽然没有注册处理函数,但是这个线程发送信号的时候,这个线程也会执行stopfunction,结束线程。
如果我们把修改MyThread2为
void stopfunction2(int sig)
{
printf("stopfunction2 run \r\n" );
pthread_exit((void*)2);
}
void * MyThread2( void* para )
{
struct sigaction sighandler;
memset(&sighandler,0,sizeof(struct sigaction));
sighandler.sa_handler = stopfunction2;
sigaction(SIGUSR1,&sighandler,NULL);
while(1);
pthread_exit(0);
}
void stopfunction(int sig)
{
printf("stopfunction call back\r\n" );
pthread_exit((void*)7);
}
void * MyThread( void* para )
{
int iCount;
struct sigaction sighandler;
memset(&sighandler,0,sizeof(struct sigaction));
sighandler.sa_handler = stopfunction;
sigaction(SIGUSR1,&sighandler,NULL);
iCount=*((int*)para);
printf("count=%d\r\n",iCount);
while(1)
{
sleep(iCount);
printf("onlytest\r\n");
}
pthread_exit(NULL);
}
int main(int argc, char** argv)
{
int i;
void *status;
int para[8];
pthread_t tid[8];
pthread_t tid2 ;
for(i=0;i<8;i++)
{
para[i]=i;
pthread_create(&tid[i],NULL,MyThread,¶[i]);
}
sleep(10);
pthread_create(&tid2,NULL,MyThread2,NULL);
sleep(10);
pthread_kill(tid2, SIGUSR1);
pthread_join(tid2,&status);
printf("thread2 return \r\n");
for(i=0;i<8;i++)
{
sleep(15);
pthread_kill(tid[i], SIGUSR1);
}
for(i=0;i<8;i++)
{
pthread_join(Id[i],&status);
if(status)
printf(" NO %d ThreadTest return %d",i,((int)status));
else
printf(" NO %d ThreadTest return id NULL",i);
}
return 0 ;
}
运行上面的代码,我们看到执行完pthread_kill,会打印“stopfunction2 run”的信息了,而不会有“stopfunction call back”的信息了。
调用pthread_join之后,status带回的返回值也变为2了。
2 线程的属性:
线程的属性是一个pthread_attr_t的结构体,该结构主要包括下面数据项:
__detachstate,表示新线程是否与进程中其他线程脱离同步,如果置位则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源。缺省为PTHREAD_CREATE_JOINABLE状态。这个属性也可以在线程创建并运行以后用pthread_detach()来设置,也可以通过pthread_attr_setdetachstate函数设置属性,然后在pthread_create的时候设置。 而一旦设置为PTHREAD_CREATE_DETACHED状态(不论是创建时设置还是运行时设置)则不能再恢复到pthread_CREATE_JOINABLE状态。
如果这个参数设置为 PTHREAD_CREATE_DETACHED ,那么就不能用pthread_join来等待线程结束了,这个时候即使线程还在正常跑,pthread_join也会直接返回。
__schedpolicy,表示新线程的调度策略,主要包括SCHED_OTHER(正常、非实时)、SCHED_RR(实时、轮转法)和SCHED_FIFO(实时、先入先出)三种,缺省为SCHED_OTHER,后两种调度策略仅对超级用户有效。运行时可以用过pthread_setschedparam()来改变。
__schedparam,一个struct sched_param结构,目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR或SCHED_FIFO)时才有效,这个参数也可以在运行时通过pthread_setschedparam()函数来改变,缺省为0。注意android中没有这个参数,而是一个sched_priority 的整形变量,所以为了保证代码的可移植性,最好不要直接给这个参数赋值,而是调用pthread_setschedparam,或者调用pthread_setschedparam来赋值。
__inheritsched,有两种值可供选择:pthread_EXPLICIT_SCHED和pthread_INHERIT_SCHED,前者表示新线程使用显式指定调度策略和调度参数(即attr中的值),而后者表示继承调用者线程的值。缺省为pthread_EXPLICIT_SCHED。 在android的pthread库中没有这个参数,直接修改线程的优先级就好了。
__scope,表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX的标准中定义了两个值:pthread_SCOPE_SYSTEM和pthread_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU时间,后者表示仅与同进程中的线程竞争CPU。目前LinuxThreads仅实现了pthread_SCOPE_SYSTEM一值。
下面详细介绍下如何设置线程的优先级:
Pthread的线程优先级主要包括两个参数,调度策略和优先级。
通过pthread_attr_setschedpolicy或者pthread_setschedparam来设置调度策略。
调度策略有三个
SCHED_FIFO: 先进先出 调度策略允许一个线程运行直到有更高优先级的线程准备好,或者直到它自愿阻塞自己。在该调度策略下,当有一个更高优先级的线程准备好时,它将立即开始执行。
SCHED_RR:轮循 调度策略,除了考虑优先级之外,还加入了时间片的限制。当一个线程执行完了一个时间片,并且有其它的SCHED_RR或者SCHED_FIFO调度策略的相同优先级的线程准备好时,运行线程将被准备好的线程抢占。
SCHED_OTHER:是分时调度策略。这个是默认的调度策略,这个策略不支持线程的优先级。所有采用SCHED_OTHER的线程优先级都是0。
优先级的设置:
对于SCHED_FIFO和SCHED_RR,通常情况下,线程的优先级是1-99,数值越大,线程的优先级越高。
对于不同调度策略的优先级,我们可以通过int sched_get_priority_max(int policy);和int sched_get_priority_min(int policy);获取该策略的优先级的取值范围。
例如:max = sched_get_priority_max(SCHED_FIFO);
min = sched_get_priority_min(SCHED_FIFO);
设置优先级可以通过
int pthread_setschedparam (pthread_t thread, int policy, const struct sched_param *__param);
或者 int pthread_attr_getschedparam (__const pthread_attr_t *attr, struct sched_param *param);
结构体sched_param我们只需要关心 sched_priority 一个成员。这个就是线程的优先级。
下面是示例:
1 在创建时设置优先级:
pthread_attr_t attr;
struct sched_param para;
pthread_t Id ;
pthread_attr_setschedpolicy(&attr,SCHED_FIFO);
pthread_attr_getschedparam(&attr, ¶);
para.sched_priority = sched_get_priority_max(SCHED_FIFO);
pthread_attr_setschedparam(&attr, ¶);
pthread_create(&Id,&attr,MyThread,NULL);
pthread_attr_destroy(&attr);
2 创建后设置优先级
int policy ;
struct sched_param para;
pthread_t Id ;
pthread_create(&Id,NULL,MyThread,NULL);
pthread_getschedparam(Id,&policy,¶);
policy = SCHED_FIFO;
para.sched_priority=sched_get_priority_max(SCHED_FIFO);
pthread_setschedparam(Id,policy,¶);
3 在线程内部设置优先级
void * MyThread( void* para )
{
int policy ;
struct sched_param para;
pthread_t Id ;
pthread_getschedparam(pthread_self(),&policy,¶);
policy = SCHED_FIFO;
para.sched_priority=sched_get_priority_max(SCHED_FIFO);
pthread_setschedparam(pthread_self(),policy,¶);
}
3 线程的互斥和同步
1 互斥锁
互斥锁是一个pthread_mutex_t的结构
互斥锁的创建,
静态创建 pthread_mutex_t mux = PTHREAD_MUTEX_INITIALIZER;
也可以动态创建
pthread_mutex_t mux;
pthread_mutex_init(&mux,NULL);
pthread_mutex_init的函数原型为int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr) ,成功返回0。
互斥锁创建后默认是开锁状态。
互斥锁的销毁:
pthread_mutex_destroy()用于注销一个互斥锁,API定义如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex)
销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。由于在Linux中,互斥锁并不占用任何资源,因此LinuxThreads中的pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作。
互斥锁的操作:
int pthread_mutex_lock(pthread_mutex_t *mutex) ;
加锁,如果锁已经被占用,将一直挂起本线程,直到锁被释放。
int pthread_mutex_unlock(pthread_mutex_t *mutex) ;
释放锁。
int pthread_mutex_trylock(pthread_mutex_t *mutex) ;
这个函数和pthread_mutex_lock的功能基本一样,但是调用这个函数时锁被占用,不会阻塞,直接返回EBUSY。
这个函数也会加锁,如果返回0的话,处理完成后一定要调用pthread_mutex_unlock释放。
int pthread_mutex_timedlock(pthread_mutex_t *mutex, const struct timespec * tv) ;
这个函数和pthread_mutex_lock的功能基本一样,但是增加了一个超时。如果等待了tv指定的时间还没有释放锁,这个函数也会返回。该函数返回0表示成功,返回ETIMEDOUT表示超时。下面是示例代码
struct timeval tv;
int err;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += 2; /* Wait 2 seconds. */
err = pthread_mutex_timedlock (&my_lock2, &ts);
if (err == 0)
{
printf("wait mutex sucess \r\n");
pthread_mutex_unlock(&my_lock2);
}
else if (err = ETIMEDOUT)
{
printf("wait mutex timeout ");
}
2 条件变量
条件变量时一个pthread_cond_t 的结构。条件变量的使用总是和一个互斥锁结合在一起。
创建条件变量
和互斥量一样条件变量也有两种创建方式:
静态方式:pthread_cond_t cond=pthread_COND_INITIALIZER ;
动态方式:采用pthread_cond_init()函数,函数申明如下:
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr) ;
下面是示例代码:
pthread_cond_t cond;
pthread_cond_init(&cond,NULL);
注销条件变量
函数申明如下:
int pthread_cond_destroy(pthread_cond_t *cond) ;
只有在没有线程在该条件变量上等待的时候才能注销这个条件变量,否则返回EBUSY。因为Linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程。
条件变量的操作:
int pthread_cond_signal (pthread_cond_t *cond);
发送一个信号给另外一个正在处于阻塞等待状态的优先级最高的线程,使其脱离阻塞状态,继续执行。假如有多个线程正在阻塞等待着这个条件变量的话,那么是根据各等待线程优先级的高低确定哪个线程接收到信号开始继续执行。如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。
int pthread_cond_broadcast (pthread_cond_t *cond);
发送一个信号给所有阻塞等待cond状态的线程,使其脱离阻塞状态,继续执行.如果没有线程处在阻塞等待状态,pthread_cond_broadcast 也会成功返回。
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) ;
挂起线程,无条件等待直到cond条件满足。在这个函数在等待的时候将释放mutex锁,当等到cond条件满足时继续执行时将给mutex加锁。
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime) ;
这个函数和pthread_cond_wait功能类似,但是多了一个等待超时。如果在设置的时间内还是没有等到cond条件满足,返回ETIMEOUT,结束等待。如果在设置时间内条件满足,函数返回0。
下面是示例代码:
pthread_mutex_t my_lock=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
void * MyThread( void* para )
{
int iCount;
pthread_mutex_lock(&my_lock);
iCount=*((int*)para);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&my_lock);
printf("NO %d started ",iCount);
。。。。。。。。
}
int main(int argc, char** argv)
{
pthread_attr_t attr;
struct sched_param para;
pthread_t Id[8];
pthread_attr_init(&attr);
pthread_attr_setschedpolicy(&attr,SCHED_FIFO);
pthread_attr_getschedparam(&attr, ¶);
para.sched_priority =sched_get_priority_max(SCHED_FIFO);
pthread_attr_setschedparam(&attr, ¶);
for(i=0;i<8;i++)
{
pthread_mutex_lock(&my_lock);
pthread_create(&(Id[i]),&attr,MyThread,(void*)&i);
pthread_cond_wait(&cond,&my_lock);
pthread_mutex_unlock(&my_lock);
}
pthread_attr_destroy(&attr);
......
}
运行上面的代码,就可以看到依次打印“NO 0 started ” “NO 1 started ”。。。。。“NO 7 started ”。
这是因为我们创建完一个线程后将在调用pthread_cond_wait,等待。这个时候线程开始执行,保存了i的数据,然后唤醒主线程,主线程继续执行将i的数值加1.
使用条件变量时一定要注意一点:
执行pthread_cond_signal产生信号后,不论是否有线程在等待,都会清除信号。也就是说如果先调用了这个函数产生信号,然后再调用pthread_cond_wait等待信号的,pthread_cond_wait将永远等不到信号,从而造成死锁。例如我们把上面的例子修为
pthread_mutex_t my_lock=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
void * MyThread( void* para )
{
int iCount;
pthread_mutex_lock(&my_lock);
iCount=*((int*)para);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&my_lock);
printf("NO %d started ",iCount);
。。。。。。。。
}
int main(int argc, char** argv)
{
pthread_attr_t attr;
struct sched_param para;
pthread_t Id[8];
pthread_attr_init(&attr);
thread_attr_setschedpolicy(&attr,SCHED_FIFO);
thread_attr_getschedparam(&attr, ¶);
para.sched_priority =sched_get_priority_max(SCHED_FIFO);
pthread_attr_setschedparam(&attr, ¶);
for(i=0;i<8;i++)
{
pthread_create(&(Id[i]),&attr,MyThread,(void*)&i);
pthread_mutex_lock(&my_lock);
pthread_cond_wait(&cond,&my_lock);
pthread_mutex_unlock(&my_lock);
}
pthread_attr_destroy(&attr);
......
}
运行上面的代码我们会发现打印了“NO 0 started”就永远不会打印“NO 1 started” “NO 2 started”。。。了
原因就是因为我们把MyThread的优先级设置为最高了,当主线程执行完pthread_create后,MyThread马上执行,MyThread保存参数的值,然后调用pthread_cond_signal产生信号,不论是否有线程等待,结束后,信号就取消。之后才轮到主线程运行pthread_cond_wait,主线程就一直在pthread_cond_wait等待,造成死锁了。
3 信号量
信号量是一个sem_t的结构体。
信号量的创建函数原型如下:
int sem_init(sem_t *sem, int pshared, unsigned int value) ;
int pshared这个参数在Linux pthread必须为0,否则函数会返回-1,value为信号量的初始值。
示例代码:
sem_t my_sem;
sem_init(&my_sem, 0, 0);
注销信号量的函数原型是
int sem_destroy(sem_t * sem) ;
被注销的信号量sem要求已没有线程在等待该信号量,否则返回-1,且置errno为EBUSY。
信号量的操作:
int sem_post(sem_t * sem) ;
调用该函数信号量加1。
int sem_wait(sem_t * sem) ;
如果信号量大于0,将信号量减1,并返回0。否则将阻塞线程,直到信号量大于0时唤醒线程,信号量减1,并返回0
int sem_trywait(sem_t * sem) ;
如果信号量大于0,将信号量减1,并返回0。否则返回-1。
int sem_getvalue(sem_t * sem, int * sval) ;
获取当前信号量的值,通过sval。注意这个函数只是获取信号量的值,不会改变信号量的数值。
信号量和条件变量的区别在于,调用sem_post信号量加1,这个信号量不会消失,直到调用sem_wait才会减1。也就是说和条件变量不同的是信号量。不论sem_post和sem_wait的调用顺序如何,都不会造成死锁。下面是示例
sem_t my_sem;
void * MyThread( void* para )
{
int iCount;
iCount=*((int*)para);
sem_post(&my_sem) ;
printf("NO %d started ",iCount);
。。。。。。。。
}
int main(int argc, char** argv)
{
pthread_attr_t attr;
struct sched_param para;
pthread_t Id[8];
pthread_attr_init(&attr);
pthread_attr_setschedpolicy(&attr,SCHED_FIFO);
pthread_attr_getschedparam(&attr, ¶);
para.sched_priority =sched_get_priority_max(SCHED_FIFO);
pthread_attr_setschedparam(&attr, ¶);
for(i=0;i<8;i++)
{
pthread_create(&(Id[i]),&attr,MyThread,(void*)&i);
sem_wait(&my_sem);
}
pthread_attr_destroy(&attr);
......
}
运行上面的代码,就可以看到依次打印“NO 0 started ” “NO 1 started ”。。。。。“NO 7 started ”。
4 线程终止时的清理
在线程运行过程,会占用一些资源,需要在线程终止的时候释放。对于线程主动调用pthread_exit()或者从线程函数中return都将使线程正常退出,这是可预见的退出方式,这样很简单,我们直接在调用之前做一些清理工作就好了。但是实际的应用中还有许多不可预见的终止,例如线程被其他的线程终止,线程由于自身的非法操作而退出,对于不可预见的退出我们如何清理资源呢?
在pthread库中,已经为我们提供了pthread_cleanup_push()/pthread_cleanup_pop()来实现资源的清理。
pthread_cleanup_push()/pthread_cleanup_pop()是以宏的形式定义的,宏定义如下:
#define pthread_cleanup_push(routine, arg) \
do { \
__pthread_cleanup_t __cleanup; \
__pthread_cleanup_push( &__cleanup, (routine), (arg) ); \
#define pthread_cleanup_pop(execute) \
__pthread_cleanup_pop( &__cleanup, (execute)); \
} while (0);
pthread_cleanup_push 的参数 routine是我们要调用的清理函数的指针,清理函数的原型为 void cleanup_handler(void *arg),
pthread_cleanup_push 的参数 arg是一个void*类型的指针,是传递给 routine的参数。
pthread_cleanup_pop 的参数 execute表示是否代码运行到 pthread_cleanup_pop是否调用routine函数做清理。0为不调用routine函数,非0为调用。注意这个参数只对正常结束的线程时有效,而不论这个参数是0还是非0 ,函数非正常终止时都会调用routine函数做清理。
pthread_cleanup_push()带有一个"{",而pthread_cleanup_pop()带有一个"}",因此这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能通过编译。
下面看看示例代码
pthread_mutex_t my_lock=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
static void cleanup_handler(void *arg)
{
int no=*((int*)arg);
printf("Called clean-up handler para=%d\n",no);
}
void stopfunction(int sig)
{
printf("sighandler call back\n");
pthread_exit((void*)7);
}
void * MyThread( void* para )
{
int iRun;
struct sigaction sighandler;
pthread_cleanup_push(cleanup_handler, &iRun );
iRun =1;
memset(&sighandler,0,sizeof(struct sigaction));
sighandler.sa_handler = stopfunction;
sigaction(SIGUSR1,&sighandler,NULL);
pthread_mutex_lock(&my_lock);
pthread_cond_wait(&cond,&my_lock);
pthread_mutex_unlock(&my_lock);
printf("thread exit \n" );
iRun =0;
pthread_cleanup_pop(1);
pthread_exit(NULL);
}
int main(int argc, char** argv)
{
void * status;
pthread_t Id ;
pthread_create(&Id,NULL,MyThread,NULL);
sleep(20);
pthread_mutex_lock(&my_lock);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&my_lock);
pthread_join(Id,&status);
printf(" ThreadTest return %d\n",((int)status));
}
我们运行上面的代码,可以看到下面的调试信息
thread exit
Called clean-up handler para=0
ThreadTest return 0
可以看到线程正常终止,调用cleanup_handler,此时参数的值为0。
如果我们修改下main函数的代码为
int main(int argc, char** argv)
{
void * status;
pthread_t Id ;
pthread_create(&Id,NULL,MyThread,NULL);
sleep(20);
pthread_kill(Id, SIGUSR1);
pthread_join(Id,&status);
printf(" ThreadTest return %d\n",((int)status));
}
运行上面的代码,调试信息如下:
sighandler call back
Called clean-up handler para 1
ThreadTest return 7
可见线程没有正常终止,而是在等待条件变量的时候被响应SIGUSR1信号终止的,cleanup_handler一样被调用了,但是这时候iRun没有被置0。
5 其他的一些函数
1 如何获取本线程的ID
在线程运行过程中可能我们需要知道本线程的ID,完成对本线程参数的一些设置,我们可以通过pthread_self来获取,函数原型如下
pthread_t pthread_self(void) ;
例如
pthread_t myId = pthread_self() ;
2 仅执行一次的操作
pthread库提供了下面的函数,保证init_routine在进程中只能被执行一次
int pthread_once(pthread_once_t *once_control, void (*init_routine) (void)) ;
如果我们在我们的进程中创建了很多线程,对一个资源进行操作,我们只希望对该资源初始化一次,那就可以通过pthread_once来实现了。
下面是示例代码:
pthread_once_t once=PTHREAD_ONCE_INIT;
pthread_mutex_t my_lock=PTHREAD_MUTEX_INITIALIZER;
int count;
void once_run(void)
{
count = 100;
printf("init count is 100 \n");
}
void * MyThread( void* para )
{
pthread_once(&once,once_run);
pthread_mutex_lock(&my_lock);
count++;
printf("count=%d\n",count);
pthread_mutex_unlock(&my_lock);
pthread_exit(NULL);
}
int main(int argc, char** argv)
{
pthread_t Id[5];
int i;
for(i=0;i<5;i++)
{
pthread_create(&(Id[i]),NULL,MyThread,NULL);
}
for(i=0;i<5;i++)
{
pthread_join(Id[i],NULL);
}
pthread_mutex_destroy(&my_lock);
}
运行上面的代码,我们可以看到下面的调试信息
init count is 100
count=101
count=102
count=103
count=104
count=105
3 如何判断一个线程是否还在执行
在开发过程中有时我们需要知道线程是否在运行,如果线程终止了可能就需要我们做一些补救,那么我们如何判断线程还在运行呢?
pthread_kill就可以实现这个功能,函数原型如下
int pthread_kill(pthread_t thread, int sig);
功能是向thread指定的线程发送sig信号 。注意:如果线程代码内不做处理,则按照信号默认的行为影响整个进程,也就是说,如果你给一个线程发送了SIGQUIT,但线程却没有实现signal处理函数,则整个进程退出。
我们前面曾经用这个函数终止线程,同样的我们可以用这个函数判断线程是否在运行。
linux保留了一个信号值为0 的信号,我们可以通过向线程发送信号0 ,来判断线程是否还在运行。
pthread_kill的返回值
成功:0
线程不存在:ESRCH
信号不合法:EINVAL
下面是示例代码:
int ret = pthread_kill(thread_id,0);
if(ret == ESRCH)
printf("the thread did not exists or already quit\n");
else if(ret == EINVAL)
printf("signal is invalid\n");
else
printf("the thread is alive\n");