目录
1. 线程控制语句
1.1pthread_exit函数
1.2pthread_join函数
1.3pthread_detach函数
1.4pthread_cancel函数
控制原语对比
2. 线程属性
2.1线程属性初始化
2.2线程的分离状态
2.3线程使用注意事项
1. 线程控制语句
1.1pthread_exit函数
将单个当前线程退出
void pthread_exit ( void * retval )参数:retval表示线程退出状态,通常传 NULL
结论: 线程中,禁止使用exit 函数,会导致进程内所有线程全部退出 。
在不添加 sleep 控制输出顺序的情况下。 pthread_create 在循环中,几乎瞬间创建 5 个线程,但只有第 1个线程有机会输出( 或者第 2 个也有,也可能没有,取决于内核调度 ) 如果第 3 个线程执行了 exit ,将整个进程退出了,所以全部线程退出了
所以,多线程环境中,应尽量少用,或者不使用exit 函数,取而代之使用 pthread_exit 函数,将单个线程退出。任何线程里 exit 导致进程退出 ,其他线程未工作结束,主控线程退出时不能 return 或 exit 。
另注意, pthread_exit 或者 return 返回的指针所指向的内存单元必须是全局的或者是用 malloc 分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
编写多线程程序,总結 exit 、 return 、 pthread_exit 各自退出效果。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <pthread.h>void sys_err(const char *str)
{perror(str);exit(1);
}void fun(void)
{//return NULL;pthread_exit(NULL);//表示退出当前线程
}void *tfn(void *arg)
{int i=(int)arg;printf("--I am %dth thread pid=%d, tid=%lu\n",i+1,getpid(),pthread_self());if(i==2){// exit(0);//表示退出进程//return NULL;//表示返回到函数调用者那里去fun();}
}int main()
{int i;int ret;pthread_t tid;for(i=0;i<5;i++){ret=pthread_create(&tid,NULL,tfn,(void *)i);if(ret!=0){sys_err("pthread_create() err");}sleep(1);}sleep(i);printf("pthread_create finish!\n");return 0;
}
return: 返回到调用者那里去pthread_exit(): 将调用该函数的线程退出exit: 将进程退出
1.2pthread_join函数
阻塞等待线程退出,获取线程退出状态,其作用对应进程中wait()函数
int pthread_join ( pthread_t thread , void ** retval );返回值:成功 : 0 ;失败 : 错误号。参数 1 : 传入 thread : 线程 ID ([ 注意 ] 不是指针 );参数 2 : retval : 存储线程结束状态。
对比记忆
进程中:main返回值、 exit 参数 -->int ;等待子进程结束 wait 函数参数 - ->int *线程中:线程主函数返回值、pthread_exit->void*; 等待线程结束 pthread_join 函数参数 - ->void **
参数retval 非空用法
例程:子线程完成一个变量空间开辟,并返回初始化结果
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<pthread.h>void sys_err(const char *str)
{perror(str);exit(1);
}void *tfn(void *arg)
{int *pval;pval=malloc(sizeof(int));*pval=10000; return (void *)pval;
}int main()
{pthread_t tid;int *retval;int ret=pthread_create(&tid,NULL,tfn,NULL);if(ret!=0){sys_err("pthread_create err");}ret =pthread_join(tid,(void **)&retval);if(ret!=0)sys_err("pthread_join err");printf("child thread exit with ret =%d,val=%d\n",ret,*retval);pthread_exit(NULL);free(retval); retval =NULL;return 0;
}
例程:子线程完成一个字符串空间开辟,并返回初始化结果hello
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <pthread.h>void sys_err(const char *str)
{perror(str);exit(1);
}void *tfn(void *arg)
{char *pval;pval=malloc(sizeof(char)*10);memset(pval,0,10);strcpy(pval,"hello");pthread_exit(100);
}int main()
{pthread_t tid;char *retval;int ret=pthread_create(&tid,NULL,tfn,NULL);if(ret != 0){sys_err("pthread_create err");}
//pthread_join等待回收子线程
//参1:tid 需要回收的线程id
//参2:需要回收的线程的返回值(是个地址)
//返回值:成功0,失败返回错误码ret= pthread_join(tid,(void **)&retval);if(ret != 0)sys_err("pthread_join err");printf("child thread exit with val=%d\n",retval);while(1); //其他业务功能
// pthread_exit(NULL);return 0;
}
调用该函数的线程将挂起等待,直到 id 为 thread 的线程终止 . thread 线程以不同的方法终止 , 通过
pthread_ join 得到的终止状态是不同的
总结如下 :1. 如果 thread 线程通过 return 返回, retval 所指向的单元里存放的是 thread 线程函数的返回值2. 如果 thread 线程是自己调用 pthread_exit 终止的, retval 所指向的单元存放的是传给 pthread_ exit 的参数。3. 如果 thread 线程被别的线程调用 pthread_cancel. 异常终止掉, retval 所指向的单元里存放的是常数PTHREAD_CANCELED4. 如果对 thread 线程的终止状态不感兴趣,可以传 NULL 给 retval 参数
1.3pthread_detach函数
实现线程分离
int pthread_detach(pthread_t thread);
返回值:成功 : 0 ;失败 : 错误号参数: thread 需要分离的线程 id
线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后,其退出状态不由其他线程获取,而直接自己自动释放。网络、多线程服务器常用。
进程若有该机制,将不会产生僵尸进程。僵尸进程的产生主要由于进程死后,大部分资源被释放,一点残留资源仍存于系统中,导致内核认为该进程仍存在。
也可使用pthread_create 函数参 2( 线程属性 ) 来设置线程分离,后面会讲到。
例程:使用 pthread_detach 函数实现线程分离
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>void *tfn(void *arg)
{printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());return NULL ;
}int main(void)
{pthread_t tid;int ret = pthread_create(&tid, NULL, tfn, NULL);if(ret!=0){perror("pthread_create error");exit(1);}ret = pthread_detach(tid);//设置线程分离,线程终止、会自动清理pcb,无需回收if(ret!=0){perror("pthread_detach error");}ret=pthread_join(tid, NULL);printf("thread_join() ret = %d\n",ret);if(ret!=0){perror("thread_join error");}printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());return 0;
}
一般情况下,线程终止后,其终止状态一直保留到其它线程调用 pthread_join 获取它的状态为止。但是线程也可以被置为detach 状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。
不能对一个已经处于 detach 状态的线程调用 pthread_join, 这样的调用将返回错误。也就是说,如果已经对一个线程调用了pthread_detach 就不能再调用 pthread_join 了。
1.4pthread_cancel函数
杀死 ( 取消 ) 一个线程,需要取消点 [ 其作用对应进程中 kill() 函数 ]
int pthread_cancel ( pthread_t thread );返回值:成功:0 ;失败:错误号。参数: thread 待杀死的线程 id
[注意]
线程的取消并不是实时的,而有一定的延时。需要等待线程到达某个取消点 ( 检查点 ) 。
取消点,是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用 creat, open,pause,close, read, write.... 执行命令 man 7 pthreads 可以查看具备这些取消点的系统调用列表。
可粗略认为一个系统调用 ( 进入内核 ) 即为一个取消点。如线程中没有取消点,可以通过调用 ==pthread_testcancel()==函数自行设置一个取消点。
例程:终止线程的三种方法。注意 “ 取消点 ” 的概念。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>void *tfn1(void *arg)
{printf("thread 1 returning\n");return (void *)111;
}void *tfn2(void *arg)
{printf("thread 2 exiting\n");pthread_exit((void *)222);
}void *tfn3(void *arg)
{while (1){printf("thread 3: I'm going to die in 3 seconds ...\n");sleep(1);//pthread_testcancel(); //自己添加取消点*/}return (void *)888;
}int main(void)
{pthread_t tid1;pthread_t tid2;pthread_t tid3;void *tret = NULL;pthread_create(&tid1, NULL, tfn1, NULL);pthread_join(tid1, &tret);printf("thread 1 exit code = %ld\n\n", (long int)tret);pthread_create(&tid2, NULL, tfn2, NULL);pthread_join(tid2, &tret);printf("thread 2 exit code = %ld\n\n", (long int)tret);pthread_create(&tid3, NULL, tfn3, NULL);sleep(3);pthread_cancel(tid3);pthread_join(tid3, &tret);printf("thread 3 exit code = %ld\n", (long int)tret);return 0;
}
测试,注释 tfn3() 函数中, printf(),sleep() 等函数功能后,发现无法杀死,说明只有进入内核,才能杀死
控制原语对比
2. 线程属性
linux 下线程的属性是可以根据实际项目需要,进行设置,之前我们讨论的线程都是采用线程的默认属性,默认属性已经可以解决绝大多数开发时遇到的问题。如我们对程序的性能提出 更高的要求那么需要设置线程属性,比如可以通过设置线程的大小来降低内存的使用,增加最大线程个数。
typedef struct{int detachstate ; // 线程的分离状态。int schedpolicy ; // 线程调度策略。struct sched_param schedparam ; // 线程的调度参数。int inhertsched ; // 线程的继承性。int scope ; // 线程的作用域。size_t guardsize ; // 线程栈末尾的警戒缓冲区大小int stackaddr_set ; // 线程栈的设置,void * stackaddr ; // 线程栈的位置。size_t stacksize ; // 线程栈的大小。} pthread_attr_t ;
主要结构体成员:
1.线程分离状态。2.线程栈大小 ( 默认平均分配 ) 。3.线程栈警戒缓冲区大小 ( 位于栈末尾 )
属性值不能直接设置,须使用相关函数进行操作,初始化的函数为 pthread_ attr_init , 这个函数必须在pthread_create 函数之前调用。之后须用 pthread_attr_destroy 函数来释放资源。
线程属性主要包括如下属性。作用域 (scope) 、栈尺寸 (stack size) 、栈地址 (stack adress) 、优先级
(priority) 、分离的状态 (detached state) 、调度策略和参数 (scheduling policy and parameters) 。默认的属性为非绑定、非分离、缺省的堆栈、与父进程同样级别的优先级。
2.1线程属性初始化
注意 : 应先初始化线程属性,再 pthread_ create 创建线程。
初始化线程属性,将线程的属性初始化为默认值,分配一些存储空间。
int pthread_attr_init ( pthread_attr_t * attr );成功 : 0 ; 失败 : 错误号。
销毁线程属性所占用的资源,删除初始化期间分配的存储空间
int pthread_attr_destroy ( pthread_attr_t * attr );成功 : 0 ; 失败 : 错误号。
2.2线程的分离状态
线程的分离状态决定一个线程以什么样的方式来终止自己。
非分离状态线程的默认属性是非分离状态 , 这种情况下 , 原有的线程等待创建的线程结束。只有当
pthread_join() 函数返回时,创建的新线程才算终止 , 才能释放自己占用的系统资源。
分离状态 : 分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。应该根据自己的需要,选择适当的分离状态。
线程分离状态的函数。
设置线程属性,分离or非分离
int pthread_attr_setdetachstate ( pthread_attr_t * attr , int detachstate );
获取线程属性,分离or非分离
int pthread_attr_getdetachstate ( pthread_attr_t * attr , int * detachstate );参数 :attr : 已初始化的线程属性。detachstate : PTHREAD_CREATE_DETACHED ( 分离线程 )PTHREAD_CREATE_JOINABLE ( 非分离线程 )
例程:通过设置线程分离属性,创建分离子线程
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>void *tfn(void *arg)
{printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());sleep(5);return NULL ;
}int main(void)
{pthread_t tid;int *retval;pthread_attr_t attr;int ret=pthread_attr_init(&attr);if(ret!=0){fprintf(stderr,"attr_init error:%s\n",strerror(ret));exit(1);}ret=pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//设置线程属性为,分离属性if(ret!=0){fprintf(stderr, "attr_setdetachstate error:%s\n",strerror(ret));exit(1);}ret = pthread_create(&tid, &attr, tfn, NULL);if(ret!=0){perror("pthread_create error");exit(1);}ret = pthread_attr_destroy(&attr);if(ret!=0){fprintf(stderr, "attr_ destroy error:%s\n",strerror(ret));exit(1);}printf("111:tid=%lu\n",tid);ret= pthread_join(tid,(void **)&retval);printf("222:tid=%lu\n",tid);printf("thread_join() ret = %d\n",ret);if(ret!=0){fprintf(stderr, "thread_join error:%s\n",strerror(ret));exit(1);}printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());return 0;
}
注意
如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create 的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timedwait 函数,让这个线程等待一会儿, 留出足够的时间让函数pthread_create 返回。设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait( )之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。
小结:设置分离属性 :
- pthread_attr_t attr 创建一个线程属性结构体变量
- pthread_attr_init(&attr); 初始化线程属性
- pthread_attr_ setdetachstate(&attr,PTHREAD_CREATE_ DETACHED); 设置线程属性为分离态
- pthread_create(&tid, &attr, tfn, NULL); 借助修改后的设置线程属性创建为分离态的新线程
- pthread_attr_destroy(&attr); 销毁线程属性
2.3线程使用注意事项
1. 主线程退出其他线程不退出,主线程应调用 pthread_exit , == 任何线程调用 exit 都会导致整个线程组退出== 。
2. 避免僵尸线程
pthread_join 回收
pthread_detache 分离
pthread_create 指定分离属性。
被join 的线程可能在 join 函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值;
3.malloc 和 mmap 申请的内存可以被其他线程释放。
4. 应避免在多线程模型中调用 fork ,除非马上 exec, 子进程中只有调用 fork 的线程存在,其他线程在子进程中均pthread_exit.
5. 信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制。