本篇目标1.线程库的引入与理解2.验证之前的概念3.知道如何创建线程终止线程等待线程和分离线程一.Linux线程控制1.引入线程库1.1.创建线程通过之前对线程的理解我们已经知道OS中有这么个执行流了那么如何验证它是真的存在呢那么就需要接下来的操作首先我们需要创建一个线程就需要用到pthread_create函数如代码原型 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);参数解析 :thread: 返回线程 ID注意在很多 Linux 环境下pthread_t底层可能是typedef unsigned long int pthread_t;attr: 设置线程的属性 attr 为 nullptr 表示使用默认属性start_routine: 是个函数地址线程启动后要执行的函数其实就是新线程要执行的函数入口arg: 传给线程启动函数的参数我们也可以在linux终端里面输入man pthread_create如图所示使用时要包含头文件pthread返回值成功返回 0 失败返回错误码。代码演示在test.cpp中#include iostream #include string #include unistd.h #include pthread.h void*threadrun(void*arg) { std::string namestatic_castconst char*(arg); int cnt10; while(cnt--) { std::coutI am namestd::endl; sleep(1); } return nullptr; } int main() { pthread_t tid; pthread_create(tid,nullptr,threadrun,(char*)new thread); int count10; while(count--) { std::coutI am main threadstd::endl; sleep(1); } return 0; }在makefile中test:test.cpp g -o $ $^ -stdc11 .PHONY:clean clean: rm -rf test结果如图这个为什么会报错呢其实是因为头文件找到了函数声明认识但链接阶段找不到函数实现也就是说g找不到phread_create函数的实现位置但真正的函数实现在线程库里所以链接时要加-lpthread此时结果1.2.线程库然后我们可以再重新开一个窗口重新执行上面这个可执行程序然后输入ps -aL就可以查看线程的具体信息了。此时的打印结果和另外一个窗口的输出解释其实这个打印出来的tid是通过pthread库中的函数pthread_self得到的它返回⼀个pthread_t类型的变量指代的是调⽤pthread_self函数的线程的“ID”。怎么理解这个“ID”呢这个“ID”是pthread库给每个线程定义的进程内唯⼀标识是由pthread库维持的。 由于每个进程有⾃⼰独⽴的内存空间故此“ID”的作⽤域是进程级⽽非系统级内核不认识。 其实pthread库也是通过内核提供的系统调用例如clone来创建线程的而内核会为每个线程创建系统全局唯⼀的“ID”来唯⼀标识这个线程。而这个ps -aL输出的结果我们可以明白的是PID毕竟两个线程同属于一个进程但是这个LWP又是什么东西呢其实这个LWP是缩写的全称是light weight process即轻量级进程之前使⽤ pthread_self 得到的这个数实际上是⼀ 个地址在虚拟地址空间上的⼀个地址通过这个地址可以找到关于这个线程的基本信息包括线 程ID线程栈寄存器等属性。在 ps-aL 得到的线程ID有⼀个线程ID和进程ID相同这个线程就是主线程主线程的栈在虚拟 地址空间的栈上而其他线程的栈在是在共享区堆栈之间因为pthread系列函数都是pthread库 提供给我们的。而pthread库是在共享区的。所以除了主线程之外的其他线程的栈都在共享区。那么CPU调度时到底是看lwp?还是pid呢其实在Linux 里CPU 调度时看的不是“进程 PID 整体”而是看具体的执行流也就是 LWP / 线程对应的调度实体也就是说Linux CPU 调度时真正调度的是 LWP / task_struct而不是抽象的整个进程。例如一个进程里面有 3 个线程进程 PID 1000 线程1 LWP 1000 线程2 LWP 1001 线程3 LWP 1002CPU 调度时不会说“我要调度 PID 1000 这个进程整体。”而是说“我要调度 1000 / 1001 / 1002 里面某一个具体执行流。”但是这里还有个疑问为什么会有这个库先看张图片解释Linux 内核本身并没有给用户直接提供一个“真正意义上的线程对象”。Linux 内核更核心的调度对象是task_struct / 轻量级进程 / LWP也就是说从 OS 角度看它不是说我创建了一个高级语言意义上的 thread。而是说我创建了一个可以被 CPU 调度的执行流这个执行流在内核中用 task_struct 描述。那么中间就需要一个东西把用户想要的“线程接口”翻译成 Linux 内核能理解的“轻量级进程创建”。这个东西就是pthread库它做的事情就是用户调用 pthread_create ↓ pthread 库封装参数、栈、属性、启动函数 ↓ 底层通过系统调用创建轻量级进程 / LWP ↓ OS 调度这个 LWP所以pthread库存在的原因就是用户想用线程但 Linux 内核底层提供的是轻量级进程所以需要 pthread 库在用户层把“线程接口”封装出来。总结Linux 内核并不直接向用户提供传统意义上的线程接口内核真正调度的是轻量级进程。可是从用户角度来看我们更希望使用“线程”这个概念来编写程序。因此在 user 和 OS 之间就需要 pthread 库对线程接口进行封装。用户调用 pthread_create 时实际上是通过 pthread 库进一步调用底层机制让 OS 创建出一个轻量级进程。也就是说pthread 库的作用就是在用户层提供线程接口在底层对接 Linux 的轻量级进程实现。2.线程控制的接口2.1.线程等待为什么需要线程等待• 已经退出的线程其空间没有被释放仍然在进程的地址空间内。• 创建新的线程不会复用刚才退出线程的地址空间。在终端里面输入man pthread_join即可查看线程等待的信息如图参数 :thread: 线程 IDvalue_ptr: 它指向⼀个指针后者指向线程的返回值返回值成功返回 0 失败返回错误码通过 pthread_join得到的终止状态是不同的总结如下:1.如果thread线程通过return返回value_ptr所指向的单元⾥存放的是thread线程函数的返回值。2. 如果thread线程被别的线程调用pthread_cancel异常终掉value_ptr所指向的单元⾥存放的是常数PTHREAD_CANCELED。3. 如果thread线程是⾃⼰调用pthread_exit终⽌的value_ptr所指向的单元存放的是传给 pthread_exit的参数。4. 如果对thread线程的终止状态不感兴趣可以传nullptr给value_ptr参数。当然等下的代码演示主要是以return 为主的demo如图#include iostream #include string #include unistd.h #include pthread.h void *threadrun( void *arg ) { printf(thread 1 returning ... \n); int*pnew int(1); return (void*)p; } int main() { pthread_t tid; pthread_create(tid,nullptr,threadrun,(char*)new thread); int count1; while(count--) { std::coutI am main threadstd::endl; sleep(1); } void*ptr; pthread_join(tid,ptr); printf(thread return, thread id:%lu, return code:%d\n, tid, *(int*)ptr); delete (int*)ptr; return 0; }输出结果2.2.线程终止如果需要只终止某个线程而不终止整个进程可以有三种⽅法:1. 从线程函数return。这种⽅法对主线程不适⽤从main函数return相当于调⽤exit。2. 线程可以调用pthread_exit终止自己但是不要在新线程执行的函数里面直接exit()否则这个进程是会直接退出的pthread_join也是接受不到退出码的例如#include iostream #include string #include unistd.h #include pthread.h void *threadrun( void *arg ) { printf(thread 1 returning ... \n); int*pnew int(1); exit(1); return (void*)p; } int main() { pthread_t tid; pthread_create(tid,nullptr,threadrun,(char*)new thread); int count1; while(count--) { std::coutI am main threadstd::endl; sleep(1); } void*ptr; pthread_join(tid,ptr); printf(thread return, thread id:%lu, return code:%d\n, tid, *(int*)ptr); delete (int*)ptr; return 0; }输出结果此时我们需要用到pthread_exit函数如图参数value_ptr线程退出时留给其他线程的返回值。#include iostream #include string #include unistd.h #include pthread.h void *threadrun( void *arg ) { printf(thread 1 returning ... \n); int*pnew int(3); pthread_exit((void*)p); } int main() { pthread_t tid; pthread_create(tid,nullptr,threadrun,(char*)new thread); int count1; while(count--) { std::coutI am main threadstd::endl; sleep(1); } void*ptr; pthread_join(tid,ptr); printf(thread return, thread id:%lu, return code:%d\n, tid, *(int*)ptr); delete (int*)ptr; return 0; }输出结果注意pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配因为当其它线程得到这个返回指针时线程函数已经退出了。3. ⼀个线程可以调⽤pthread_cancel终⽌同⼀进程中的另⼀个线程如图功能取消⼀个执⾏中的线程参数thread: 线程 ID返回值成功返回 0 失败返回错误码demo如图#include iostream #include string #include unistd.h #include pthread.h void *threadrun( void *arg ) { printf(thread 1 returning ... \n); int*pnew int(3); return (void*)p; } int main() { pthread_t tid; pthread_create(tid,nullptr,threadrun,(char*)new thread); pthread_cancel(tid); int count1; while(count--) { std::coutI am main threadstd::endl; sleep(1); } void*ptr; pthread_join(tid,ptr); printf(thread return, thread id:%lu, return code:%d\n, tid, *(int*)ptr); delete (int*)ptr; return 0; }执行结果原因我们调用了pthread_cancel(tid);但是线程被取消成功那么pthread_join拿到的返回值不是p而是PTHREAD_CANCELED它通常是一个特殊指针值类似(void*)-1所以你再执行*(int*)ptr就相当于访问非法地址自然可能报错。修改后的代码#include iostream #include string #include unistd.h #include pthread.h void *threadrun(void *arg) { printf(thread 1 returning ... \n); int *p new int(3); return (void *)p; } int main() { pthread_t tid; pthread_create(tid, nullptr, threadrun, (char *)new thread); pthread_cancel(tid); int count 1; while (count--) { std::cout I am main thread std::endl; sleep(1); } void *ptr; pthread_join(tid, ptr); if (ptr PTHREAD_CANCELED) { printf(thread was canceled, return: PTHREAD_CANCELED,ptr %p\n, ptr); } else { printf(thread return, thread id:%lu, return code:%d\n,(unsigned long)tid, *(int *)ptr); delete (int *)ptr; } return 0; }执行结果注意取消的时候一定要保证新线程已经启动了并且一般是主线程取消新线程而新线程取消主线程一般是不常用的。2.3.分离线程• 默认情况下新创建的线程是joinable的线程退出后需要对其进行pthread_join操作否则无法释放资源从而造成系统泄漏。• 如果不关心线程的返回值join是⼀种负担这个时候我们可以告诉系统当线程退出时⾃ 动释放线程资源。可以是线程组内其他线程对目标线程进行分离也可以是线程自己分离pthread_detach(pthread_self());如图参数thread: 线程 ID返回值成功返回 0 失败返回错误码demo如代码#include iostream #include string #include unistd.h #include pthread.h void *threadrun(void *arg) { printf(thread 1 returning ... \n); int *p new int(3); return (void *)p; } int main() { pthread_t tid; pthread_create(tid, nullptr, threadrun, (char *)new thread); pthread_detach(tid); int count 1; while (count--) { std::cout I am main thread std::endl; sleep(1); } void *ptr; pthread_join(tid, ptr); printf(thread return, thread id:%lu, return code:%d\n,(unsigned long)tid, *(int *)ptr); delete (int *)ptr; return 0; }执行结果原因pthread_join 和 pthread_detach两者冲突了所以想拿线程返回值就 join想让线程自动回收就 detach但 detach 后不能再 join。新代码#include stdio.h #include stdlib.h #include string.h #include unistd.h #include pthread.h void *thread_run(void *arg) { pthread_detach(pthread_self()); printf(%s\n, (char *)arg); return NULL; } int main(void) { pthread_t tid; int ret 0; if (pthread_create(tid, NULL, thread_run,(char*) thread1 run...) ! 0) { printf(create thread error\n); return 1; } // 很重要要先让新线程运行并在线程内部把自己设置为分离状态 sleep(1); if (pthread_join(tid, NULL) 0) { printf(pthread wait success\n); ret 0; } else { printf(pthread wait failed\n); ret 1; } return ret; }执行结果总结本篇主要学习了 Linux 线程控制相关接口。首先通过pthread_create创建线程验证了进程内部可以存在多个执行流。用户层使用的是 pthread 库提供的线程接口而 Linux 内核真正调度的是 LWP / task_struct因此 pthread 库起到了 user 和 OS 之间的封装作用。随后学习了pthread_join线程等待它可以等待线程退出并获取线程的返回值。线程终止可以通过return、pthread_exit或pthread_cancel实现其中线程被取消后pthread_join得到的返回值是PTHREAD_CANCELED不能当作普通指针解引用。最后学习了pthread_detach线程分离。默认线程是 joinable 状态需要 join 回收资源如果不关心线程返回值可以将线程设置为 detached 状态让系统在线程退出后自动回收资源。但需要注意pthread_join和pthread_detach不能同时使用。