线程的概念
线程的创建:pthread_create
线程的退出: pthread_exit
线程的等待退出(回收):pthread_join
线程取消:pthread_cancel
线程终止清理函数:pthread_cleanup_push、pthread_cleanup_pop
一、线程的概念
进程从创建到运行需要700ms。线程的创建速度是100us,快了近1000倍。
进程是系统中程序执行和资源分配的基本单位,每个进程有0-4G的空间。
为了进一步减少处理器的空转时间支持多处理器和减少上下文切换开销,也就出现了线程。处理器空转:没有在执行业务进程。
线程为了更好的利用资源。
一个进程里有多个线程,线程依赖于进程。
多线程并发执行。使用同一个0-4G。
线程只拥有自己的栈空间。每个函数要有自己的函数栈。只有有了自己的函数栈,才能有独立的执行路径。
线程独有:线程控制表+栈寄存器(栈空间)。
一个线程崩溃了,其他线程都要崩溃。凡是线程的异常都会映射到进程中。都对其他线程造成影响。
linux线程依赖于nptl线程库,是posix标准。这是大家的共识。对于linux来说,现在只有这一套接口。
线程:主线程、子线程。不叫父线程。
创建 退出 等待
多进程 fork() exit() wait()
多线程 pthread_create pthread_exit() pthread_join()
int pthread_create(pthread_t*
thread, pthread_attr_t * attr, void *(*
start_routine)(void *), void *
arg);
//thread为传出参数,创建好了之后返回
新线程的标识,保存到thread中。
attr是一个结构体指针,表示线程属性,通常
为NULL。start_routine是一个函数指针。指向子线程的入口函数,这个函数的
开头和结尾就是线程的开头和结尾,该函数传入的参数是void * 类型(传入的
其实就是后面的参数arg),返回类型也是void *。arg用于给前面的函数指针传
参数,若为NULL,表示不传递。
void pthread_exit(void *retval);
//退出线程。一般在子线程中使用。因为start_routine指向的子线程的返回值类
型是void *类型,所以return的类型也是void * 类型。
int pthread_join(pthread_t th, void **thread_return);
//线程的等待退出。
int pthread_cancel(pthread_t thread);
//线程的取消
二、线程的创建:pthread_create
例1:线程的创建
void* thread(void* p){
//线程的开始
printf("I am child thread\n");
while(1);
}
//线程的结束,到这句话执行完,线程就执行完了。
int main(){
pthread_t th_id;
int ret=
pthread_create(
&th_id,NULL,
thread,NULL);
//创建线程thread
if(ret!=0){
printf("pthread_create failed ret=%d\n",ret);
//注意:
在线程库里,不能使用perror函数查看错误。
原理是:原有的能用perror的 函数,在函数失败以后,修改全局变量errno的值为错误码。调用perror函数就是 读取errno,显示错误。但是pthread_creat函数返回的错误码,并没有修改全局变量 errno。有以下三种方法处理这个问题:a.直接打印出错误码,自己去查看错误。 b.先把错误码赋给errno,然后再调用perror。c.也可以自己实现perror函数打印。
return -1;
}
while(1);
//注意:这个while(1)要写上,否则线程还没怎么执行,主线程就退出了。而进程的 资源
依赖于主线程,如文件描述符0,1,2。主线程退出,文件描述符关闭,进程
地址空间清理,进程就没了。
return 0;
}
编译:gcc pthread.c -lpthread //编译的时候要加-lpthread.显式的写出来。gcc pehtead.c -lpthread
查看线程:ps -elLf //
proc下面每一个文件夹是一个进程。进到进程文件夹中,可以看到线程
例2:创建线程时,传入的参数是整型数据。
void* thread(void* p){
//p的内容就是5
int i=
(int)p;
//强制转换为整型
printf("I am child thread %d\n",i);
while(1);
}
int main(){
pthread_t th_id;
int ret=pthread_create(&th_id,NULL,thread,
(void*)5);
//创建线程,将数值放入指针内
if(ret!=0){
printf("pthread_create failed ret=%d\n",ret);
return -1;
}
while(1);
return 0;
}
例3:
创建线程,传递指针数据
void* thread(void* p){
//p指向malloc出来的内存
strcpy((char*)p,"hello");
printf("I am child thread\n");
printf("child p is %p\n",p);
return
(void*)0;
}
int main(){
pthread_t th_id;
void* p=malloc(20);
//malloc返回的本身就是void * 类型
printf("p is
%p\n",p);
//打印p的地址
int ret=pthread_create(&th_id,NULL,thread,
p);
//传递一个指针,就相当于传递了整个是世界!
if(ret!=0){
printf("pthread_create failed ret=%d\n",ret);
return -1;
}
sleep(1);
printf("main thread %s\n",
(char*)p);
//要转化成字符串指针,才能打印字符串。
return 0;
}
三、线程的退出:pthread_exit
比如说线程进了很多层,return 退出有时候很痛苦。如果用exit:会让整个进程都退出了。
使用pthread_exit退出线程。
例:
void* thread(void* p){
strcpy((char*)p,"hello");
printf("I am child thread\n");
printf("child p is %p\n",p);
pthread_exit(
(void*)0);
//通过pthread_exit实现线程退出,没有返回值,就写pthread_exit(NULL);
}
int main(){
pthread_t th_id;
void* p=malloc(20);
printf("p is %p\n",p);
int ret=pthread_create(&th_id,NULL,thread,p);
if(ret!=0){
printf("pthread_create failed ret=%d\n",ret);
return -1;
}
sleep(1);
printf("main thread %s\n",(char*)p);
return 0;
}
四、线程的等待退出:接线程的返回值,回收资源
int pthread_join(pthread_t th,
void **thread_return);
主线程可以等待子线程,子线程也可以等其他的子线程。子线程等待主线程没有意义,因为主线程完了进程就完了。
pthread_join跟wait类似,用来打扫战场的,回收子线程的资源的。join回收的线程控制表。
void * p //万能指针。p是没法+1的。
例1:
void* thread(void* p){
//p指向malloc出来的内存。p是void *类型。
strcpy((char*)p,"hello");
printf("I am child thread\n");
printf("child p is %p\n",p);
//打印地址
pthread_exit(
p);
//返回void *类型的p
}
int main(){
pthread_t th_id;
void* p=malloc(20);
printf("p is %p\n",p);
int ret=pthread_create(&th_id,NULL,thread,
p);
if(ret!=0){
printf("pthread_create failed ret=%d\n",ret);
return -1;
}
void* p1;
//想用p1接收传出的值。线程中p是void * 类型,所以p1也定义为void * 类型。
ret=
pthread_join(th_id,
&p1);
//想要改变p1的值,所以传入p1的地址。写pthread_join(th_id,NULL)表示不获取
返回值。
if(ret!=0){
printf("pthread_join failed ret=%d\n",ret);
return -1;
}
printf("main thread %s\n",(char*)p);
printf("main thread p1=%p\n",p1);
return 0;
}
例2:
void * thread(void *p){
//p指向malloc出来的内存。p是void *类型。
strcpy((char*)p,"hello");
printf("I am child thread\n");
printf("child p is %p\n",p);
pthread_exit(
(void *)5);
//5转为void * 类型。
}
int main(){
pthread_t th_id;
void *p=malloc(20);
printf("p is %p\n",p);
int ret=prhtrea_create(&th_id,NULL,thread,
p);
if(ret!=0){
printf("phtread_create failed ret=%d\n",ret);
return -1;
}
int i;
//写法一:我想用i来接受(void *)5,又希望改变i的值,传入的是&i。因为5是 void
*类型,
所以i转化为void *类型。所以&i是(void **)类型。
join函
数为:
pthread_join(th_id,void **p); 这是p就等于i的地址,*p就是i。(*p)是void*类
型。
然后*p=(void*)5。
ret=pthread_join(th_id,
(void**)&i);
//希望改变i的值,所以传入&i。i是void *类型,所以&i 是void ** 类型。
// void *a;
// ret=pthread_join(th_id,&a);
//写法二:这种写法好理解,要接受void * 类型,我就定义void * 类型。然后把
地
址传入,
在pthread_join函数中a解引用就是void *类型。*a=(void *)5。
if(ret!=0){
printf("pthread_join failed");
return -1;
}
printf("main thread i=%d\n",
i);
printf("main thread a=%d\n",
(int)a);
return 0;
}
1.如何让线程结束。
2.线程非正常结束后的资源释放
时序控制:同步与互斥
五、线程的取消:
int pthread_cancel(pthread_t thread);
//线程的取消
例:
void* thread(void* p){
printf("I am child\n");
char buf[128]={0};
read(0,buf,sizeof(buf));
//read读标准输入的时候会阻塞。
printf("after read\n");
pthread_exit(
(void*)5);
}
pthread_t pth_id;
pthread_create(&pth_id,NULL,thread,NULL);
int ret;
ret=pthread_cancel(pth_id);
//发信号,让线程提前结束。如果不写这一句,即不取消子线程,子线程会在read
的
时候卡主,而主线程会在join的时候一直等着子线程返回。而写了这句之后。 cancel一定放在join之前,否则没有意义。
if(ret!=0){
printf("pthread_cancel failed ret=%d\n",ret);
return -1;
}
void* p;
ret=
pthread_join
(pth_id,&p);
//如果不写cancel,线程正常结束,拿到的是5。 在写了cancel 之后,线程不是正
常结束,也可以拿到线程的返回值,得到的p不再是5。非正常结束,join还是要清
理线程控制表。
if(ret!=0){
printf("pthread_join failed ret=%d\n",ret);
return -1;
}
printf("main thread p=%d\n",(int)p);
return 0;
}
所有线程共享012等文件描述符。
六、线程终止清理函数。 相当于异步的信号处理函数
子线程申请了堆空间。在子线程结束后,malloc的内存依然可以使用。子线程结束后,虽然可以被pthread_join函数清理掉线程控制表等资源,但是malloc的内存不一定就释放了。因为取消线程是异步的,不一定在什么时候就取消线程了,这时候不一定来得及free。所以提供了一个线程终止清理函数。相当于临终遗言。在cancel前,释放malloc和锁资源。一旦cancel之后先去清理函数栈中拿出函数来执行。
void pthread_cleanup_
push(void (*routine) (void *), void *arg); //将清理函数压到“退出清理函数栈”。清理函数都在栈里面。当cancel 时,会倒清理函数栈中,弹出清理函数,执行。
void pthread_cleanup_
pop(int execute);
例1:子线程malloc内存
void* thread(void* p){
p=malloc(20);
//子线程malloc空间,p是void * 类型。
strcpy((char*)p,"hello");
printf("I am child thread\n");
printf("child p is %p\n",p);
return (void*)p;
}
int main(){
pthread_t th_id;
int ret=pthread_create(&th_id,NULL,thread,NULL);
if(ret!=0){
printf("pthread_create failed ret=%d\n",ret);
return -1;
}
char* p;
ret=pthread_join(th_id,(void**)&p);
//等待子线程退出,清理线程控制表。
if(ret!=0){
printf("pthread_join failed ret=%d\n",ret);
return -1;
}
printf("p is %s\n",p);
//子线程的线程控制表清理之后,子线程malloc的空间可以继续使用。
return 0;
}
例2:子线程被cancel后,能够执行线程清理函数
void
clean1(void* p){
printf("I am clean func %d\n",(int)p);
}
void* thread(void* p){
pthread_cleanup_push(clean1,(void*)1);
//将线程终止清理函数clean1,压栈。一上来就写线程终止清理函数,防止一
上
来就背cancel掉。
printf("I am child\n");
char buf[128]={0};
read(0,buf,sizeof(buf));
printf("after read\n");
pthread_exit((void*)5);
pthread_cleanup_pop(0); //push和pop必须成对出现。如果不成对出现,编译不了。pop一般写在exit 之后,因为执行exit函数时,会先执行线程清理函数,所以写pop(0)和pop(1) 没区别。
}
int main(){
pthread_t pth_id;
pthread_create(&pth_id,NULL,thread,NULL);
int ret;
ret=
pthread_cancel(pth_id);
//取消子线程
if(ret!=0){
printf("pthread_cancel failed ret=%d\n",ret);
return -1;
}
void* p;
ret=
pthread_join(pth_id,&p);
if(ret!=0){
printf("pthread_join failed ret=%d\n",ret);
return -1;
}
printf("main thread p=%d\n",(int)p);
return 0;
}
程序运行结果:
I am child
//首先创建子线程,子线程运行,打印出第一句。
I am clean func 1
//然后主线程cancel子线程,去执行线程终止清理函数,打印出第二句。
main thread p=-1
//子线程非正常退出,返回值为-1,被join函数接收,然后释放线程控制表。
例3:子线程正常退出(return或exit)时,也会执行线程清理函数。
void
clean1(void* p){
printf("I am clean func %d\n",(int)p);
}
void* thread(void* p){
pthread_cleanup_push(clean1,(void*)1);
printf("I am child\n");
char buf[128]={0};
read(0,buf,sizeof(buf));
//手动输入值,让线程正常退出
printf("after read\n");
pthread_exit((void*)5);
//线程不被cancel,当正常退出时,也会执行线程清理函数。
pthread_cleanup_pop(0);
}
int main(){
pthread_t pth_id;
pthread_create(&pth_id,NULL,thread,NULL);
int ret;
void* p;
ret=pthread_join(pth_id,&p);
if(ret!=0){
printf("pthread_join failed ret=%d\n",ret);
return -1;
}
printf("main thread p=%d\n",(int)p);
return 0;
}
例4:子线程被cancel后,能够执行线程清理函数,清理函数进行堆内存free
void clean1(void* p){
printf("I am clean,%s\n",(char*)p);
free(p);
//因为cancel和退出都会执行清理函数,
所以free写在清理函数中。
}
void* thread(void* p){
p=malloc(20);
pthread_cleanup_push(clean1,p);
//压栈
printf("I am child\n");
strcpy((char*)p,"hello");
char buf[128]={0};
read(0,buf,sizeof(buf));
printf("after read\n");
pthread_
exit((void*)5);
pthread_cleanup_
pop(0);
}
int main(){
pthread_t pth_id;
pthread_create(&pth_id,NULL,thread,NULL);
int ret;
sleep(2);
//睡一下,否则子线程来不及strcpy
ret=
pthread_cancel(pth_id);
//取消子线程
if(ret!=0){
printf("pthread_cancel failed ret=%d\n",ret);
return -1;
}
void* p;
ret=pthread_
join(pth_id,&p);//不获取子线程的返回值
if(ret!=0){
printf("pthread_join failed ret=%d\n",ret);
return -1;
}
printf("main thread p=%d\n",(int)p);
return 0;
}
例5:注意压栈和出栈的顺序,先压栈的后执行。
void clean1(void* p){
printf("I am clean func %d\n",(int)p);
}
void* thread(void* p){
pthread_cleanup_push(clean1,(void*)1);
//先压栈,后执行
pthread_cleanup_push(clean1,(void*)2);
//后压栈,先执行
printf("I am child\n");
char buf[128]={0};
read(0,buf,sizeof(buf));
printf("after read\n");
pthread_exit((void*)5);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
}
int main(){
pthread_t pth_id;
pthread_create(&pth_id,NULL,thread,NULL);
int ret;
ret=
pthread_cancel(pth_id);
if(ret!=0){
printf("pthread_cancel failed ret=%d\n",ret);
return -1;
}
void* p;
ret=pthread_
join(pth_id,&p);
if(ret!=0){
printf("pthread_join failed ret=%d\n",ret);
return -1;
}
printf("main thread p=%d\n",(int)p);
return 0;
}