本章目标1.创建线程2.线程终止3.线程等待4.线程分离1.创建线程在Linux当中posix线程库当中提供了我们有关于线程的一系列的接口,但是这些接口都是基于Linux环境下设计的,以LWP为主的.它是不提供跨平台性的.但是市面上99%的服务器上都运行着Linux的环境的服务.如果我们想在window或者是mac上部署服务的话,我们可以使用c提供给我们的并发库的接口去使用.,c11的并发库的底层是封装了其他平台的系统调用做出来的.一套代码可以在不同的平台上跑.有关于c11并发库的东西,我们会在后续进行介绍.但是今天,我们只考虑Linux当中给我们提供的接口.在Linux当中我们创建线程所使用的接口是pthread_create,它一共有四个参数.1.第一个参数是pthread_t 类型的,它就是我们外部提供的tid,跟我们的进程的pid是向对应的它也是一个整数类型,不过它是被typedef出来的.它是用来标识一个线程的唯一标识.因为它并没有在内核当中维护.tcb这个结构,而是在用户栈当中的共享区去进行维护的.所以我们要在外部进行定义这个变量,方便,我们后续对线程进行等待,分离.2.第二个参数是线程的属性.我们一般如果是只在线程内部使用,没有任何其他的定制化需求的,我们一般是选择传nullptr,让它创建线程的时候选择默认的创建行为.3.第三个就是我们线程的入口函数.它是一格void*(ptr)(void)类型的函数指针.它用来指向,我们函数的的入口函数.但是我们可以从这个地方看出一点问题.我们这个线程的参数,在这里只有一个参数的位置.因为c并没有提供向可变模板参数,它自己的可变参数实现出来,由十分的复杂,我们在传多参数的时候,往往是把数据打包成一格类或者说是结构体的方式传进去,然后在内部在强转回来.而且定义这个结构体变量,我们一般是在堆区进行开辟的.因为这个数据是属于我们要交给子线程.它和主线程属于不同的执行流.我们为了防止栈上变量在主线程当中出作用域直接自动销毁.我们要传一个在堆上的空间.在子进程内部使用完之后再对这块区域进行free或者delete.在这里我们有关于这个地方入口函数的返回值,我们放到进程等待的地方去说第四个参数就是我们要传进去的参数,的指针.如果我们不需要传参数的话,我们一般是给nullptr.#includeiostream#includeunistd.h#includepthread.hstructdata{inta;intb;};void*hander(void*arg){data*d(data*)arg;std::coutd-ad-bstd::endl;deleted;returnnullptr;}intmain(){pthread_t tid;data*dnewdata{1,2};pthread_create(tid,nullptr,hander,(void*)d);sleep(100);return0;}我们是能够看到结果的,在这里我们一定要在主线程不让它走到return的地方.因为线程的创建也是需要时间的.我们要给他们时间去创建线程,同时让线程执行完自己的任务.2.线程终止在Linux当中线程既然能够进行创建就一定能够进行运行,我们线程也可以进行终止.这个终止并不是线程自己主动的去结束.而是当线程执行到一半的时候我们线程自己能主动的去终止线程.或者说是主线程去终止线程在Linux当中,给我们提供下面这些接口去实现线程的终止这个只有一格参数需要我们传进去对应线程的tid,这个就是我们线程自己的创建时候,我们传进去的第一个参数.但是对于子线程当中,它由如何拿到自己线程的tid呢?在Linux当中给我们提供相应的函数.pthread_self,它的参数没有.它的返回值就是当前所处线程的pid.#includeiostream#includeunistd.h#includepthread.hstructdata{inta;intb;};void*hander(void*arg){data*d(data*)arg;std::coutd-ad-bstd::endl;deleted;sleep(100);returnnullptr;}intmain(){pthread_t tid;data*dnewdata{1,2};pthread_create(tid,nullptr,hander,(void*)d);sleep(5);pthread_cancel(tid);sleep(100);return0;}我呢吧在这里在5秒后去取消这个我们创建的子线程,我们可以再另起一个终端去查看效果我们在左边能够看到这个效果,最开始只有两个线程,但是有一个线程最后被取消了.如果我们想在子线程当中自己取消自己,我们可以在合适的时机这么写pthread_cancel(pthread_self());即可,这里很简单,我们就不重复进行实验了,二者的效果实际上是一样的.对于线程取消,它的返回值,如果说是被成功终止取消了,那么它就会返回0,如果失败了就会返回一个非0的值3.线程等待我们在进程部分,我们进程退出的时候要对其子进程进行等待回收.如果我们不回收就会出现僵尸进程的问题.如果我们主进程长期不退出的话,就会出现非常严重的内存泄漏的问题.我们在过去有很多解决方法.除了老老实实的进行回收.还有去捕捉SIGCHILD这个信号.在这个信号捕捉的自定义函数内部实现一格非轮询等待的方式去进行回收.或者说是通过singal去忽略SIGCHILD这个信号的方式,让主进程自己去回收,也还有创建孙子进程,把实现逻辑放到孙子当中.让孙子变为孤儿进程,最后让操作系统统一进行回收.方式有很多.对于线程部分,在上面我们看到如果我们线程结束了,我们LWP确实是被内核新型回收了.但是实际上我们的线程也会出现类似于僵尸进程的问题.在上面进程创建,我们说过线程的入口函数会有一个返回值.我们如果有需要可以将子线程完成任务的情况.进行返回.但是这个返回结果我们要放到哪里?肯定不能够是LWP当中.这个东西一旦结束就会被回收.而是放到我们在上一格小节所说的TCB当中的.这个TCB是在整个进程地址空间当中的共享区当中进行维护的.也就是说它是被我们的posix原生线程库所管理起来的.我们即使不想要拿到这个退出信息,也要对这块空间进行回收.所以在Linux当中给我们提供了回收子进程的接口这个函数有两个参数,第一个就是我们回收线程的tid,二哥就是我们的返回值.它是一个void** 类型.因为我们返回来的类型是一格void* 类型的,对于指针类型来说,如果作为传进去,想要作为输出型参数带出来.它就需要升为二级指针.我们在这里同样可以定义一格结构表示我们的进程完成的结果.对于线程等待的返回值,我们如果成功返回会返回一个0,失败就会设置一格错误码,一般大于0#includeiostream#includeunistd.h#includepthread.hstructdata{inta;intb;};classreturnval{public:inttruecode;intresult;};void*hander(void*arg){data*d(data*)arg;std::coutd-ad-bstd::endl;returnval*retnewreturnval();ret-truecode1;ret-resultd-ad-b;deleted;return(void*)ret;}intmain(){pthread_t tid;data*dnewdata{1,2};pthread_create(tid,nullptr,hander,(void*)d);sleep(5);void*retnullptr;pthread_join(tid,ret);std::couttruecode is ((returnval*)ret)-truecode result is ((returnval*)ret)-resultstd::endl;delete(returnval*)ret;sleep(100);return0;}我们在子线程完成计算,然后通过子线程的带出计算结果,当线程成功回收,我们也就能够拿到这一块的退出信息了.4.线程分离如果我们不想对线程进行回收,我们不关系线程的返回值,我们想让它完成自己的任务后主动退出我们可以将线程设置为分离状态.我们就不要进行回收了• 默认情况下新创建的线程是joinable的线程退出后需要对其进⾏pthread_join操作否则⽆法释放资源从⽽造成系统泄漏。• 如果不关⼼线程的返回值join是⼀种负担这个时候我们可以告诉系统当线程退出时⾃动释放线程资源。#includestdio.h#includestdlib.h#includestring.h#includeunistd.h#includepthread.hvoid*thread_run(void*arg){pthread_detach(pthread_self());printf(%s\n,(char*)arg);returnNULL;}intmain(void){pthread_t tid;if(pthread_create(tid,NULL,thread_run,(void*)(thread1 run...))!0){printf(create thread error\n);return1;}intret0;sleep(1);//很重要要让线程先分离再等待if(pthread_join(tid,NULL)0){printf(pthread wait success\n);ret0;}else{printf(pthread wait failed\n);ret1;}returnret;}