线程的生命周期
1、当c程序运行时,首先运行main函数。在线程代码中,这个特殊的执行流被称作初始线程或者主线程。你可以在初始线程中做任何普通线程可以做的事情。
2、主线程的特殊性在于,它在main函数返回的时候,会导致进程结束,进程内所有的线程也将会结束。这可不是一个好的现象,你可以在主线程中调用pthread_exit函数,这样进程就会等待所有线程结束时才终止。
3、主线程接受参数的方式是通过argc和argv,而普通的线程只有一个参数void*
int main(int argc, char const *argv[])
4、在绝大多数情况下,主线程在默认堆栈上运行,这个堆栈可以增长到足够的长度。而普通线程的堆栈是受限制的,一旦溢出就会产生错误
线程的四个基本状态
- 就绪:当线程刚被创建时就处于就绪状态,或者当线程被解除阻塞以后也会处于就绪状态。就绪的线程在等待一个可用的处理器,当一个运行的线程被抢占时,它立刻又回到就绪状态
- 运行:当处理器选中一个就绪的线程执行时,它立刻变成运行状态
- 阻塞:线程会在以下情况下发生阻塞:试图加锁一个已经被锁住的互斥量,等待某个条件变量,调用singwait等待尚未发生的信号,执行无法完成的I/O信号,由于内存页错误
- 终止:线程通常启动函数中返回来终止自己,或者调用pthread_exit退出,或者取消线程
线程基本控制
- 线程终止:
- 如果进程中的任意一个线程调用了exit,_Exit,_exit,那么整个进程就会终止
- 普通的单个线程有一下3中方式退出,这样不会终止进程
(1)从启动例程中返回,返回值是线程的退出码
(2)线程可以被同一进程中的其他线程取消
(3)线程调用pthread_exit(void *rval)函数,rval是退出码
- 线程连接:
- int pthread_join(pthead_t tid, void **rval)
调用该函数的线程会一直阻塞,直到指定的线程tid调用pthread_exit、从启动例程返回或者被取消
参数tid就是指定线程的id
参数rval是指定线程的返回码,如果线程被取消,那么rval被置为PTHREAD_CANCELED
该函数调用成功会返回0,失败返回错误码
为什麼要连接线程:如果不连接线程,会导致线程资源无法释放,也不能被复用,造成资源的泄露。还有就是利用连接使用线程退出的返回值,得知退出状态 - 调用pthread_join会使指定的线程处于分离状态,如果指定线程已经处于分离状态,那么调用就会失败
pthread_detach可以分离一个线程,线程可以自己分离自己
int pthread_detach(pthread_t thread);
成功返回0,失败返回错误码
为什么需要分离:在默认的情况下,新创建的线程处于可连接状态,线程退出后需要对它进行连接操作,否则造成资源泄露,如果其他线程并不关心线程的还回值,进行连接就是一种负担,因此有了让线程退出后,系统进行给我们释放线程的资源,无需进行连接释放,这就是线程分离的作用
- int pthread_join(pthead_t tid, void **rval)
线程取消:
int pthread_cancel(pthread_t tid)
取消tid指定的线程,成功返回0。但是取消只是发送一个请求,并不意味着等待线程终止,而且发送成功也不意味着tid一定会终止取消状态,就是线程对取消信号的处理方式,忽略或者响应。线程创建时默认响应取消信号int pthread_setcancelstate(int state, int *oldstate)
设置本线程对Cancel信号的反应,state有两种值:PTHREAD_CANCEL_ENABLE(缺省)PTHREAD_CANCEL_DISABLE,
分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行;old_state如果不为NULL则存入原来的Cancel状态以便恢复- 取消类型:取消类型,是线程对取消信号的响应方式,立即取消或者延时取消。线程创建时默认延时取消
int pthread_setcanceltype(int type, int *oldtype)
设置本线程取消动作的执行时机,type由两种取值:PTHREAD_CANCEL_DEFFERED和PTHREAD_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和立即执行取消动作(退出);oldtype如果不为NULL则存入运来的取消动作类型值。 - 取消点:取消一个线程,它通常需要被取消线程的配合。线程在很多时候会查看自己是否有取消请求
如果有就主动退出, 这些查看是否有取消的地方称为取消点
很多地方都是包含取消点,包括
pthread_join()、 pthread_testcancel()、pthread_cond_wait()、 pthread_cond_timedwait()、sem_wait()、sigwait()、write、read,大多数会阻塞的系统调用,在linux中man pthreads查看取消点函数。
实例
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
void * pthread_func(void *arg)
{
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);//set pthread's state ,can't cancel
printf("i am new pthread\n");
sleep(4);
printf("set the new pthread be can cancel\n");
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
//pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);//情况二:第二次也设置为线程不能被取消。
//pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);//情况三:将这里取掉注销,就变成立即取消
printf("i am the first point of cancel\n");
printf("i am the second point of cancel\n");
return (void *)0;//如果是正常的退出,还回值为0
}
int main()
{
pthread_t pid;
int err;
void *val;//new pthread return value
err=pthread_create(&pid,NULL,pthread_func,NULL);//create a new pthread
if(err!=0)
{
printf("create new pthread failed\n");
return -1;
}
sleep(2);
pthread_cancel(pid);//取消新线程
pthread_join(pid,&val);//join the new pthread,nutil new pthread over;
printf("new pthread return val=%d\n",(int *)val);//打印0,正常退出,打印为-1时,说明新线程是被取消退出的。
return 0;
}
1、
2、把情况二处注释取掉
3、把情况二处注释、情况三处注释取掉
- 向线程发送信号:int pthread_kill(pthread_t thread, int sig);
是向线程发送signal。大部分signal的默认动作是终止进程的运行,所以,我们才要用sigaction()去抓信号并加上处理函数。
向指定ID的线程发送sig信号,如果线程代码内不做处理,则按照信号默认的行为影响整个进程,也就是说,如果你给一个线程发送了SIGQUIT,但线程却没有实现signal处理函数,则整个进程退出。
如果要获得正确的行为,就需要在线程内实现sigaction了。
所以,如果int sig的参数不是0,那一定要清楚到底要干什么,而且一定要实现线程的信号处理函数,否则,就会影响整个进程。
如果int sig是0呢,这是一个保留信号,其实并没有发送信号,作用是用来判断线程是不是还活着。 - 清除操作:线程可以安排它退出时的清理操作,这与进程的可以用atexit函数安排进程退出时需要调用的函数类似。这样的函数称为线程清理处理程序。线程可以建立多个清理处理程序,处理程序记录在栈中,所以这些处理程序执行的顺序与他们注册的顺序相反
pthread_cleanup_push(void (rtn)(void), void *args)//注册处理程序
pthread_cleanup_pop(int excute)//清除处理程序
当执行以下操作时调用清理函数,清理函数的参数由args传入
1、调用pthread_exit
2、响应取消请求
3、用非零参数调用pthread_cleanup_pop
练习
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
void clean_first(void *arg)
{
printf("%s first clean\n",(char *)arg );
}
void clean_second(void *arg)
{
printf("%s second clean\n", (char *)arg);
}
void *pthread_func1(void *arg)
{
printf("this is new pthread1\n");
pthread_cleanup_push(clean_first,"pthread1");
pthread_cleanup_push(clean_second,"pthread1");
pthread_cleanup_pop(1);
pthread_cleanup_pop(0);
return (void *)1;
}
void *pthread_func2(void *arg)
{
printf("this is new pthread2\n");
pthread_cleanup_push(clean_first,"pthread2");
pthread_cleanup_push(clean_second,"pthread2");
pthread_exit((void *)2);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
}
int main()
{
pthread_t pid;
int err;
err = pthread_create(&pid,NULL,pthread_func1,NULL);
if(err!=0)
{
printf("create first new pthread failed\n");
return -1;
}
err = pthread_create(&pid,NULL,pthread_func2,NULL);
if(err!=0)
{
printf("create second new pthread failed\n");
return -2;
}
sleep(5);
return 0;
}
结果: