#include
#include
#include
//定义线程函数(固定)--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
#include
#include
#include
//定义线程函数(固定)--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线程在实际开发中会被经常用到
使用多线程的话,必须要OS支持
所有的线程库的原理和使用方式都是类似
理解了C线程函数,在学习其它语言的线程库函数就会容易很多
为了弥补
进程的缺点
进程最大的优点:
拥有独立的进程空间
可以很好地保证每一个进程的安全,不被其它进程所攻击或者干扰,
进程的缺点:(也是优点带来的)
- 进程间
切换
的计算机资源开销很大,切换效率
非常低- 进程间
数据共享的开销
也很大
OS是通过虚拟内存机制来实现进程空间独立的
进程在并发运行时
需要相互间的切换,切换时必然需要涉及虚拟内存机制
的控制
但是虚拟内存机制比较复杂,所以在进行进程间切换时,会耗费高昂的cpu、缓存(cache)、内存等计算机资源,也非常耗费切换时间
当程序涉及多进程
时,往往会涉及到进程间的通信
,由于进程空间的独立性
,OS提供了各种各样的通信机制
:
这些通信机制共同原理:通过OS来转发进程间的数据
但是调用OS提供的这些通信机制的函数时,这些OS函数的运行也是需要消耗相当cpu、内存等计算机资源的,同时也很耗费时间
对于有OS的计算机来说:
虽然进程是必不可少的,但是进程确又不能太多,进程太多会导致计算机资源被剧烈消耗,此时你会发现你的计算机非常的卡
目的1:创建子进程,执行新程序
目的2:创建子进程得到多进程,通过多进程并发实现多线任务
同时阻塞的读鼠标和键盘
时,如果单线的话会想互影响,需要两线任务来实现
比如:父进程读鼠标,子进程读键盘
读写管道
时,读操作是阻塞的,为了避免读操作影响写操作,也需要两线任务同时进行
父子进程一个读管道,一个写管道
第一种目的,执行新程序
时必须创建子进程,这个无法逃避的
第二种目的,使用多进程来实现就存在巨大的问题
因为几乎所有的程序都涉及
多线任务
的操作
程序往往都是十几个任务以上,如果此时使用多进程
来实现多线任务的,这就大致大量进程的产生
比如计算机运行了100个程序,假设每个程序平均10多个任务,如果全部采用多进程来实现,计算机最终要运行的进程就多达上100个
总结:
进程切换和进程间通信
的计算机资源开销又很大,往往导致计算机非常卡顿,程序的运行效率非常低
线程的发明目的:弥补
多进程实现多线任务
的缺点。
线程与进程一样,线程和进程会被OS统一调度,所以线程和进程都是一起并发运行的
实现程序的多线任务的前提就是:并发执行
有了线程以后,凡是程序涉及到多线任务时,都使用多线程来实现
轻量级的进程
”-----开销非常低说白了线程就是为了多线任务而生的
使用多线程来实现多线任务时:
线程本质上它只是程序(进程)的一个函数,与普通函数的区别是:普通函数时单线的运行关系,而线程函数被注册为线程后,是多线并发
运行。
简单理解就是:
1.普通函数的执行是:堆栈,也就是遇到子函数去执行子函数,再遇到子函数,切换到调用的子函数,执行完就返回到调用函数,直至结束,所以是单线
2.而线程是并发的,不存在相互调用,只能时间片切换,所以是多线
--
线程函数也可以去调用普通函数
不管是函数间的切换,还是线程的切换都是
进程内部的事情
不涉及进程间的切换,故而省去了进程间切换的巨大开销
但是两个线程分别存在于不同的进程,此时两个线程切换依然需要进程切换
线程的本质就是函数,所以线程通信属于函数通信
函数间通信有两种方式:(1)具有相互调用关系函数来说使用函数传参来通信。(2)对于没有调用关系的函数来说使用全局变量来通信。A函数 ————> 全局变量 ————> B函数
用来实现无调用关系的函数间通信
的
进程中所有的线程函数
除了相互并发
运行外,没有调用关系
所以线程函数间想要数据共享
的话,就使用全局变量
来通信
线程的本质是函数,函数运行需要内存空间,线程运行的内存空间就是进程的内存空间
线程运行时必须依赖于进程的存在,如果没有进程所提供的
内存空间
这个资源,线程根本无法运行
线程作为函数,只是进程的一个部分而已,线程是不可能脱离进程而独立存在
同一个进程中所有线程共相同的进程空间
既然进程空间是共享的,那么所有的函数,自然就能共享访问在共享空间中所开辟出来的全局变量
并发运行的就是一个单独执行体
每个线程拥有自己独立的线程ID(TID)
每个线程有独立的切换状态
说白了就是:保存现场,便于还原
有自己独立的函数栈
每一个函数都有自己的
函数栈
,所有的函数栈都开辟于进程空间的进程栈
函数栈的作用就是用来
保存函数局部变量
的
自己独立的错误号
线程函数出错时,错误号并不是通过设置errno实现的,而是直接将错误号返回
每一个线程有自己独立的信号屏蔽字
和未决信号集
每个线程有自己独立的tack_struct结构体
- 管理线程时:也会为线程开辟一个task_struct变量
- 只不过适用于存放的是
线程的管理信息
创建子进程执行新程序
,大多都是OS操心的事
比如:父进程(命令行、图形界面)创建子进程并加载执行新程序
但是我们自己的程序很少使用多进程,一般使用多线程
进程控制的fork、exec等函数都是由os系统提供的
为了不给OS增加负担,同时也为了提高线程的灵活性:
后来的线程不由OS提供,而是由单独的线程库
来提供
不过
线程库
在实现时,也是调用了相应的系统API的
c线程库
并不是C标准库,而是POSIX C库
的一部分
java、c++、c#的线程函数由他们自己的线程库来实现的
- java、c++的线程函数会比c的线程复杂一些
线程库函数实际上也是封住OS的相应API来实现的
线程库运行在Linux,通过调用Linux的
clone()
等系统函数实现
将线程函数注册为线程时:
其实就是通过调用这类系统API,然后去模拟我们的进程来实现的
正是因为是:模拟进程来实现的,所以线程函数才能进程一样,一起被并发运行
可以自己调用系统API,然后封装做出自己的c/c++/java线程库
----不过没啥价值和意义,因为有现成的
#include
//把第三个参数的函数注册成为一个线程函数
//该函数一旦注册成功,这个函数就以次线程的方式开始并发运行int pthread_create(pthread_t *thread, const pthread_attr_t *attr,\void *(*start_routine) (void *), void *arg);
/*返回值成功返回0,失败返回非零错误号*/
Compile and link with
-pthread
目的主要是实现并发,而且该注册函数是次进程
void*
(*start_routine) (void *
):参数和返回值都是void *,函数名就是地址
thread------注意传指针,需要取地址
attr----(attibute属性)
是个重命名的结构体,用于设置线程属性
设置线程属性
是为了实现某些特殊功能NULL
,表示不设置特有的属性,使用线程默认属性
所提供的功能正常情况下,线程默认属性所提供的功能就已经够用了
要注册为线程的函数地址
如果不注册,线程函数就是一个普通的函数
//线程函数需要我们自己定义 void *pth_fun(void *pth_arg){...//线程要做的事情} //pth_fun和pth_arg的命名由自己决定。
传递给线程函数的参数,这个参数会传递给pth_arg
总结:pthread(进程号地址,设置属性(一般NULL),函数名地址,线程函数的传参)
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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;ipth_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线程函数一旦被pthread_create注册为线程后会
立即被启动运行
c++、java等面向对象的函数,都被封装在类里面,包括线程函数也是如此
而c这种面向过程语言的函数,全部都裸露在外的
#include
//当次线程是死循环时,可以调动这个函数主动取消该线程
//返回值:成功返回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
使用编号来结束
好处是,完整执行程序后,再去判断是否该杀死
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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(;iglobalVal.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;iglobalVal.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;
}
#include
//线程调用这个函数时,可以主动退出(终止)
//返回值:成功返回0,失败返回非零错误号
//参数----------retval:线程结束时的返回值。
void pthread_exit(void *retval);
/*如果返回值很多时,就封装成一个结构体,返回结构体变量的地址即可*/
次线程
的事实上:
线程
也可以通过调动return
来退出(终止)
#include
//功能:线程获取自己的TID,类似于进程调用getpid()获取自己的PID一样
pthread_t pthread_self(void);
//返回值:成功返回线程TID,失败返回非零错误号。
使用:
printf("fatherTid:%lu\n",pthread_self());//主线程TID
#include
/*功能:
阻塞等待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;ipthread_join(globalVal.thr[i].thrId,&retval);printf("%p\n",retval);}
如果线程是被pthread_cancel取消掉的,自动返回-1
#include
/*功能:如果次线程的资源不希望别人调用pthread_join函数来回收的话,而是希望自己在结束时,自动回收资源的话,就可以调用这个函数。
*/
//返回值:成功返回0,失败返回错误号
//参数:thread:你要分离的那个次线程的TID。
int pthread_detach(pthread_t thread);
这个函数的功能就是分离次线程
,让次线程在结束时自动回收资源
使用:
//次进程资源自动回收pthread_detach(globalVal.thr[i].thrId);
pthread_join
和pthread_detach
作为两种不同的线程资源回收方式
,只能二选一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
globalVal.pth_exit_flag[i]=PTHEXIT;pthread_cancel(globalVal.thr[i].thrId);}}else if(signo==SIGINT){exit(0);} }
signal(SIGINT,signal_fun);
#include
//注册函数
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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(;iglobalVal.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;iglobalVal.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;ipthread_join(globalVal.thr[i].thrId,&retval);printf("%ld\n",(long)retval);}#endif//父线程while(1){printf("fatherTid:%lu\n",pthread_self());//主线程TIDsleep(2);}return 0;
}