C线程
有关线程的简单实现
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
//定义线程函数(固定)--void *
void *pth_fun(void *pth_arg){
while(1){
printf("pthread\n");
sleep(1);
}
return NULL;
}
int main(){
pth_fun(NULL);//先将线程函数作为普通函数调用
return 0;
}
guojiawei@ubantu-gjw:~/Desktop/pthread$ gcc pthread.c
guojiawei@ubantu-gjw:~/Desktop/pthread$ ./a.out
pthread
pthread
pthread
pthread
pthread
^Xpthread
pthread
^Z
[1]+ 已停止 ./a.out
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include <pthread.h>
//定义线程函数(固定)--void *
void *pth_fun(void *pth_arg){
while(1){
printf("child_pthread\n");
sleep(1);
}
return NULL;
}
int main(){
/*将函数注册为线程函数*/
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,\
void *(*start_routine) (void *), void *arg);
pthread_t tId=0;
//函数名就是地址
pthread_create(&tId,NULL,pth_fun,NULL);//主线程
while(1){
printf("father\n");
sleep(2);
}
return 0;
}
程序结果:
可以自己链接线程库
-pthread
,但是现在可以不加
主线程和次线程是并发运行
的,主线程不运行,次线程无法运行
- 其实主线程是一个进程,目的为了带动线程运行
- 线程就是一个函数,有的语言会封装
学习C线程意义
-
c程序经常涉及到多线任务的问题,所以c线程在实际开发中会被经常用到
使用多线程的话,必须要OS支持
-
所有的线程库的原理和使用方式都是类似
理解了C线程函数,在学习其它语言的线程库函数就会容易很多
为什么会有线程
为了弥补
进程的缺点
进程最大的优点:
拥有独立的进程空间
可以很好地保证每一个进程的安全,不被其它进程所攻击或者干扰,
进程的缺点:(也是优点带来的)
- 进程间
切换
的计算机资源开销很大,切换效率
非常低- 进程间
数据共享的开销
也很大
为什么进程之间切换资源开销大
OS是通过虚拟内存机制来实现进程空间独立的
-
进程在
并发运行时
需要相互间的切换,切换时必然需要涉及虚拟内存机制
的控制 -
但是虚拟内存机制比较复杂,所以在进行进程间切换时,会耗费高昂的cpu、缓存(cache)、内存等计算机资源,也非常耗费切换时间
进程通信的开销大
当程序涉及多进程
时,往往会涉及到进程间的通信
,由于进程空间的独立性
,OS提供了各种各样的通信机制
:
这些通信机制共同原理:通过OS来转发进程间的数据
但是调用OS提供的这些通信机制的函数时,这些OS函数的运行也是需要消耗相当cpu、内存等计算机资源的,同时也很耗费时间
对于有OS的计算机来说:
虽然进程是必不可少的,但是进程确又不能太多,进程太多会导致计算机资源被剧烈消耗,此时你会发现你的计算机非常的卡
创建多进程目的
目的1:创建子进程,执行新程序
目的2:创建子进程得到多进程,通过多进程并发实现多线任务
多线任务例子
-
同时阻塞的读鼠标和键盘
时,如果单线的话会想互影响,需要两线任务来实现比如:父进程读鼠标,子进程读键盘
-
读写管道
时,读操作是阻塞的,为了避免读操作影响写操作,也需要两线任务同时进行父子进程一个读管道,一个写管道
-
第一种目的,执行
新程序
时必须创建子进程,这个无法逃避的 -
第二种目的,使用多进程来实现就存在巨大的问题
因为几乎所有的程序都涉及
多线任务
的操作
多线任务使用多进程的问题
程序往往都是十几个任务以上,如果此时使用多进程
来实现多线任务的,这就大致大量进程的产生
比如计算机运行了100个程序,假设每个程序平均10多个任务,如果全部采用多进程来实现,计算机最终要运行的进程就多达上100个
总结:
进程切换和进程间通信
的计算机资源开销又很大,往往导致计算机非常卡顿,程序的运行效率非常低
线程的发明目的:弥补
多进程实现多线任务
的缺点。
线程为什么可以弥补进程的缺点
线程与进程一样,线程和进程会被OS统一调度,所以线程和进程都是一起并发运行的
实现程序的多线任务的前提就是:并发执行
有了线程以后,凡是程序涉及到多线任务时,都使用多线程来实现
- 线程间的切换和数据通信的开销非常低
- 线程还有另一个名称,叫“
轻量级的进程
”-----开销非常低
说白了线程就是为了多线任务而生的
为什么线程切换的开销低
使用多线程来实现多线任务时:
线程本质上它只是程序(进程)的一个函数,与普通函数的区别是:普通函数时单线的运行关系,而线程函数被注册为线程后,是多线并发
运行。
简单理解就是:
1.普通函数的执行是:堆栈,也就是遇到子函数去执行子函数,再遇到子函数,切换到调用的子函数,执行完就返回到调用函数,直至结束,所以是单线
2.而线程是并发的,不存在相互调用,只能时间片切换,所以是多线
--
线程函数也可以去调用普通函数
- 对于普通函数来说,只有当相互调动时才会涉及函数间的切换
- 对于线程函数来说,只要运行的时间片到了就会切换
不管是函数间的切换,还是线程的切换都是
进程内部的事情
不涉及进程间的切换,故而省去了进程间切换的巨大开销
但是两个线程分别存在于不同的进程,此时两个线程切换依然需要进程切换
- 线程的切换其实就是函数间的切换,函数切换当然也需要开销,但是这些开销非常小
为什么线程间数据共享的开销很低
线程的本质就是函数,所以线程通信属于函数通信
函数间通信有两种方式:
(1)具有相互调用关系函数来说
使用函数传参来通信。
(2)对于没有调用关系的函数来说
使用全局变量来通信。
A函数 ————> 全局变量 ————> B函数
全局变量的作用是什么?
用来实现无调用关系的函数间通信
的
进程中所有的线程函数
除了相互并发
运行外,没有调用关系
所以线程函数间想要数据共享
的话,就使用全局变量
来通信
线程为什么不能替代进程
线程的本质是函数,函数运行需要内存空间,线程运行的内存空间就是进程的内存空间
线程运行时必须依赖于进程的存在,如果没有进程所提供的
内存空间
这个资源,线程根本无法运行
线程作为函数,只是进程的一个部分而已,线程是不可能脱离进程而独立存在
- 同一个进程中的所有线程,都是运行在相同的进程空间中的
- 换句话说
同一个进程中所有线程共相同的进程空间
既然进程空间是共享的,那么所有的函数,自然就能共享访问在共享空间中所开辟出来的全局变量
线程独立的属性
线程共享进程空间的资源
- 全局变量
- 工作目录(进程的当前工作目录)
- 打开的文件描述符
- 子函数
- 进程uid、gid,进程PID等等
线程的独立属性
并发运行的就是一个单独执行体
-
每个线程拥有自己独立的线程ID(TID)
-
每个线程有独立的切换状态
- 在切换时,当前线程被中断的那条指令的地址
- 线程切换时相关的运行状态
说白了就是:保存现场,便于还原
-
有自己独立的
函数栈
每一个函数都有自己的
函数栈
,所有的函数栈都开辟于进程空间的进程栈
函数栈的作用就是用来
保存函数局部变量
的 -
自己独立的错误号
线程函数出错时,错误号并不是通过设置errno实现的,而是直接将错误号返回
-
每一个线程有自己独立的
信号屏蔽字
和未决信号集
-
每个线程有自己
独立的tack_struct结构体
- 管理线程时:也会为线程开辟一个task_struct变量
- 只不过适用于存放的是
线程的管理信息
创建子进程执行新程序
,大多都是OS操心的事
比如:父进程(命令行、图形界面)创建子进程并加载执行新程序
但是我们自己的程序很少使用多进程,一般使用多线程
线程控制函数
- pthread_create()
- pthread_join()
- pthread_detach()
- pthread_cancel()
- pthread_exit等
线程函数由谁提供
进程控制的fork、exec等函数都是由os系统提供的
为了不给OS增加负担,同时也为了提高线程的灵活性:
后来的线程不由OS提供,而是由单独的线程库
来提供
不过
线程库
在实现时,也是调用了相应的系统API的
线程库
c线程库
并不是C标准库,而是POSIX C库
的一部分
java、c++、c#的线程函数由他们自己的线程库来实现的
- java、c++的线程函数会比c的线程复杂一些
线程库和OS系统API的关系
线程库函数实际上也是封住OS的相应API来实现的
线程库运行在Linux,通过调用Linux的
clone()
等系统函数实现
将线程函数注册为线程时:
其实就是通过调用这类系统API,然后去模拟我们的进程来实现的
正是因为是:模拟进程来实现的,所以线程函数才能进程一样,一起被并发运行
可以自己调用系统API,然后封装做出自己的c/c++/java线程库
----不过没啥价值和意义,因为有现成的
C线程控制函数
pthread_create()
#include <pthread.h>
//把第三个参数的函数注册成为一个线程函数
//该函数一旦注册成功,这个函数就以次线程的方式开始并发运行
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,\
void *(*start_routine) (void *), void *arg);
/*返回值
成功返回0,失败返回非零错误号*/
Compile and link with
-pthread
目的主要是实现并发,而且该注册函数是次进程
- 凡是使用pthread_create注册的线程都是次线程,次线程会和主线程一起并发运行。
void*
(*start_routine) (void *
):参数和返回值都是void *,函数名就是地址
参数
-
thread------注意传指针,需要取地址
-
attr----(attibute属性)
是个重命名的结构体,用于设置线程属性
设置线程属性
是为了实现某些特殊功能- 设置为
NULL
,表示不设置特有的属性,使用线程默认属性
所提供的功能
正常情况下,线程默认属性所提供的功能就已经够用了
-
要注册为线程的函数地址
如果不注册,线程函数就是一个普通的函数
//线程函数需要我们自己定义 void *pth_fun(void *pth_arg){ ...//线程要做的事情 } //pth_fun和pth_arg的命名由自己决定。
- arg—传递给线程函数的参数
传递给线程函数的参数,这个参数会传递给pth_arg
- 如果参数很多的话,定义一个结构体,然后把结构体变量的地址传过去
- 如果不传递参数,设置为NULL
总结:pthread(进程号地址,设置属性(一般NULL),函数名地址,线程函数的传参)
程序----两个次进程向文件写数据
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#define SECOND_PTH_NUMS 2
void pth_print_err(char * str,int err){
printf("%s:%s",str,strerror(err));
exit(-1);
}
//次线程结构体参数
typedef struct pthread_arg{
pthread_t tid;
int pth_no;//线程编号
int fd;//打开的文件描述符
}ptharg;
//线程普通函数
void *pth_fun(void *pth_arg){
int fd=((ptharg *)pth_arg)->fd;
while(1){
printf("%d",((ptharg *)pth_arg)->pth_no);
write(fd,"hello ",6);
write(fd,"world\n",6);
sleep(1);
}
return NULL;
}
int main(){
int i,ret;
int fd=open("./mmm.txt",O_CREAT|O_TRUNC|O_RDWR,0664);
if(fd==-1) pth_print_err("open fails\n",errno);
/*将函数注册为线程函数,传建两个次线程
int pthread_create(线程号地址,设置属性,函数地址,线程函数传参);*/
ptharg pth_arg[SECOND_PTH_NUMS];
for(int i=0;i<SECOND_PTH_NUMS;i++){
pth_arg[i].fd=fd;
pth_arg[i].pth_no=i;
ret= pthread_create(&pth_arg[i].tid,NULL,\
pth_fun,(void *)&pth_arg[i]);
if(ret!=0){
pth_print_err("pthread_create fails\n",ret);
}
}
while(1){
printf("father\n");
sleep(2);
}
return 0;
}
需要强调的地方
-
gcc编译时,跟-pthread选项(现在可以不加)
-
父子线程调用子函数不冲突
- main函数调用子函数时,子函数属于主线程这条线
- 次线程调用子函数时,子函数属于次线程这条线。
-
主线程代表了整个进程的存在
- 主线程结束了,整个进程也就结束了,进程都没了线程自然也没了
- 所以主线程一定不能死
- 次线程结束了,对整个进程没有任何影响
-
C
线程函数的启动
与c++/java线程函数启动的略微有所不同- c++/java的线程函数被注册为线程后不会立即启动,需要单独调用某个启动函数来启动
c线程函数一旦被pthread_create注册为线程后会
立即被启动运行
-
c++、java等面向对象的函数,都被封装在类里面,包括线程函数也是如此
而c这种面向过程语言的函数,全部都裸露在外的
pthread_cancel()
#include <pthread.h>
//当次线程是死循环时,可以调动这个函数主动取消该线程
//返回值:成功返回0,失败返回非零错误号
int pthread_cancel(pthread_t thread);
//参数------thread:要取消线程的TID
//Compile and link with -pthread
//设置全局变量,供signal_fun使用
typedef struct thread_args{
pthread_t thrId;
int thrNo;
int fd;
}threadArg;
threadArg thr[2];
//取消两个次线程(信号捕捉)
void signal_fun(int signo){
if(SIGALRM==signo){
int i=0;
for(;i<PTH_NUMS;i++)
pthread_cancel(thr[i].thrId);
}
}
//alarm,10秒后取消
signal(SIGALRM,signal_fun);
alarm(10);
使用编号来结束
好处是,完整执行程序后,再去判断是否该杀死
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define PTH_NUMS 2
#define PTHEXIT -1
typedef struct thread_args{
pthread_t thrId;
int thrNo;
int fd;
}threadArg;
//结构体封装所有全局变量
struct global_vals{
threadArg thr[2];
int pth_exit_flag[PTH_NUMS];
}globalVal;//全局变量初始化为0
void print_err(int err,char* str){
printf("%s:%s",str,strerror(err));
exit(-1);
}
void *pthread_fun(void *arg){
int fd=((threadArg *)arg)->fd;
int thrNo=((threadArg*)arg)->thrNo;
pthread_t thrId=((threadArg *)arg)->thrId;
/*线程功能*/
while(1){
printf("线程号:%lu,线程编号:%d\n",thrId,thrNo);
write(fd,"hello,",6);
write(fd,"world\n",6);
if(globalVal.pth_exit_flag[thrNo]) break;
sleep(1);
}
return NULL;
}
void signal_fun(int signo){
if(SIGALRM==signo){
int i=0;
for(;i<PTH_NUMS;i++){
globalVal.pth_exit_flag[i]=PTHEXIT;
pthread_cancel(globalVal.thr[i].thrId);
}
}
}
int main(void){
int fd=open("./threadFile",O_CREAT|O_TRUNC|O_RDWR,0664);
int i,ret;
for(i=0;i<PTH_NUMS;i++){
globalVal.thr[i].fd=fd;
globalVal.thr[i].thrNo=i;
ret=pthread_create(&globalVal.thr[i].thrId,NULL,\
pthread_fun,(void *)&globalVal.thr[i]);
if(ret!=0)
print_err(ret,"pthread_create fails\n");
}
//定时10s,时间到后取消次线程
signal(SIGALRM,signal_fun);
alarm(10);
//父线程
while(1){
printf("father\n");
sleep(2);
}
return 0;
}
pthread_exit()
#include <pthread.h>
//线程调用这个函数时,可以主动退出(终止)
//返回值:成功返回0,失败返回非零错误号
//参数----------retval:线程结束时的返回值。
void pthread_exit(void *retval);
/*如果返回值很多时,就封装成一个结构体,返回结构体变量的地址即可*/
- 类似于exit函数,不过exit是终止整个进程的
- pthread_exit是终止
次线程
的
事实上:
线程
也可以通过调动return
来退出(终止)
pthread_self()
#include <pthread.h>
//功能:线程获取自己的TID,类似于进程调用getpid()获取自己的PID一样
pthread_t pthread_self(void);
//返回值:成功返回线程TID,失败返回非零错误号。
使用:
printf("fatherTid:%lu\n",pthread_self());//主线程TID
pthread_join()
#include <pthread.h>
/*功能:
阻塞等待tid为thread的次线程结束,结束时该函数会回收次线程所占用的所有资源(存储空间)
*/
//返回值:成功返回0,失败返回错误号
int pthread_join(pthread_t thread, void **retval);
这个函数只对
次线程
有意义,对主线程
没有意义
- 因为主线程结束时整个进程就结束了,整个进程资源会由父进程回收
- 这个函数一般都是由主线程调用,以回收相关次线程的资源
- 当然次线程也是可以调用这个函数来回收其它次线程资源的
回收次线程资源原因
- 有些程序(进程)一旦运行后将会长期运行,不会结束
- 如果不回收,每结束一个次线程就导致一部分资源被占用
- 慢慢累积会使得整个进程资源越用越少,最好导致进程崩溃
说白了就是:次进程不断占据运行的进程的资源,最后过多而使得进程崩溃
参数:
(a)thread:指定要回收次线程的TID
(b)retval:次线程函数返回的返回值
``参数二取的是void * 返回值的地址,所以是两个*`
使用
//次进程函数里面的返回值
//return NULL;
pthread_exit((void *)10);
//循环接收次进程资源(阻塞)
void *retval=NULL;
for(i=0;i<PTH_NUMS;i++){
pthread_join(globalVal.thr[i].thrId,&retval);
printf("%p\n",retval);
}
如果线程是被pthread_cancel取消掉的,自动返回-1
pthread_detach()
#include <pthread.h>
/*功能:
如果次线程的资源不希望别人调用pthread_join函数来回收的话,而是希望自己在结束时,自动回收资源的话,就可以调用这个函数。
*/
//返回值:成功返回0,失败返回错误号
//参数:thread:你要分离的那个次线程的TID。
int pthread_detach(pthread_t thread);
这个函数的功能就是分离次线程
,让次线程在结束时自动回收资源
使用:
//次进程资源自动回收
pthread_detach(globalVal.thr[i].thrId);
pthread_join
和pthread_detach
作为两种不同的线程资源回收方式
,只能二选一
注释代码的方式
注册进程退出处理函数
- 进程程在退出时会自动调用
atexit();
,实现进程的扫尾处理
回顾进程处理函数:atexit
void process_deal(){
printf("\ndealing process!!\n");
}
//函数无返回值无传参
/*进程处理函数*/
atexit(process_deal);
把ctrl+c变成正常退出
void signal_fun(int signo){ if(SIGALRM==signo){ int i=0; for(;i<PTH_NUMS;i++){ globalVal.pth_exit_flag[i]=PTHEXIT; pthread_cancel(globalVal.thr[i].thrId); } } else if(signo==SIGINT){ exit(0); } }
signal(SIGINT,signal_fun);
- 线程也是这样
#include <pthread.h>
//注册函数
void pthread_cleanup_push(void (*routine)(void *),void *arg);
void pthread_cleanup_pop(int execute);
- pthread_cleanup_push
-
将类型为void (*routine)(void *)函数注册为“线程
退出处理
函数”注意第一个没*,第二个有*
-
arg
:传递给退出处理函数的参数
- pthread_cleanup_pop
执行这个函数时,参数:
-
如果参数写!0
会将压入栈中的推出处理函数地址弹出,然后调用退出函数进行线程的扫尾处理。
-
如果参数写0
不弹出调用
注册的原理就是将处理函数地址压入线程栈
----可以反复调用该函数注册多个退出处理函数,但是一般一个就够了
----注册了多个线程退出处理函数的话,由于栈先进后出的特点
注册压栈的顺序与弹栈调动的顺序刚好相反
有一个pthread_cleanup_push,就必须要对应有一个pthread_cleanup_pop
就算这个函数调用不到也必须写,否则编译时不通过
就像{}是成对出现的,缺一个都会报错
例子
/*线程退出处理函数*/
void pth_exit_deal(void* t_arg){
printf("pthread %lu exit\n",((threadArg*)t_arg)->thrId);
}
/*进程函数*/
void *pthread_fun(void *arg){
//注册 线程退出处理 函数
pthread_cleanup_push(pth_exit_deal,arg);
pthread_cleanup_pop(!0);
pthread_exit((void *)10);
}
弹栈线程退出处理函数的几种条件
(a)调用thread_cleanup_pop(!0),主动弹栈
(b)如果线程是被别人调用pthread_cancel取消的,也会弹栈
(c)如果线程是调用pthread_exit函数退出的,也会弹栈
如果线程时调用return退出的话,是不会自动弹栈的
要弹栈的话,必须主动调动thread_cleanup_pop(!0)
线程的属性设置
就是pthreadcreate()的第二个参数不要写NULL
可以设置的线程属性
- 设置绑定属性
- 设置分离属性
- 设置线程堆栈属性
- 设置线程调度优先级属性
事实上默认属性所提供的功能就已经足够我们使用了,
C线程中设置属性的函数非常多,基本每一种属性都有独立的设置函数
例子:设置分离属性
将线程分离有两种方法:
-
调用
pthread_detach函数
实现 -
通过设置
分离属性
实现事实上使用pthread_detach()更方便些
分离属性设置的步骤:
-
定义一个变量来存放新属性
pthread_attr_t attr;//一个结构体,被typedef重命名了
-
调用
pthread_attr_init(&attr);
初始化一下attr结构体变量int pthread_attr_init(pthread_attr_t *attr); //成功返回0,失败返回错误号
-
调用pthread_attr_setdetachstate预设分离属性
想设置什么类型属性就选择什么函数
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
指定PTHREAD_CREATE_DETACHED宏后:
pthread_attr_setdetachstate函数会自动的将:
分离属性相关的数据设置到attr结构体变量的各个成员中
-
调动pthread_create创建线程时,将attr传递给pthread_create,将线程分离
-
删除属性设置
int pthread_attr_destroy(pthread_attr_t *attr); //成功返回0,失败返回错误号
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <string.h> #include <signal.h> #define PTH_NUMS 2 #define PTHEXIT -1 typedef struct thread_args{ pthread_t thrId; int thrNo; int fd; }threadArg; //结构体封装所有全局变量 struct global_vals{ threadArg thr[2]; int pth_exit_flag[PTH_NUMS]; pthread_attr_t attr;//设置线程属性 }globalVal;//全局变量初始化为0 void print_err(int err,char* str){ printf("%s:%s",str,strerror(err)); exit(-1); } /*线程退出处理函数*/ void pth_exit_deal(void* t_arg){ printf("pthread %lu exit\n",((threadArg*)t_arg)->thrId); } void *pthread_fun(void *arg){ int fd=((threadArg *)arg)->fd; int thrNo=((threadArg*)arg)->thrNo; pthread_t thrId=((threadArg *)arg)->thrId; //pthread_detach(pthread_self()); //注册 线程退出处理 函数 pthread_cleanup_push(pth_exit_deal,arg); /*线程功能*/ while(1){ printf("线程号:%lu,线程编号:%d\n",thrId,thrNo); write(fd,"hello,",6); write(fd,"world\n",6); if(globalVal.pth_exit_flag[thrNo]) break; sleep(1); } //pthread_exit((void *)10); return NULL; pthread_cleanup_pop(!0); } void signal_fun(int signo){ if(SIGALRM==signo){ int i=0; for(;i<PTH_NUMS;i++){ globalVal.pth_exit_flag[i]=PTHEXIT;//退出标志 //pthread_cancel(globalVal.thr[i].thrId);//主动 } } else if(signo==SIGINT){ pthread_attr_destroy(&globalVal.attr); exit(0); } } void process_deal(){ printf("\ndealing process!!\n"); } int main(void){ /*进程处理函数*/ atexit(process_deal); int fd=open("./threadFile",O_CREAT|O_TRUNC|O_RDWR,0664); int i,ret; //属性初始化 ret=pthread_attr_init(&globalVal.attr); if(ret!=0) print_err(ret,"attr fails\n"); //设置分离属性 pthread_attr_setdetachstate(&globalVal.attr,PTHREAD_CREATE_DETACHED); //线程初始化 for(i=0;i<PTH_NUMS;i++){ globalVal.thr[i].fd=fd; globalVal.thr[i].thrNo=i; ret=pthread_create(&globalVal.thr[i].thrId,\ &globalVal.attr,\ pthread_fun,(void *)&globalVal.thr[i]); //次进程资源自动回收 pthread_detach(globalVal.thr[i].thrId); if(ret!=0) print_err(ret,"pthread_create fails\n"); } //信号异常终止变成正常终止 signal(SIGINT,signal_fun); //定时10s,时间到后取消次线程 signal(SIGALRM,signal_fun); alarm(3); #if 0 //循环接收次进程资源(阻塞) void *retval=NULL; for(i=0;i<PTH_NUMS;i++){ pthread_join(globalVal.thr[i].thrId,&retval); printf("%ld\n",(long)retval); } #endif //父线程 while(1){ printf("fatherTid:%lu\n",pthread_self());//主线程TID sleep(2); } return 0; }