Linux系统篇40——线程(五) 线程ID的真面目、共享验证

发布时间:2026/9/27 21:27:39
Linux系统篇40——线程(五) 线程ID的真面目、共享验证 本文收录于「流浪」的系列专栏Linux系统⚙️C数据结构与算法PythonLangChain LangGraph️MySQL 数据库Git 工具计算机网络AI大厂面试、八股学习筑基专栏 博客主页流浪 原创首发于 CSDN前言上一篇把线程造出来了pthread 库的由来、create 的四个参数、join 的等待闭环。但有几笔账还悬着——create第一个参数到底是啥、共享地址空间有没有实锤、线程跑完怎么收场。线程五把三笔一次算清ID 真相、共享验证。一、线程ID到底是什么1.1 两套ID各管各的到这里必须把 ID 说透因为线程有两套 ID长得还都不一样LWP内核视角的真 IDps -aL看到的那列内核调度就认它pthread_tpthread 库给的进程内标识pthread_create 带出来的、pthread_join 传进去的都是它内核不认识 pthread_t库不参与调度——两套体系各管各的。操作线程用 pthread_tcreate/join/self观察线程用 LWPps -aL、top -H。1.2 pthread_t的本质是一个地址打印过 pthread_t 的同学会发现它是一串很长的数字而且完全不像 LWP 那样从 1 开始规规矩矩。为什么intmain(){pthread_t tid;pthread_create(tid,nullptr,newidea,(void*)pthread-1);std::couttidstd::endl;return0;}因为pthread_t 的本质是一个虚拟地址——它指向线程的 TCB线程控制块也就是上一篇说过的、库在用户层维护的那个控制结构。printf(0x%ld\n,tid);上一篇留的问题正面回答来了——tid 为什么是一串长数字、不直接暴露 LWP线程是库封装出来的线程的管理信息栈指针、状态、返回值暂存都在库手里的 TCB 里pthread_t 指向 TCB 就是拿到了线程的全部家底库处理起来最顺LWP 是内核的调度编号暴露出去除了看一眼什么也干不了——内核的编号体系不该泄漏到用户层分层就该分干净地址天然是进程内唯一的拿地址当 ID不重号、好比较、还能直接定位控制块——一举三得。1.3 pthread_self谁调用返回谁pthread_tpthread_self(void);返回调用者自己的线程标识——新线程想知道自己是谁在入口函数里调 pthread_self主线程调它拿到的是主线程的。和 getpid 一样的定位我是谁问自己。voidhello(){intcnt5;while(cnt){std::cout我是新线程我的pid是getpid()我的tid是pthread_self()std::endl;sleep(1);cnt--;}}注意它返回的同样是 pthread_t不是 LWP。想看内核视角的编号得走ps -aL或者系统层的 gettid那是另一套体系。1.4 库在哪栈在哪TCB在哪把线程三4.2 的账补全——说了子线程栈是库 mmap 的 8MB那这些东西在地址空间的哪个位置pthread 库本身映射在共享区地址空间里堆栈之间的那段随进程加载进来除主线程外线程栈也在共享区——库在共享区里给每条新线程 mmap 一块栈再把 TCB、栈、返回值暂存的位置都登记好。主线程是例外它的栈就是进程地址空间里正经的栈区进程创建时就有了不经库的手。TCB 里的result 字段专门放线程的返回值——上一篇 3.3 说的「暂存在 TCB 里」落的就是这里线程函数 return值进 resultjoin 到来值从 result 出来线程退出了 TCB 还在直到被 join 才连同栈一起释放。二、一个共享验证和两个悬而未决的问题2.1 flag实验共享的直接证据前四篇讲了大量「线程共享地址空间」拿个实验钉死它intflag100;#includethreadvoidhello(){intcnt5;while(cnt){sleep(1);flag;cnt--;}}intmain(){std::threadt(hello);intcnt5;while(cnt){std::coutflag:flagstd::endl;sleep(1);cnt--;}t.join();return0;}主线程打印出来的值是子线程改过的——全局变量在线程之间是共享的。谁改的别人看得见不需要任何进程间通信手段因为大家本来就在同一个地址空间里。函数也一样——所有线程调的都是同一份代码全局函数天生共享。线程三4.4 那张收拢表跟容器走的共享、跟执行流走的私有在这里有了实验证据。2.2 多个线程同时打印时显示器为什么乱共享的不只是内存。显示器就是一种共享资源——它背后是文件标准输出的文件描述符文件描述符表跟容器走两个线程都往上面打印谁的输出都在同一个屏幕上挤。两个线程同时 printf输出互相穿插看起来乱糟糟——这不是 bug这是共享的直接后果。fd 表共享的根基线程三4.4 讲过这里兑现成肉眼可见的现象。voidhello(){intcnt5;while(cnt){std::cout我是子线程std::endl;sleep(1);cnt--;}}intmain(){std::threadt(hello);intcnt5;while(cnt){std::cout我是主线程std::endl;sleep(1);cnt--;}t.join();return0;}怎么让打印不乱加锁让打印变成原子操作——这就是线程同步互斥要解决的问题下一篇的主角。先记住现象和根子乱是因为共享。2.3 健壮性低和复用结构更健壮为什么不矛盾篇36 留过一对看似打架的结论这里正面回答篇36 说 Linux复用进程结构模拟线程设计更健壮——内核只维护一套 task_struct 的代码调度路径统一出错面小线程三说线程健壮性低——一个线程崩全进程崩矛盾吗不矛盾两句话说的是两个层面。Linux 复用结构的「健壮」说的是内核实现层一套代码同时管进程和线程不用像 Windows 那样维护 EPROCESS/ETHREAD 两套结构内核代码简单、路径统一天然不容易出内核级 bug。线程的「健壮性低」说的是应用层线程之间共享地址空间、没有隔离墙应用代码一个线程出错殃及整个进程。一个是内核设计得干净一个是应用层共享的代价——前者夸内核后者提醒用户。内核越简单可靠越能把复杂的共享语义留给用户层自己兜底两句话合起来正好是线程的完整画像。三、全篇总结一条线收拢ID 真相LWP 是内核真 IDpthread_t 是库给的标识本质指向 TCB 的地址——两套体系各管各的共享验证flag 实验钉死全局共享显示器乱是 fd 共享的直观后果健壮性的两个说法分属内核层和应用层不矛盾四、文末面试题4.1 推导题(按本讲知识点,附答案)先自己想再看答案——答案都用本章的逻辑推不引入新知识。【推导】pthread_t 和 LWP 是什么关系答:没有从属关系是两套独立体系。LWP 是内核视角的真 IDps -aL 可见调度认它pthread_t 是库给的进程内标识本质是虚拟地址指向库维护的 TCB内核不认识。操作线程用 pthread_t观察线程用 LWP。4.2 真题(来源已核实,转述注明)pthread_self 返回的 ID 和 gettid 返回的 ID 有什么区别答:pthread_self 返回线程库分配的进程内标识glibc 下是 TCB 相关的虚拟地址仅进程内有效gettid 返回内核分配的线程 ID即 LWP全系统唯一。两者不是一回事观察用 gettid/ps -aL操作用 pthread_self 的返回值。【真题·转述自 Techeasy 博客《Pthread - Lets create, join and detach threads》Thread ID returned by pthread_self and gettid 一节】线程不 join 也不 detach 会怎样怎么选答:默认 joinable 状态下资源永久保留长期运行内存泄漏直至无法创建新线程。要拿返回值用 join阻塞、可回收状态后台任务用 detach非阻塞、自动回收、拿不到返回值二选一必须选一个。【真题·转述自 CSDN 博客《Linux系统编程_thread_内存泄漏》】 pthread_t 的地址真相、flag 实验钉死共享。两个线程同时往屏幕打印的乱象你也见过。觉得有收获点个赞再走呗。