当前位置: 首页 > news >正文

广州优质网站排名公司南昌专业的企业网站建设公司

广州优质网站排名公司,南昌专业的企业网站建设公司,网站颜色搭配实例,wordpress文章标题总有网站名文章目录 引言一、Linux 线程概念1.1 什么是线程1.2 分页式存储管理1.2.1 虚拟地址和页表的由来1.2.2 物理内存管理struct page 的主要用途 1.2.3 页表1.2.4 页目录结构1.2.5 两级页表的地址转换1.2.6 缺页异常 1.3 线程的优点1.4 线程缺点1.5 线程异常1.6 线程用途 二、Linux进… 文章目录 引言一、Linux 线程概念1.1 什么是线程1.2 分页式存储管理1.2.1 虚拟地址和页表的由来1.2.2 物理内存管理struct page 的主要用途 1.2.3 页表1.2.4 页目录结构1.2.5 两级页表的地址转换1.2.6 缺页异常 1.3 线程的优点1.4 线程缺点1.5 线程异常1.6 线程用途 二、Linux进程VS线程2.1 进程和线程2.2 进程的多个线程共享2.3 关于进程线程的问题 三、Linux线程控制3.1 POSIX线程库3.2 创建线程3.2.1 基本用法示例3.2.2 线程属性pthread_attr_t 3.3 线程终止3.3.1 函数原型与参数3.3.2 基本用法示例3.3.3 核心作用与使用场景3.3.4 pthread_exit() 与 return 的区别 3.4 线程等待3.4.1 函数原型与参数3.4.2 基本用法示例3.4.3 核心作用与使用场景3.4.4 资源回收机制3.4.5 错误处理与常见错误码3.4.6 注意事项 3.5 分离线程3.5.1 函数原型与参数3.5.2 基本用法示例3.5.3 核心作用与使用场景3.5.4 与 pthread_join() 的对比3.5.5 设置分离状态的两种方式3.5.6 错误处理与常见错误码3.5.7 注意事项 四、线程ID及进程地址空间布局4.1 pthread_self 函数4.1.1 函数原型与参数4.1.2 基本用法示例4.1.3 核心作用与使用场景4.1.4 线程 ID 的特性4.1.5 线程 ID 的比较pthread_equal4.1.6 注意事项 引言 在现代操作系统中多线程编程是提升程序并发能力的关键技术。Linux 通过轻量级进程LWP实现线程机制将进程资源与执行流分离使得多个线程可共享同一地址空间并独立调度。本文从底层内存管理出发深入剖析分页式存储、页表结构与struct page的设计原理进而探讨线程与进程的本质区别。同时结合 POSIX 线程库的核心函数如pthread_create、pthread_join等解析线程创建、终止、等待及资源管理的全流程帮助开发者理解 Linux 线程的实现逻辑与编程实践要点。 一、Linux 线程概念 1.1 什么是线程 在一个程序里的一个执行路线就叫做线程(thread更准确的定义是线程是“一个进程内部的控制序列”一切进程至少都有一个执行线程线程在进程内部运行本质是在进程地址空间内运行在Linux系统中在CPU眼中看到的PCB都要比传统的进程更加轻量化透过进程虚拟地址空间可以看到进程的大部分资源将进程资源合理分配给每个执行流就形成了线程执行流 1.2 分页式存储管理 1.2.1 虚拟地址和页表的由来 思考一下如果在没有虚拟内存和分页机制的情况下每一个用户程序在物理内存上所对应的空间必须是连续的如下图 因为每一个程序的代码、数据长度都是不一样的按照这样的映射方式物理内存将会被分割成各种离散的、大小不同的块。经过一段运行时间之后有些程序会退出那么它们占据的物理内存空间可以被回收导致这些物理内存都是以很多碎片的形式存在。 怎么办呢我们希望操作系统提供给用户的空间必须是连续的但是物理内存最好不要连续。此时虚拟内存和分页便出现了如下图所示 把物理内存按照一个固定的长度的页框进行分割有时叫做物理页。每个页框包含一个物理页(page)。一个页的大小等于页框的大小。大多数32位体系结构支持4KB的页而64位体系结构一般会支持8KB的页。区分一页和一个页框是很重要的 页框是一个存储区域而页是一个数据块可以存放在任何页框或磁盘中。 有了这种机制CPU便并非是直接访问物理内存地址而是通过虚拟地址空间来间接的访问物理内存地址。所谓的虚拟地址空间是操作系统为每一个正在执行的进程分配的一个逻辑地址在32位机上其范围从04G-1。 操作系统通过将虚拟地址空间和物理内存地址之间建立映射关系也就是页表这张表上记录了每一对页和页框的映射关系能让CPU间接的访问物理内存地址。 总结一下其思想是将虚拟内存下的逻辑地址空间分为若干页将物理内存空间分为若干页框通过页表便能把连续的虚拟内存映射到若干个不连续的物理内存页。这样就解决了使用连续的物理内存造成的碎片问题。 1.2.2 物理内存管理 假设一个可用的物理内存有4GB的空间。按照一个页框的大小4KB进行划分4GB的空间就是4GB/4KB1048576个页框。有这么多的物理页操作系统肯定是要将其管理起来的操作系统需要知道哪些页正在被使用哪些页空闲等等。 内核用struct page结构表示系统中的每个物理页出于节省内存的考虑struct page中使用了大量的联合体union。 struct page {unsigned long flags; // 页状态标志位如PG_locked、PG_uptodate等atomic_t _count; // 引用计数表示有多少地方正在使用该页union {struct { // 用于页缓存Page Cachestruct list_head lru; // 链表节点用于LRU队列struct address_space *mapping; // 映射的地址空间pgoff_t index; // 在映射中的偏移量};struct { // 用于匿名页未映射到文件struct mm_struct *mapping; // 指向所属的内存描述符void *virtual; // 虚拟地址如果已映射};};struct page *next_hash; // 哈希表指针用于页缓存哈希... };关键字段解析 flags 包含多个状态位如 PG_locked页被锁定不可交换或修改。PG_uptodate页数据已更新无需从磁盘读取。PG_dirty页数据已修改需写回磁盘。 _count 引用计数为 0 时表示页未被使用可被回收。 mapping 对于文件映射页指向文件的地址空间address_space。对于匿名页如堆、栈指向进程的内存描述符mm_struct。 struct page 的主要用途 内存管理 页分配与回收内核通过 struct page 跟踪空闲页框使用伙伴系统Buddy System分配和回收物理页。页置换当内存不足时内核根据 struct page 的状态如是否脏页、最近使用情况选择置换页。 页缓存Page Cache 用于缓存磁盘文件数据加速文件读写。struct page 通过 mapping 和 index 字段关联文件偏移实现文件内容与物理页的映射。 虚拟内存管理 匿名页如进程堆、栈通过 struct page 与进程的虚拟地址空间关联。当发生缺页异常时内核通过 struct page 分配物理页并建立映射。 内存碎片整理 内核通过 struct page 检测和整理内存碎片提高连续内存分配成功率。 1.2.3 页表 页表中的每一个表项指向一个物理页的开始地址。在32位系统中虚拟内存的最大空间是4GB这是每一个用户程序都拥有的虚拟内存空间。既然需要让4GB的虚拟内存全部可用那么页表中就需要能够表示这所有的4GB空间那么就一共需要4GB/4KB1048576个表项。如下图所示 虚拟内存看上去被虚线“分割”成一个个单元其实并不是真的分割虚拟内存仍然是连续的。这个虚线的单元仅仅表示它与页表中每一个表项的映射关系并最终映射到相同大小的一个物理内存页上。 页表中的物理地址与物理内存之间是随机的映射关系哪里可用就指向哪里物理页)。虽然最终使用的物理内存是离散的但是与虚拟内存对应的线性地址是连续的。处理器在访问数据、获取指令时使用的都是线性地址只要它是连续的就可以了最终都能够通过页表找到实际的物理地址。 在32位系统中地址的长度是4个字节那么页表中的每一个表项就是占用4个字节。所以页表占据的总空间大小就是1048576*44MB的大小。也就是说映射表自己本身就要占用4MB/4KB1024个物理页。这会存在哪些问题呢? 回想一下当初为什么使用页表就是要将进程划分为一个个页可以不用连续的存放在物理内存中但是此时页表就需要1024个连续的页框似乎和当时的目标有点背道而驰了…此外根据局部性原理可知很多时候进程在一段时间内只需要访问某几个页就可以正常运行了。因此也没有必要一次让所有的物理页都常驻内存。 解决需要大容量页表的最好方法是把页表看成普通的文件对它进行离散分配即对页表再分页由此形成多级页表的思想。 为了解决这个问题可以把这个单一页表拆分成1024个体积更小的映射表。如下图所示。这样一来1024(每个表中的表项个数)*1024(表的个数)仍然可以覆盖4GB的物理内存空间。 这里的每一个表就是真正的页表所以一共有1024个页表。一个页表自身占用4KB那么1024个页表一共就占用了4MB的物理内存空间和之前没差别啊? 从总数上看是这样但是一个应用程序是不可能完全使用全部的4GB空间的也许只要几十个页表就可以了。例如一个用户程序的代码段、数据段、栈段一共就需要10MB的空间那么使用3个页表就足够了。 计算过程 每一个页表项指向一个4KB的物理页那么一个页表中1024个页表项一共能覆盖4MB的物理内存;那么10MB的程序向上对齐取整之后(4MB的倍数就是12MB)就需要3个页表就可以了。 1.2.4 页目录结构 到目前为止每一个页框都被一个页表中的一个表项来指向了那么这1024个页表也需要被管理起来。管理页表的表称之为页目录表形成二级页表。如下图所示 所有页表的物理地址被页目录表项指向页目录的物理地址被CR3寄存器指向这个寄存器中保存了当前正在执行任务的页目录地址。 所以操作系统在加载用户程序的时候不仅仅需要为程序内容来分配物理内存还需要为用来保存程序的页目录和页表分配物理内存。 1.2.5 两级页表的地址转换 下面以一个逻辑地址为例。将逻辑地址00000000000000000001·11111111111转换为物理地址的过程 在32位处理器中采用4KB的页大小则虚拟地址中低12位为页偏移剩下高20位给页表分成两级每个级别占10个bit1010)。CR3 寄存器读取页目录起始地址再根据一级页号查页目录表找到下一级页表在物理内存中存放位置。根据二级页号查表找到最终想要访问的内存块号。结合页内偏移量得到物理地址。 注一个物理页的地址一定是4KB对齐的(最后的12位全部为0所以其实只需要记录物理页地址的高 20 位即可。以上其实就是MMU的工作流程。MMU(MemoryManage Unit)是一种硬件电路其速度很快主要工作是进行内存管理地址转换只是它承接的业务之一。 到这里其实还有个问题MMU要先进行两次页表查询确定物理地址在确认了权限等问题后MMU再将这个物理地址发送到总线内存收到之后开始读取对应地址的数据并返回。那么当页表变为N级时就变成了N次检索1次读写。可见页表级数越多查询的步骤越多对于CPU来说等待时间越长效率越低。 让我们现在总结一下单级页表对连续内存要求高于是引入了多级页表但是多级页表也是一把双刃剑在减少连续存储要求且减少存储空间的同时降低了查询效率。 有没有提升效率的办法呢计算机科学中的所有问题都可以通过添加一个中间层来解决。MMU引入了新武器江湖人称快表的TLB其实就是缓存) 当CPU给MMU传新虚拟地址之后MMU先去问TLB那边有没有如果有就直接拿到物理地址发到总线给内存齐活。但TLB容量比较小难免发生Cache Miss这时候MMU还有保底的老武器页表在页表中找到之后MMU除了把地址发到总线传给内存还把这条映射关系给到TLB让它记录一下刷新缓存。 1.2.6 缺页异常 设想CPU给MMU的虚拟地址在TLB和页表都没有找到对应的物理页该怎么办呢其实这就是缺页异常Page Fault它是一个由硬件中断触发的可以由软件逻辑纠正的错误。 假如目标内存页在物理内存中没有对应的物理页或者存在但无对应权限CPU就无法获取数据这种情况下CPU就会报告一个缺页错误。 由于CPU没有数据就无法进行计算CPU罢工了用户进程也就出现了缺页中断进程会从用户态切换到内核态并将缺页中断交给内核的Page Fault Handler处理。 缺页中断会交给 Page Fault Handler 处理其根据缺页中断的不同类型会进行不同的处理 Hard Page Fault也被称为Major Page Fault翻译为硬缺页错误/主要缺页错误这时物理内存中没有对应的物理页需要CPU打开磁盘设备读取到物理内存中再让MMU建立虚拟地址和物理地址的映射。SoftPage Fault也被称为Minor PageFault翻译为软缺页错误/次要缺页错误这时物理内存中是存在对应物理页的只不过可能是其他进程调入的发出缺页异常的进程不知道而已此时MMU只需要建立映射即可无需从磁盘读取写入内存一般出现在多进程共享内存区域。Invalid Page Fault翻译为无效缺页错误比如进程访问的内存地址越界访问又比如对空指针解引l用内核就会报segment fault错误中断进程直接挂掉。 1.3 线程的优点 创建一个新线程的代价要比创建一个新进程小得多与进程之间的切换相比线程之间的切换需要操作系统做的工作要少很多 最主要的区别是线程的切换虚拟内存空间依然是相同的但是进程切换是不同的。这两种上下文切换的处理都是通过操作系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出。另外一个隐藏的损耗是上下文的切换会扰乱处理器的缓存机制。简单的说一旦去切换上下文处理器中所有已经缓存的内存地址一瞬间都作废了。还有十个显著的区别是当你改变虚拟内存空间的时候处理的页表缓冲TLB快表会被全部刷新这将导致内存的访问在一段时间内相当的低效。但是在线程的切换中不会出现这个问题当然还有硬件cache。 线程占用的资源要比进程少很能充分利用多处理器的可并行数量在等待慢速I/O操作结束的同时程序可执行其他的计算任务计算密集型应用为了能在多处理器系统上运行将计算分解到多个线程中实现I/O密集型应用为了提高性能将I/O操作重叠。线程可以同时等待不同的I/O操作。 1.4 线程缺点 性能损失 一个很少被外部事件阻塞的计算密集型线程往往无法与其它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多那么可能会有较大的性能损失这里的性能损失指的是增加了额外的同步和调度开销而可用的资源不变。 健壮性降低 编写多线程需要更全面更深入的考虑在一个多线程程序里因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的换句话说线程之间是缺乏保护的。 缺乏访问控制 进程是访问控制的基本粒度在一个线程中调用某些OS函数会对整个进程造成影响。 编程难度提高 编写与调试一个多线程程序比单线程程序困难得多 1.5 线程异常 单个线程如果出现除零野指针问题导致线程崩溃进程也会随着崩溃线程是进程的执行分支线程出异常就类似进程出异常进而触发信号机制终止进程进程终止该进程内的所有线程也就随即退出 1.6 线程用途 合理的使用多线程能提高CPU密集型程序的执行效率合理的使用多线程能提高IO密集型程序的用户体验如生活中我们一边写代码一边下载开发工具就是多线程运行的一种表现 二、Linux进程VS线程 2.1 进程和线程 进程是资源分配的基本单位线程是调度的基本单位线程共享进程数据但也拥有自己的一部分数据 线程ID一组寄存器栈errno信号屏蔽字调度优先级 2.2 进程的多个线程共享 同一地址空间因此Text Segment、Data Segment都是共享的如果定义一个函数,在各线程中都可以调用如果定义一个全局变量在各线程中都可以访问到除此之外各线程还共享以下进程资源和环境 文件描述符表每种信号的处理方式(SIG_IGN、SIG_DFL或者自定义的信号处理函数)当前工作目录用户id和组id 进程和线程的关系如下图 2.3 关于进程线程的问题 如何看待之前学习的单进程具有一个线程执行流的进程 三、Linux线程控制 3.1 POSIX线程库 与线程有关的函数构成了一个完整的系列绝大多数函数的名字都是以“pthread_”打头的要使用这些函数库要通过引l入头文pthread.h链接这些线程函数库时要使用编译器命令的 -lpthread 选项 3.2 创建线程 #include pthread.hint pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);参数说明 thread输出参数指向 pthread_t 类型的变量用于存储新创建线程的标识符。attr线程属性设置通常传入 NULL 使用默认属性。start_routine线程入口函数类型为 void* (*)(void*)即返回值和参数均为 void* 的函数。arg传递给线程入口函数的参数需强制转换为 void*。 返回值 成功时返回 0失败时返回错误码如 EAGAIN、EINVAL 等不设置 errno。 错误处理 pthread_create() 失败时返回错误码常见错误 EAGAIN系统资源不足无法创建新线程。EINVALattr 参数无效如栈大小设置不合理。EPERM没有权限设置指定的 attr。 3.2.1 基本用法示例 以下代码展示如何创建一个打印信息的线程 #include stdio.h #include pthread.h// 线程入口函数 void* print_message(void* arg) {char* message (char*)arg; // 转换参数类型printf(线程输出: %s\n, message);return NULL; // 线程正常结束 }int main() {pthread_t thread_id;char* message Hello from pthread!;// 创建线程if (pthread_create(thread_id, NULL, print_message, (void*)message) ! 0) {perror(pthread_create failed);return 1;}// 等待线程结束可选if (pthread_join(thread_id, NULL) ! 0) {perror(pthread_join failed);return 1;}printf(主线程继续执行\n);return 0; }3.2.2 线程属性pthread_attr_t 通过 pthread_attr_init() 和 pthread_attr_set*() 函数设置常用属性包括 分离状态Detach State PTHREAD_CREATE_JOINABLE默认主线程需通过 pthread_join() 等待线程结束。PTHREAD_CREATE_DETACHED线程结束后自动释放资源无法被 join。 pthread_attr_t attr; pthread_attr_init(attr); pthread_attr_setdetachstate(attr, PTHREAD_CREATE_DETACHED);栈大小Stack Sizesize_t stack_size 1024 * 1024; // 1MB pthread_attr_setstacksize(attr, stack_size);3.3 线程终止 如果需要只终止某个线程而不终止整个进程可以有三种方法 从线程函数return。这种方法对主线程不适用从main函数return相当于调用exit。线程可以调用pthread_exit终止自己。一个线程可以调用pthread_cancel终止同一进程中的另一个线程。 3.3.1 函数原型与参数 #include pthread.hvoid pthread_exit(void *retval);参数说明 retval线程的返回值类型为 void*可被等待该线程的其他线程通过pthread_join() 获取。若无需返回值可传入 NULL。 返回值 无返回值void调用后当前线程立即终止。 3.3.2 基本用法示例 以下代码展示线程主动退出并传递返回值 #include stdio.h #include stdlib.h #include pthread.h// 线程入口函数 void* thread_function(void* arg) {int* data (int*)arg;printf(线程收到参数: %d\n, *data);// 动态分配内存用于返回值int* result malloc(sizeof(int));*result (*data) * 2;// 线程主动退出并传递返回值pthread_exit((void*)result); }int main() {pthread_t thread_id;int input 42;void* retval;// 创建线程if (pthread_create(thread_id, NULL, thread_function, input) ! 0) {perror(pthread_create failed);return 1;}// 等待线程结束并获取返回值if (pthread_join(thread_id, retval) ! 0) {perror(pthread_join failed);return 1;}printf(线程返回值: %d\n, *(int*)retval);free(retval); // 释放动态分配的内存return 0; }3.3.3 核心作用与使用场景 线程主动退出 当线程完成任务后可调用 pthread_exit() 立即终止自身。 传递返回值 通过 retval 参数向等待的线程传递结果如计算结果、状态码。 资源清理 在退出前释放线程持有的资源如锁、文件描述符但需注意 线程局部存储Thread-Local Storage, TLS会自动释放。若线程是分离状态PTHREAD_CREATE_DETACHEDretval 会被忽略。 3.3.4 pthread_exit() 与 return 的区别 对比项pthread_exit()return作用范围仅终止当前线程终止整个函数若为主线程则进程退出返回值传递通过 retval 传递给 pthread_join()返回值类型需与线程函数声明一致执行上下文可在线程函数的任何位置调用只能从线程函数的顶层返回对主线程的影响主线程退出但进程不终止其他线程继续运行主线程返回会导致进程终止所有线程退出 示例对比 // 使用 pthread_exit() void* thread_func(void* arg) {// ... 执行任务 ...pthread_exit(NULL); // 仅当前线程退出 }// 使用 return void* thread_func(void* arg) {// ... 执行任务 ...return NULL; // 效果同上但主线程使用 return 会终止进程 }3.4 线程等待 为什么需要线程等待? 已经退出的线程其空间没有被释放仍然在进程的地址空间内。创建新的线程不会复用刚才退出线程的地址空间。 3.4.1 函数原型与参数 #include pthread.hint pthread_join(pthread_t thread, void **retval);参数说明 thread目标线程的标识符由 pthread_create() 返回。retval输出参数指向 void* 的指针用于存储目标线程的返回值即 pthread_exit() 或 return 的参数。若无需获取返回值可传入 NULL。 返回值 成功时返回 0失败时返回错误码如 EDEADLK、ESRCH 等。 3.4.2 基本用法示例 以下代码展示如何等待线程结束并获取返回值 #include stdio.h #include pthread.h #include stdlib.h// 线程入口函数 void* calculate_sum(void* arg) {int* numbers (int*)arg;int sum numbers[0] numbers[1];// 动态分配内存存储结果int* result malloc(sizeof(int));*result sum;pthread_exit((void*)result); // 等价于 return (void*)result; }int main() {pthread_t thread_id;int numbers[2] {3, 5};void* retval;// 创建线程if (pthread_create(thread_id, NULL, calculate_sum, numbers) ! 0) {perror(pthread_create failed);return 1;}// 等待线程结束并获取返回值if (pthread_join(thread_id, retval) ! 0) {perror(pthread_join failed);return 1;}printf(计算结果: %d\n, *(int*)retval);free(retval); // 释放动态分配的内存return 0; }3.4.3 核心作用与使用场景 同步线程执行顺序 主线程通过 pthread_join() 等待工作线程完成任务确保数据处理的顺序性。 获取线程返回值 通过 retval 参数接收目标线程的返回值如计算结果、状态码。 资源回收 回收线程的栈空间、线程描述符等资源避免成为僵尸线程。 3.4.4 资源回收机制 僵尸线程Zombie Thread 线程结束后通过 pthread_exit() 或 return若未被 join其资源不会被释放成为僵尸线程。僵尸线程会占用系统资源如线程 ID、内存长期运行可能导致系统资源耗尽。 pthread_join() 的作用 阻塞调用线程直到目标线程结束。回收目标线程的资源。通过 retval 获取目标线程的返回值。 3.4.5 错误处理与常见错误码 EDEADLK检测到死锁如线程尝试等待自身。ESRCH指定的线程 ID 不存在或已被 join。EINVAL线程已被设置为分离状态PTHREAD_CREATE_DETACHED。 推荐处理方式 int ret pthread_join(thread_id, retval); if (ret ! 0) {fprintf(stderr, pthread_join failed, error: %d\n, ret);// 进一步处理... }3.4.6 注意事项 避免重复 join 对同一线程多次调用 pthread_join() 会导致未定义行为通常返回 ESRCH。内存管理 若线程返回值指向动态分配的内存如 malloc接收方需负责释放避免内存泄漏。避免返回指向线程栈的指针因为线程退出后栈空间会被回收。 性能考量 大量线程频繁 join 可能导致性能开销可考虑使用线程池减少线程创建 / 销毁次数。 3.5 分离线程 默认情况下新创建的线程是joinable的线程退出后需要对其进行pthread_join操作否则无法释放资源从而造成系统泄漏。如果不关心线程的返回值join是一种负担这个时候我们可以告诉系统当线程退出时自动释放线程资源。 3.5.1 函数原型与参数 #include pthread.hint pthread_detach(pthread_t thread);参数说明 thread目标线程的标识符由 pthread_create() 返回。 返回值 成功时返回 0失败时返回错误码如 ESRCH、EINVAL 等。 3.5.2 基本用法示例 以下代码展示如何将线程设置为分离状态 #include stdio.h #include pthread.h #include unistd.h// 线程入口函数 void* background_task(void* arg) {printf(后台任务开始执行...\n);sleep(2); // 模拟耗时操作printf(后台任务完成\n);pthread_exit(NULL); }int main() {pthread_t thread_id;// 创建线程if (pthread_create(thread_id, NULL, background_task, NULL) ! 0) {perror(pthread_create failed);return 1;}// 将线程设置为分离状态if (pthread_detach(thread_id) ! 0) {perror(pthread_detach failed);return 1;}printf(主线程继续执行不等待后台任务\n);// 主线程可继续执行其他任务...sleep(5); // 防止主线程过早退出return 0; }3.5.3 核心作用与使用场景 自动资源回收 分离状态的线程结束后系统自动回收其资源如栈空间、线程描述符无需其他线程干预。 后台任务 适用于无需返回值的后台任务如日志记录、监控避免主线程阻塞等待。 减少资源泄漏风险 若线程创建后未被 join 或 detach会成为僵尸线程占用系统资源。 3.5.4 与 pthread_join() 的对比 特性pthread_join()pthread_detach()资源回收方式需主动调用阻塞等待线程结束线程结束后自动回收返回值获取可通过 retval 获取线程返回值返回值被忽略线程状态适用于可连接线程默认状态适用于分离线程阻塞行为阻塞调用线程非阻塞立即返回适用场景需要同步执行结果的场景后台任务、无需返回值的场景 3.5.5 设置分离状态的两种方式 创建后分离动态方式 pthread_t thread_id; pthread_create(thread_id, NULL, start_routine, arg); pthread_detach(thread_id); // 动态设置为分离状态创建时分离静态方式 通过线程属性设置 pthread_attr_t attr; pthread_attr_init(attr); pthread_attr_setdetachstate(attr, PTHREAD_CREATE_DETACHED); // 设置为分离状态 pthread_create(thread_id, attr, start_routine, arg); pthread_attr_destroy(attr); // 销毁属性对象3.5.6 错误处理与常见错误码 ESRCH指定的线程 ID 不存在。EINVAL线程已终止或已被设置为分离状态。 推荐处理方式 int ret pthread_detach(thread_id); if (ret ! 0) {fprintf(stderr, pthread_detach failed, error: %d\n, ret);// 进一步处理... }3.5.7 注意事项 无法获取返回值 分离状态的线程结束后其返回值会被丢弃因此无需通过 pthread_exit() 传递有效返回值。资源立即释放 线程结束后其栈空间、寄存器等资源会立即释放若其他线程持有指向该线程栈的指针会导致悬空引用。 避免重复操作 对同一线程同时调用 pthread_join() 和 pthread_detach() 会导致冲突通常返回 EINVAL。 线程状态检查 无法直接查询线程是否处于分离状态需通过编程逻辑确保正确设置。 四、线程ID及进程地址空间布局 pthread_create函数会产生一个线程ID存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程是操作系统调度器的最小单位所以需要一个数值来唯一表示该线程。pthread_create函数第一个参数指向一个虚拟内存单元该内存单元的地址即为新创建线程的线程ID属于NPTL线程库的范畴。线程库的后续操作就是根据该线程ID来操作线程的。线程库NPTL提供了pthread_self函数可以获得线程自身的ID pthread_t到底是什么类型呢取决于实现。对于Linux目前实现的NPTL实现而言pthread_t类型的线程ID本质就是一个进程地址空间上的一个地址。 4.1 pthread_self 函数 4.1.1 函数原型与参数 #include pthread.hpthread_t pthread_self(void);参数无。返回值 返回调用该函数的线程的 pthread_t 类型标识符。 4.1.2 基本用法示例 以下代码展示如何获取并使用线程 ID #include stdio.h #include pthread.hvoid print_thread_id(const char* prefix) {pthread_t tid pthread_self();printf(%s 线程ID: %lu\n, prefix, (unsigned long)tid); }// 线程入口函数 void* thread_function(void* arg) {print_thread_id(子线程);// 比较线程IDif (pthread_equal(pthread_self(), *(pthread_t*)arg)) {printf(子线程检测到与传入的线程ID相同\n);} else {printf(子线程检测到与传入的线程ID不同\n);}return NULL; }int main() {pthread_t thread_id, main_tid;main_tid pthread_self(); // 获取主线程IDprint_thread_id(主线程);// 创建线程并传入主线程IDif (pthread_create(thread_id, NULL, thread_function, main_tid) ! 0) {perror(pthread_create failed);return 1;}// 等待子线程结束pthread_join(thread_id, NULL);return 0; }4.1.3 核心作用与使用场景 线程身份标识 在多线程程序中通过线程 ID 区分不同线程常用于日志记录或调试。printf(线程 %lu 正在处理任务...\n, (unsigned long)pthread_self());线程同步 在某些同步机制中如线程特定数据需使用线程 ID 作为键值。 线程间通信 在线程间传递消息时使用线程 ID 指定目标线程。 避免自等待 在使用 pthread_join() 或 pthread_detach() 时可通过 pthread_self() 避免线程对自身操作可能导致死锁。if (!pthread_equal(thread_id, pthread_self())) {pthread_join(thread_id, NULL); // 确保不等待自身 }4.1.4 线程 ID 的特性 唯一性 在同一进程内每个活跃线程的 ID 唯一但线程终止后其 ID 可能被新创建的线程复用。 不可移植性 pthread_t 的实现因系统而异可能是整数、指针或结构体不可直接比较或存储需使用 pthread_equal() 函数。 生命周期 线程 ID 仅在其生命周期内有效线程终止后 ID 可能失效。 4.1.5 线程 ID 的比较pthread_equal int pthread_equal(pthread_t t1, pthread_t t2);返回值 若 t1 和 t2 为同一线程返回非零值否则返回 0。 示例 pthread_t tid pthread_self(); if (pthread_equal(tid, another_thread_id)) {// 是同一线程 }4.1.6 注意事项 避免直接操作线程 ID 不要假设 pthread_t 是整数类型而直接比较或转换必须使用 pthread_equal()。 线程 ID 的持久化 不要将线程 ID 存储在全局变量或文件中因为线程终止后 ID 可能被复用。 跨进程无效 线程 ID 仅在其所属进程内有效不同进程的线程 ID 无意义。 性能考量 pthread_self() 通常是轻量级操作但频繁调用仍可能影响性能。
http://www.sczhlp.com/news/217432/

相关文章:

  • 长春建设信息网站济南阿里科技网站建设有限公司
  • 山东省住房和城乡建设厅网站注册中心社群营销与运营
  • wordpress是建站工具 还是语言信息系统开发计划
  • 做拍卖的网站怎么在wordpress上添加视频
  • 软件工程作业三
  • 机器学习基础 -- 线性回归模型
  • 泰勒展开
  • 做网站给菠菜引流课程网站建设的基本原理
  • 找别人做网站一般注意什么广告设计软件app
  • 建设银行网站查询不显示整存争取金额wordpress纪念册主题
  • 网站直播怎样做小规模纳税人企业所得税怎么征收
  • 不备案 网站 盈利客户关系管理系统源码
  • 中文儿童网站模板17网站一起做网店普宁池尾
  • 品牌微信网站定制玩游戏的网页
  • 网站开发怎么拉客户网页传奇怎么制作
  • 酒店自建网站的功能郑州网站推广 汉狮网络
  • 温州网站建设选择乐云seo广州市天河区发布
  • 天天联盟广告网站如何做网站建设后台怎么修改
  • 已有网站做google推广寺院网站建设
  • 网站建设的大公司软件培训班学费多少
  • 网站栏目词数学建模网站建设
  • 海南住房与建设厅网站三门县住房和城乡建设规划局网站
  • 淘宝怎么做基础销量什么网站好济南网站制作搜到
  • 厦门市海沧建设局网站免费行情软件app大全
  • 自己搭建公司网站app软件开发公司那家好
  • 深圳做网站建设的公司广州惠科互联网技术有限公司
  • 徐州网站建设4哪里有免费的个人简历模板
  • 如何给网站做下载附件郑州短视频推广
  • 网站建设的技术方案模板下载手机网站建设林肖
  • 购物型网站模板网站用什么软件编写