hello~ 很高兴见到大家! 这次带来的是Linux系统中关于线程这部分的一些知识点,如果对你有所帮助的话,可否留下你宝贵的三连呢?个 人 主 页: 默|笙文章目录一、进程控制1.1 POSIX 线程库1.2 线程创建pthread_create函数线程资源共享的体现1.3 线程终止1.4 线程等待1.5 分离线程一、进程控制1.1 POSIX 线程库与POSIX 线程有关的所有函数构成了一个完整的系列它们绝大部分都以pthread_开头。POSIX 是一套操作系统接口标准让不同类 UNIX 系统Linux、macOS、BSD 等都能用同一套函数、同一套 API 应用程序编程接口写代码。要使用这些函数需要包含头文件pthread.h。编译链接这些线程函数库时需要在编译器命令中添加-lpthread选项现在推荐 -pthread选项会提供全套线程支持不只有线程库。因为该线程库并非标准 C 语言库部分旧版编译器无法自动识别此库而新版编译器已原生支持。1.2 线程创建pthread_create函数创建线程需要用到函数 pthread_create它的第一个参数需要传递一个用来存储线程 ID 变量的地址第二个参数用于指定线程属性这里暂不研究第三个参数是一个回调函数新线程创建成功后会立即执行该函数。这个函数的返回值为 void* 类型参数也是 void* 类型void* 表示通用指针可以接收任意类型指针使用时进行强制类型转换即可第四个参数是一个指针会作为参数传递给第三个参数对应的线程函数。pthread_t 就是 Linux 下用来表示线程 ID 的一个数据类型相当于线程的 “身份证号”。第一个参数的类型就得是 pthread_t。就跟进程 ID 类型是 pid_t 一样。pthread 系列函数的返回值规则统一执行成功返回 0执行失败则返回对应的非 0 错误码不会设置 errno。#includeiostream#includepthread.h#includeunistd.hvoid*Routine(void*args){std::string namestatic_castconstchar*(args);while(true){printf(new thread running..., pid : %d\n,getpid());sleep(1);}}intmain(){pthread_t tid;pthread_create(tid,nullptr,Routine,(void*)thread-new);while(true){printf(main thread running..., pid : %d, tid : 0x%lx\n,getpid(),tid);sleep(1);}return0;}通过上面的测试代码可以看到主线程和从线程的 pid 完全相同说明它们同属于一个进程。同时我们也成功将 pthread_create 的第四个参数传递给了线程函数 Routine 的形参进一步印证了该函数的功能。5. 但这里也出现了一个新问题为什么 pthread_t 类型的 tid 数值很大和我们常见的 LWP 完全不同LWP 是内核层面的轻量级进程而 tid 是用户层封装后的标识就像我们使用的 FILE 结构体内部封装了文件描述符一样。这个问题我们将留到下一篇博客中详细解答。给从线程所要执行的函数传参时pthread_create 支持通过 void通用指针传递任意类型的数据包括基本数据类型、自定义结构体甚至是自定义类对象 / 类指针*。我们可以将自定义类的实例或指针传递给线程让从线程基于这个类对象去执行对应的成员方法、处理专属任务。线程资源共享的体现#includeiostream#includepthread.h#includeunistd.h//#include cstdiovoid*Routine(void*args){std::string namestatic_castconstchar*(args);while(true){printf(new thread running..., name: %s\n,name.c_str());sleep(1);}}intmain(){constintnum10;for(inti0;i10;i){pthread_t tid;charthreadname[64];snprintf(threadname,sizeofthreadname,thread-%d,i1);intnpthread_create(tid,nullptr,Routine,(void*)threadname);(void)n;}while(true){printf(main thread running...\n);sleep(1);}return0;}这段代码我本来是想要创建10个线程并传递给它们不同的参数用来标识线程然后让它们都去执行对应的Routine函数可是为什么程序执行之后却只能看到线程4和线程10呢线程1、2、3等等线程怎么看不到这本质上就是线程之间的资源共享导致的问题同一进程内的所有线程共享该进程的虚拟地址空间主线程栈上的 threadname 数组对所有新线程都是可见的。我创建线程时传递的不是线程名的拷贝而只是这个数组的地址在线程 1、2、3 还没来得及执行对应的 Routine 函数时主线程就已经快速跑完多轮循环反复往同一个 threadname 里写入新内容把之前的线程名覆盖掉了。等到这些线程真正执行函数去读取数据时threadname 里的值已经被主线程改成了后面的内容比如 thread-4、thread-10所以前面的线程号就看不到了。要解决这个问题就必须给每个线程申请专属的独立内存空间比如用 new 为每个线程动态分配一块专属的字符串缓冲区避免所有线程共用主线程循环中被反复覆盖的栈数组。同时主线程循环里的 pthread_t tid 变量也会被不断覆盖导致无法正确保存所有线程 ID。因此我们可以使用 vector 数组来统一存储每一个线程 ID保证每个 ID 都被独立保存下来。通过给每个线程分配专属动态内存 用容器保存所有线程 ID就能彻底解决因为线程间资源共享、内存复用与覆盖而导致的参数错误问题。#includeiostream#includepthread.h#includevector#includeunistd.h//#include cstdiovoid*Routine(void*args){std::string namestatic_castconstchar*(args);while(true){printf(new thread running..., name: %s\n,name.c_str());sleep(1);}}intmain(){constintnum10;std::vectorpthread_ttids;for(inti0;i10;i){pthread_t tid;//char threadname[64];//snprintf(threadname, sizeof threadname, thread-%d, i 1);char*threadnamenewchar[64];sprintf(threadname,thread-%d,i1);intnpthread_create(tid,nullptr,Routine,(void*)threadname);(void)n;tids.push_back(tid);}for(autotid:tids)printf(tid: 0x%lx\n,tid);while(true){printf(main thread running...\n);sleep(1);}return0;}但是还是会存在一个问题也就是多个线程并发执行同一函数时的线程安全问题。多个线程同时进入并执行同一段函数代码如果函数内部访问了全局变量、静态变量等共享资源而又没有做同步保护线程调度的随机性就会让对共享数据的读写操作被打断、穿插执行最终引发数据不一致、逻辑错乱等问题。1.3 线程终止如果只需要终止某个线程不终止整个进程有以下三种方法线程执行完毕后正常 return该方法不适用于主线程—— 在 main 函数中 return 会直接终止整个进程。线程调用 pthread_exit 终止自己该方法适用于所有线程包括主线程主线程调用 pthread_exit 只会退出自身不会终止进程其他线程仍可继续运行。任意线程可以调用 pthread_cancel 向同一进程内的其他任意线程含主线程发送取消请求来终止线程。注意该操作仅终止目标线程本身不会导致整个进程终止只要进程内还有其他线程存活。pthread_exit 的用法其实跟 return 非常像可以就把它的参数理解为 return 返回的那个值。在子线程里面两个方法等价在主线程里面 return 会终止整个进程而 pthread_exit 不会终止整个进程只是会终止主线程。对于pthread_cancel函数只需要传入目标线程的 ID就能向其发送取消请求以终止线程。但在实际开发中一般不会直接使用 pthread_cancel 来终止线程。从线程不能够使用 exit 函数因为 exit 函数是用来终止整个进程而不是单个线程的。任意一个线程出现异常会导致整个进程挂掉。1.4 线程等待线程函数的返回值需要由等待它的线程使用线程等待函数 pthread_join 来获取。pthread_join 的第一个参数需要传入目标线程的线程 ID第二个参数是一个输出型参数由于线程返回值是 void * 类型想要接收这个返回值就需要传入一个 void* 变量的地址也就是上面看到的void**类型让函数通过这个地址来修改传入的变量的值从而拿到线程的返回值。子线程必须被主线程调用 pthread_join 等待因为子线程执行完毕后会进入类似进程中僵尸进程的状态占用内核资源需要由主线程来回收。而通过 pthread_join 拿回线程返回值只是次要目的。以下是一段测试代码#includeiostream#includepthread.h#includeunistd.hvoid*Routine(void*args){std::string namestatic_castconstchar*(args);printf(new thread running, name: %s, tid: 0x%lx, 即将退出\n,name.c_str(),pthread_self());//pthread_exit((void*)12);return(void*)12;}intmain(){pthread_t tid;pthread_create(tid,nullptr,Routine,(void*)thread-new);printf(新线程的id: 0x%lx\n,tid);void*retalnullptr;pthread_join(tid,retal);while(true){std::cout返回值(longlong)retalstd::endl;printf(main thread running..., tid: 0x%lx\n,pthread_self());sleep(2);}return0;}能够看到我们成功用 pthread_join 函数拿到了从线程执行的 routine 函数的返回值。这里再介绍一个函数 pthread_self它类似进程中的 getpid可以拿到当前调用它的线程自身的 TID。主线程通过 pthread_join 等待子线程的方式为阻塞等待。调用后主线程会一直阻塞挂起直到目标子线程退出才会继续向下执行。为什么 pthread_join 函数不像 waitpid 函数一样可以获取子线程的退出信号与退出状态它不需要判断子线程是否发生异常吗这是因为同一进程内的线程共享地址空间不具备独立性。一旦某个子线程出现异常比如段错误、除零错误整个进程都会直接被操作系统终止主线程根本来不及执行 pthread_join 去等待。而 waitpid 能够获取子进程的退出信息是因为进程之间相互独立子进程异常只会导致自身退出不会影响父进程父进程才有机会通过 waitpid 收集退出状态。它只用关心正常情况。1.5 分离线程默认情况下新创建的子线程是 joinable 的该线程退出后主线程需要对其进行等待pthread_join回收否则资源无法释放会造成内存泄漏。但如果不关心子线程的返回值那么 join 就是一种负担这个时候我们就可以高速系统该子线程执行完毕之后自动释放资源就好。也就是让线程被分离。线程库中有一个函数 pthread_detach专门用于将线程设置为分离状态。分离状态detached与可等待状态joinable是线程的两种互斥状态同一线程只能处于其中一种。使用时只用传入目标线程的 tid 就行。主线程也可以被分离但没什么用。今天的分享就到此结束啦,如果对读者朋友们有所帮助的话,可否留下宝贵的三连呢~~让我们共同努力, 一起走下去!