线程的概念和控制
1Linux线程概念1什么是线程在⼀个程序⾥的⼀个执⾏路线就叫做线程thread。更准确的定义是线程是“⼀个进程内部的控制序列”⼀切进程⾄少都有⼀个执⾏线程线程在进程内部运⾏本质是在进程地址空间内运⾏在Linux系统中在CPU眼中看到的PCB都要⽐传统的进程更加轻量化透过进程虚拟地址空间可以看到进程的⼤部分资源将进程资源合理分配给每个执⾏流就形成了线程执⾏流2分页式储存管理1虚拟地址和页表的由来如果在没有虚拟地址和页表的情况下每个用户程序在物理内存上所对应的空间必须是连续的如下图因为每一个程序的代码数据长度都是不一样的按照这样的映射方式物理内存将分割成各种离散的大小不同的块。经过一段允许时间之后有些程序会退出那么它们占据的物理内存空间可以被回收导致这些物理内存都是以很多碎片的形式存在的。为了解决这个问题虚拟地址和页表就出现了我们希望操作系统人提供给用户的空间必须是连续的但是物理内存最好不要连续。因为每⼀个程序的代码、数据⻓度都是不⼀样的按照这样的映射⽅式物理内存将会被分割成各种离散的、⼤⼩不同的块。经过⼀段运⾏时间之后有些程序会退出那么它们占据的物理内存空间可以被回收导致这些物理内存都是以很多碎⽚的形式存在。怎么办呢我们希望操作系统提供给⽤⼾的空间必须是连续的但是物理内存最好不要连续。此时虚拟内存和分⻚便出现了如下图所示把物理内存按照⼀个固定的⻓度的⻚框进⾏分割有时叫做物理⻚。每个⻚框包含⼀个物理⻚page。⼀个⻚的⼤⼩等于⻚框的⼤⼩。⼤多数 32位 体系结构⽀持 4KB 的⻚⽽ 64位 体系结构⼀般会⽀持 8KB 的⻚。区分⼀⻚和⼀个⻚框是很重要的⻚框是⼀个存储区域⽽⻚是⼀个数据块可以存放在任何⻚框或磁盘中。有了这种机制CPU 便并⾮是直接访问物理内存地址⽽是通过虚拟地址空间来间接的访问物理内存地址。所谓的虚拟地址空间是操作系统为每⼀个正在执⾏的进程分配的⼀个逻辑地址在32位机上其范围从0 ~ 4G-1。操作系统通过将虚拟地址空间和物理内存地址之间建⽴映射关系也就是⻚表这张表上记录了每⼀对⻚和⻚框的映射关系能让CPU间接的访问物理内存地址。总结⼀下其思想是将虚拟内存下的逻辑地址空间分为若⼲⻚将物理内存空间分为若⼲⻚框通过⻚表便能把连续的虚拟内存映射到若⼲个不连续的物理内存⻚。这样就解决了使⽤连续的物理内存造成的碎⽚问题。2物理内存管理假设⼀个可⽤的物理内存有4GB的空间。按照⼀个⻚框的⼤⼩4KB进⾏划分4GB的空间就是4GB/4KB 1048576个⻚框。有这么多的物理⻚操作系统肯定是要将其管理起来的操作系统需要知道哪些⻚正在被使⽤哪些⻚空闲等等。内核⽤struct page结构表⽰系统中的每个物理⻚出于节省内存的考虑struct page中使⽤了⼤量的联合体union。/* include/linux/mm_types.h */ struct page { /* 原⼦标志有些情况下会异步更新 */ unsigned long flags; union { struct { /* 换出⻚列表例如由zone-lru_lock保护的active_list */ struct list_head lru; /* 如果最低为为0则指向inode * address_space或为NULL * 如果⻚映射为匿名内存最低为置位 * ⽽且该指针指向anon_vma对象 */ struct address_space *mapping; /* 在映射内的偏移量 */ pgoff_t index; /* * 由映射私有不透明数据 * 如果设置了PagePrivate通常⽤于buffer_heads * 如果设置了PageSwapCache则⽤于swp_entry_t * 如果设置了PG_buddy则⽤于表⽰伙伴系统中的阶 */ unsigned long private; }; struct { /* slab, slob and slub */ union { struct list_head slab_list; /* uses lru */ struct { /* Partial pages */ struct page *next; #ifdef CONFIG_64BIT int pages; /* Nr of pages left */ int pobjects; /* Approximate count */ #else short int pages; short int pobjects; #endif }; }; struct kmem_cache *slab_cache; /* not slob */ /* Double-word boundary */ void *freelist; /* first free object */ union { void *s_mem; /* slab: first object */ unsigned long counters; /* SLUB */ struct { /* SLUB */ unsigned inuse : 16; /* ⽤于SLUB分配器对象的数⽬ */ unsigned objects : 15; unsigned frozen : 1; }; }; }; ... }; union { /* 内存管理⼦系统中映射的⻚表项计数⽤于表⽰⻚是否已经映射还⽤于限制逆向映射搜索*/ atomic_t _mapcount; unsigned int page_type; unsigned int active; /* SLAB */ int units; /* SLOB */ }; ... #if defined(WANT_PAGE_VIRTUAL) /* 内核虚拟地址如果没有映射则为NULL即⾼端内存 */ void *virtual; #endif /* WANT_PAGE_VIRTUAL */ ... }其中⽐较重要的⼏个参数1. flags ⽤来存放⻚的状态。这些状态包括⻚是不是脏的是不是被锁定在内存中等。flag的每⼀位单独表⽰⼀种状态所以它⾄少可以同时表⽰出32种不同的状态。这些标志定义在linux/page-flags.h中。其中⼀些⽐特位⾮常重要如PG_locked⽤于指定⻚是否锁定PG_uptodate⽤于表⽰⻚的数据已经从块设备读取并且没有出现错误。2. _mapcount 表⽰在⻚表中有多少项指向该⻚也就是这⼀⻚被引⽤了多少次。当计数值变为-1时就说明当前内核并没有引⽤这⼀⻚于是在新的分配中就可以使⽤它。3. virtual 是⻚的虚拟地址。通常情况下它就是⻚在虚拟内存中的地址。有些内存即所谓的⾼端内存并不永久地映射到内核地址空间上。在这种情况下这个域的值为NULL需要的时候必须动态地映射这些⻚。要注意的是struct page与物理⻚相关⽽并⾮与虚拟⻚相关。⽽系统中的每个物理⻚都要分配⼀个这样的结构体让我们来算算对所有这些⻚都这么做到底要消耗掉多少内存。算struct page占40个字节的内存吧假定系统的物理⻚为4KB⼤⼩系统有4GB物理内存.那么系统中共有⻚⾯ 1048576个1兆个所以描述这么多⻚⾯的page结构体消耗的内存只不过40MB 相对系统4GB内存⽽⾔仅是很⼩的⼀部分罢了。因此要管理系统中这么多物理⻚⾯这个代价并不算太⼤。要知道的是⻚的⼤⼩对于内存利⽤和系统开销来说⾮常重要⻚太⼤⻚⻚必然会剩余较⼤不能利⽤的空间⻚内碎⽚。⻚太⼩虽然可以减⼩⻚内碎⽚的⼤⼩但是⻚太多会使得⻚表太⻓⽽占⽤内存同时系统频繁地进⾏⻚转化加重系统开销。因此⻚的⼤⼩应该适中通常为 512B -8KB windows系统的⻚框⼤⼩为4KB。3:页表⻚表中的每⼀个表项指向⼀个物理⻚的开始地址。在32位系统中虚拟内存的最⼤空间是4GB 这是每⼀个⽤⼾程序都拥有的虚拟内存空间。既然需要让 4GB的虚拟内存全部可⽤那么⻚表中就需要能够表⽰这所有的 4GB空间那么就⼀共需要4GB/4KB 1048576个表项。如下图所⽰虚拟内存看上去被虚线“分割”成⼀个个单元其实并不是真的分割虚拟内存仍然是连续的。这个虚线的单元仅仅表⽰它与⻚表中每⼀个表项的映射关系并最终映射到相同⼤⼩的⼀个物理内存⻚上。⻚表中的物理地址与物理内存之间是随机的映射关系哪⾥可⽤就指向哪⾥(物理⻚)。虽然最终使⽤的物理内存是离散的但是与虚拟内存对应的线性地址是连续的。处理器在访问数据、获取指令时使⽤的都是线性地址只要它是连续的就可以了最终都能够通过⻚表找到实际的物理地址。在32位系统中地址的⻓度是4个字节那么⻚表中的每⼀个表项就是占⽤4个字节。所以⻚表占据的总空间⼤⼩就是 1048576*4 4MB的⼤⼩。也就是说映射表⾃⼰本⾝就要占⽤4MB /4KB 1024个物理⻚。这会存在哪些问题呢回想⼀下当初为什么使⽤⻚表就是要将进程划分为⼀个个⻚可以不⽤连续的存放在物理内存中但是此时⻚表就需要1024个连续的⻚框似乎和当时的⽬标有点背道⽽驰了......此外根据局部性原理可知很多时候进程在⼀段时间内只需要访问某⼏个⻚就可以正常运⾏了。因此也没有必要⼀次让所有的物理⻚都常驻内存。解决需要⼤容量⻚表的最好⽅法是把⻚表看成普通的⽂件对它进⾏离散分配即对⻚表再分⻚由此形成多级⻚表的思想。为了解决这个问题可以把这个单⼀⻚表拆分成1024个体积更⼩的映射表。如下图所⽰。这样⼀来1024(每个表中的表项个数) * 1024(表的个数)仍然可以覆盖4GB的物理内存空间。这⾥的每⼀个表就是真正的⻚表所以⼀共有1024个⻚表。⼀个⻚表⾃⾝占⽤4KB那么1024个⻚表⼀共就占⽤了4MB的物理内存空间和之前没差别啊从总数上看是这样但是⼀个应⽤程序是不可能完全使⽤全部的 4GB 空间的也许只要⼏⼗个⻚表就可以了。例如⼀个⽤⼾程序的代码段、数据段、栈段⼀共就需要 10 MB的空间那么使⽤3个⻚表就⾜够了。计算过程每⼀个⻚表项指向⼀个 4KB 的物理⻚那么⼀个⻚表中 1024 个⻚表项⼀共能覆盖 4MB 的物理内存;那么 10MB 的程序向上对⻬取整之后(4MB 的倍数就是 12 MB)就需要 3 个⻚表就可以了。4页目录结构到⽬前为⽌每⼀个⻚框都被⼀个⻚表中的⼀个表项来指向了那么这1024个⻚表也需要被管理起来。管理⻚表的表称之为⻚⽬录表形成⼆级⻚表。如下图所⽰所有⻚表的物理地址被⻚⽬录表项指向⻚⽬录的物理地址被CR3寄存器 指向这个寄存器中保存了当前正在执⾏任务的⻚⽬录地址。所以操作系统在加载⽤⼾程序的时候不仅仅需要为程序内容来分配物理内存还需要为⽤来保存程序的⻚⽬录和⻚表分配物理内存。5二级页表的地址转化下⾯以⼀个逻辑地址为例。将逻辑地址0000000000,0000000001,11111111111转换为物理地址的过程1.在32位处理器中采⽤4KB的⻚⼤⼩则虚拟地址中低12位为⻚偏移剩下⾼20位给⻚表分成两级每个级别占10个bit1010。2.CR3寄存器 读取⻚⽬录起始地址再根据⼀级⻚号查⻚⽬录表找到下⼀级⻚表在物理内存中存放位置。3.根据⼆级⻚号查表找到最终想要访问的内存块号。4.结合⻚内偏移量得到物理地址。5.注⼀个物理⻚的地址⼀定是4KB对⻬的(最后的12位全部为0)所以其实只需要记录物理⻚地址的⾼ 20 位即可。6.以上其实就是 MMU 的⼯作流程。MMU(Memory Manage Unit)是⼀种硬件电路其速度很快主要⼯作是进⾏内存管理地址转换只是它承接的业务之⼀。到这⾥其实还有个问题MMU要先进⾏两次⻚表查询确定物理地址在确认了权限等问题后MMU再将这个物理地址发送到总线内存收到之后开始读取对应地址的数据并返回。那么当⻚表变为N级时就变成了N次检索1次读写。可⻅⻚表级数越多查询的步骤越多对于CPU来说等待时间越⻓效率越低。让我们现在总结⼀下单级⻚表对连续内存要求⾼于是引⼊了多级⻚表但是多级⻚表也是⼀把双刃剑在减少连续存储要求且减少存储空间的同时降低了查询效率。有没有提升效率的办法呢计算机科学中的所有问题都可以通过添加⼀个中间层来解决。MMU引⼊了新武器江湖⼈称快表的TLB其实就是缓存有没有提升效率的办法呢计算机科学中的所有问题都可以通过添加⼀个中间层来解决。MMU引⼊了新武器江湖⼈称快表的TLB其实就是缓存当 CPU给MMU传新虚拟地址之后MMU先去问TLB那边有没有如果有就直接拿到物理地址发到总线给内存⻬活。但 TLB容量⽐较⼩难免发⽣Cache Miss这时候MMU还有保底的⽼武器⻚表在⻚表中找到之后 MMU除了把地址发到总线传给内存还把这条映射关系给到TLB让它记录⼀下刷新缓存。6缺页异常设想CPU 给 MMU 的虚拟地址在TLB和⻚表都没有找到对应的物理⻚该怎么办呢其实这就是缺⻚异常 Page Fault它是⼀个由硬件中断触发的可以由软件逻辑纠正的错误。假如⽬标内存⻚在物理内存中没有对应的物理⻚或者存在但⽆对应权限CPU 就⽆法获取数据这种情况下CPU就会报告⼀个缺⻚错误。由于 CPU 没有数据就⽆法进⾏计算CPU罢⼯了⽤⼾进程也就出现了缺⻚中断进程会从⽤⼾态切换到内核态并将缺⻚中断交给内核的 Page Fault Handler处理。缺⻚中断会交给PageFaultHandler处理其根据缺⻚中断的不同类型会进⾏不同的处理• Hard Page Fault 也被称为 Major Page Fault 翻译为硬缺⻚错误 / 主要缺⻚错误这时物理内存中没有对应的物理⻚需要CPU打开磁盘设备读取到物理内存中再让MMU建⽴虚拟地址和物理地址的映射。• Soft Page Fault 也被称为 Minor Page Fault 翻译为软缺⻚错误 / 次要缺⻚错误这时物理内存中是存在对应物理⻚的只不过可能是其他进程调⼊的发出缺⻚异常的进程不知道⽽已此时MMU只需要建⽴映射即可⽆需从磁盘读取写⼊内存⼀般出现在多进程共享内存区域。• Invalid Page Fault 翻译为⽆效缺⻚错误⽐如进程访问的内存地址越界访问⼜⽐如对空指针解引⽤内核就会报 segment fault 错误中断进程直接挂掉。3线程的优点• 创建⼀个新线程的代价要⽐创建⼀个新进程⼩得多• 与进程之间的切换相⽐线程之间的切换需要操作系统做的⼯作要少很多◦ 最主要的区别是线程的切换虚拟内存空间依然是相同的但是进程切换是不同的。这两种上下⽂切换的处理都是通过操作系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出。◦ 另外⼀个隐藏的损耗是上下⽂的切换会扰乱处理器的缓存机制。简单的说⼀旦去切换上下⽂处理器中所有已经缓存的内存地址⼀瞬间都作废了。还有⼀个显著的区别是当你改变虚拟内存空间的时候处理的⻚表缓冲 TLB 快表会被全部刷新这将导致内存的访问在⼀段时间内相当的低效。但是在线程的切换中不会出现这个问题当然还有硬件cache。• 线程占⽤的资源要⽐进程少很• 能充分利⽤多处理器的可并⾏数量• 在等待慢速I / O操作结束的同时程序可执⾏其他的计算任务• 计算密集型应⽤为了能在多处理器系统上运⾏将计算分解到多个线程中实现• I / O密集型应⽤为了提⾼性能将I / O操作重叠。线程可以同时等待不同的I / O操作4线程的缺点• 性能损失◦ ⼀个很少被外部事件阻塞的计算密集型线程往往⽆法与其它线程共享同⼀个处理器。如果计算密集型线程的数量⽐可⽤的处理器多那么可能会有较⼤的性能损失这⾥的性能损失指的是增加了额外的同步和调度开销⽽可⽤的资源不变。• 健壮性降低◦ 编写多线程需要更全⾯更深⼊的考虑在⼀个多线程程序⾥因时间分配上的细微偏差或者因共享了不该共享的变量⽽造成不良影响的可能性是很⼤的换句话说线程之间是缺乏保护的。• 缺乏访问控制◦ 进程是访问控制的基本粒度在⼀个线程中调⽤某些OS函数会对整个进程造成影响。• 编程难度提⾼◦ 编写与调试⼀个多线程程序⽐单线程程序困难得多5线程异常• 单个线程如果出现除零野指针问题导致线程崩溃进程也会随着崩溃• 线程是进程的执⾏分⽀线程出异常就类似进程出异常进⽽触发信号机制终⽌进程进程终⽌该进程内的所有线程也就随即退出6线程用途• 合理的使⽤多线程能提⾼CPU密集型程序的执⾏效率• 合理的使⽤多线程能提⾼IO密集型程序的⽤⼾体验如⽣活中我们⼀边写代码⼀边下载开发⼯具就是多线程运⾏的⼀种表现2Linux进程VS线程1进程和线程• 进程是资源分配的基本单位• 线程是调度的基本单位• 线程共享进程数据但也拥有⾃⼰的⼀部分数据 :◦ 线程ID◦ ⼀组寄存器◦ 栈◦ errno◦ 信号屏蔽字◦ 调度优先级2进程的多个线程共享同⼀地址空间,因此Text Segment、Data Segment都是共享的,如果定义⼀个函数,在各线程中都可以调⽤,如果定义⼀个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:• ⽂件描述符表• 每种信号的处理⽅式(SIG_ IGN、SIG_ DFL或者⾃定义的信号处理函数)• 当前⼯作⽬录• ⽤⼾id和组id进程和线程的关系如下图:3Linux线程控制1POSIX线程库与线程有关的函数构成了⼀个完整的系列绝⼤多数函数的名字都是以“pthread_”打头的要使⽤这些函数库要通过引⼊头⽂pthread.h链接这些线程函数库时要使⽤编译器命令的“-lpthread”选项2创建线程/*功能创建⼀个新的线程 原型*/ int pthread_create(pthread_t* thread, const pthread_attr_t* attr, void* (*start_routine)(void*), void* arg); /*参数: thread:返回线程ID attr : 设置线程的属性attr为NULL表⽰使⽤默认属性 start_routine : 是个函数地址线程启动后要执⾏的函数 arg : 传给线程启动函数的参数 返回值成功返回0失败返回错误码*/错误检查• 传统的⼀些函数是成功返回0失败返回 - 1并且对全局变量errno赋值以指⽰错误。• 传统 函数是成功 回 失败 回 并 对 局变 赋值以指⽰错误• pthreads函数出错时不会设置全局变量errno⽽⼤部分其他POSIX函数会这样做。⽽是将错误代码通过返回值返回• pthreads同样也提供了线程内的errno变量以⽀持其它使⽤errno的代码。对于pthreads函数的错误建议通过返回值业判定因为读取返回值要⽐读取线程内的errno变量的开销更⼩打印出来的 tid 是通过 pthread 库中有函数pthread_self得到的它返回⼀个 pthread_t 类型的变量指代的是调⽤ pthread_self 函数的线程的 “ID”。怎么理解这个“ID”呢这个“ID”是 pthread 库给每个线程定义的进程内唯⼀标识是 pthread 库维持的。由于每个进程有⾃⼰独⽴的内存空间故此“ID”的作⽤域是进程级⽽⾮系统级内核不认识。其实 pthread 库也是通过内核提供的系统调⽤例如clone来创建线程的⽽内核会为每个线程创建系统全局唯⼀的“ID”来唯⼀标识这个线程。使用PS查看线程情况LWP 是什么呢LWP 得到的是真正的线程ID。之前使⽤pthread_self得到的这个数实际上是⼀个地址在虚拟地址空间上的⼀个地址通过这个地址可以找到关于这个线程的基本信息包括线 程ID线程栈寄存器等属性。在ps -aL得到的线程ID有⼀个线程ID和进程ID相同这个线程就是主线程主线程的栈在虚拟地址空间的栈上⽽其他线程的栈在是在共享区堆栈之间因为pthread系列函数都是pthread库提供给我们的。⽽pthread库是在共享区的。所以除了主线程之外的其他线程的栈都在共享区。3线程终止如果需要只终⽌某个线程⽽不终⽌整个进程,可以有三种⽅法:1.从线程函数return。这种⽅法对主线程不适⽤,从main函数return相当于调⽤exit。2.线程可以调⽤pthread_ exit终⽌⾃⼰。3.⼀个线程可以调⽤pthread_ cancel终⽌同⼀进程中的另⼀个线程。pthread_exit函数/*功能线程终⽌ 原型:*/ void pthread_exit(void *value_ptr); /*参数: value_ptr:value_ptr不要指向⼀个局部变量。 返回值 ⽆返回值跟进程⼀样线程结束的时候⽆法返回到它的调⽤者⾃⾝*/需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是⽤malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了.pthread_cancel函数/*功能取消⼀个执⾏中的线程 原型:*/ int pthread_cancel(pthread_t thread); /*参数: thread:线程ID 返回值成功返回0失败返回错误码*/4线程等待为什么需要线程等待已经退出的线程其空间没有被释放仍然在进程的地址空间内。创建新的线程不会复⽤刚才退出线程的地址空间。/*功能等待线程结束 原型*/ int pthread_join(pthread_t thread, void **value_ptr); /*参数: thread:线程ID value_ptr:它指向⼀个指针后者指向线程的返回值 返回值成功返回0失败返回错误码*/调⽤该函数的线程将挂起等待,直到id为thread的线程终⽌。thread线程以不同的⽅法终⽌,通过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线程的终⽌状态不感兴趣,可以传NULL给value_ ptr参数。#include stdio.h #include stdlib.h #include string.h #include unistd.h #include pthread.h void *thread1( void *arg ) { printf(thread 1 returning ... \n); int *p (int*)malloc(sizeof(int)); *p 1; return (void*)p; } void *thread2( void *arg ) { printf(thread 2 exiting ...\n); int *p (int*)malloc(sizeof(int)); *p 2; pthread_exit((void*)p); } void *thread3( void *arg ) { while ( 1 ){ // printf(thread 3 is running ...\n); sleep(1); } return NULL; } int main( void ) { pthread_t tid; void *ret; // thread 1 return pthread_create(tid, NULL, thread1, NULL); pthread_join(tid, ret); printf(thread return, thread id %lx, return code:%d\n, tid, *(int*)ret); free(ret); // thread 2 exit pthread_create(tid, NULL, thread2, NULL); pthread_join(tid, ret); printf(thread return, thread id %lx, return code:%d\n, tid, *(int*)ret); free(ret); // thread 3 cancel by other pthread_create(tid, NULL, thread3, NULL); sleep(3); pthread_cancel(tid); pthread_join(tid, ret); if ( ret PTHREAD_CANCELED ) printf(thread return, thread id %lx, return code:PTHREAD_CANCELED\n, tid); else printf(thread return, thread id %lx, return code:NULL\n, tid); }结果5分离线程默认情况下新创建的线程是joinable的线程退出后需要对其进⾏pthread_join操作否则⽆法释放资源从⽽造成系统泄漏。如果不关⼼线程的返回值join是⼀种负担这个时候我们可以告诉系统当线程退出时⾃动释放线程资源。int pthread_detach(pthread_t thread);可以是线程组内其他线程对⽬标线程进⾏分离也可以是线程⾃⼰分离pthread_detach(pthread_self());joinable和分离是冲突的⼀个线程不能既是joinable⼜是分离的。#include stdio.h #include pthread.h #include unistd.h void *worker(void *arg) { printf(分离线程开始执行\n); sleep(2); printf(分离线程执行结束会自动释放资源\n); pthread_exit(NULL); } int main() { pthread_t tid; pthread_attr_t attr; // 1. 初始化线程属性 pthread_attr_init(attr); // 2. 设置线程为分离状态 pthread_attr_setdetachstate(attr, PTHREAD_CREATE_DETACHED); // 3. 创建分离线程 pthread_create(tid, attr, worker, NULL); // 无需调用pthread_join主线程可以直接继续执行 printf(主线程无需等待继续运行\n); sleep(3); // 等分离线程执行完避免主线程先退出导致进程结束 pthread_attr_destroy(attr); return 0; }4线程ID以及进程地址空间布局pthread_ create函数会产⽣⼀个线程ID存放在第⼀个参数指向的地址中。该线程ID和前⾯说的线程ID不是⼀回事。前⾯讲的线程ID属于进程调度的范畴。因为线程是轻量级进程是操作系统调度器的最⼩单位所以需要⼀个数值来唯⼀表⽰该线程。pthread_ create函数第⼀个参数指向⼀个虚拟内存单元该内存单元的地址即为新创建线程的线程ID属于NPTL线程库的范畴。线程库的后续操作就是根据该线程ID来操作线程的。线程库NPTL提供了pthread_ self函数可以获得线程⾃⾝的IDpthread_t pthread_self(void);pthread_t到底是什么类型呢取决于实现。对于Linux⽬前实现的NPTL实现⽽⾔pthread_t类型的线程ID本质就是⼀个进程地址空间上的⼀个地址。

相关新闻

机器学习深度学习——个人笔记(持续更新中~)

机器学习深度学习——个人笔记(持续更新中~)

吴恩达机器学习&深度学习前言机器学习部分特征缩放梯度下降是否收敛分类算法逻辑回归决策边界逻辑回归的成本函数损失函数 L梯度下降实现过拟合问题Solusion深度学习部分需求预测神经网络&计算机视觉神经网络层向前传播的神经网络推理基本模型的发展前馈神经网络卷积神…

2026/5/17 12:21:31 阅读更多 →
工业AI不是套壳:安托如何让大模型真正读懂CAD图纸

工业AI不是套壳:安托如何让大模型真正读懂CAD图纸

从"能聊天"到"能干活",制造业场景的AI落地到底难在哪里?安托给出了解法。真正在工程师的CATIA桌面上跑起来的AI,长什么样?安托(ATOZ)给出了一个相对具体的答案。安托深耕制造业30余年&…

2026/7/3 14:23:05 阅读更多 →
Dubbo实战|配置详解、服务监控与问题排查(生产环境必备) (三)

Dubbo实战|配置详解、服务监控与问题排查(生产环境必备) (三)

前言 大家好,前两篇我们讲解了Dubbo的入门知识和核心功能(服务注册发现、负载均衡、容错机制),相信大家已经掌握了Dubbo的基础使用和核心原理。 但在实际生产环境中,我们会遇到更多细节问题:比如Dubbo的超时…

2026/7/3 1:37:45 阅读更多 →

最新新闻

python如果捕捉错误精准到行

python如果捕捉错误精准到行

文章目录问题解决一 引用traceback库解决二 Loguru 完整异常捕获教程问题 错误捕捉是很常用的功能,但是python的错误捕捉不能精准的定位到错误是哪一行,只能显示错误捕捉的行数,而不是具体的报错行数,这样有的时候给查找错误带来…

2026/7/4 21:58:14 阅读更多 →
BitNet b1.58:CPU端大模型部署与优化实战

BitNet b1.58:CPU端大模型部署与优化实战

1. BitNet b1.58:重新定义CPU端大模型的可能性去年第一次听说1-bit量化大模型时,我和多数同行一样持怀疑态度——直到在ThinkPad X1 Carbon(i7-1260P/32GB)上跑通了BitNet b1.58的2B4T版本。这个仅占2.4GB内存的模型,不…

2026/7/4 21:58:14 阅读更多 →
E-Hentai Downloader 项目中的 GP 限制问题解析

E-Hentai Downloader 项目中的 GP 限制问题解析

E-Hentai Downloader 项目中的 GP 限制问题解析 问题背景 在使用 E-Hentai Downloader 脚本下载旧图库时,用户可能会遇到"GP Limit Exceeded"的错误提示。这个问题通常出现在下载较旧的图库(90天以上)时,特别是当用户尝…

2026/7/4 21:56:14 阅读更多 →
AutoUnipus:3分钟搞定U校园网课答题的终极指南

AutoUnipus:3分钟搞定U校园网课答题的终极指南

AutoUnipus:3分钟搞定U校园网课答题的终极指南 【免费下载链接】AutoUnipus U校园脚本,支持全自动答题,百分百正确 2024最新版 项目地址: https://gitcode.com/gh_mirrors/au/AutoUnipus 还在为U校园平台枯燥的网课任务消耗宝贵时间而烦恼吗?Auto…

2026/7/4 21:54:13 阅读更多 →
Sublime Text Orgmode插件常见问题解决方案:从安装到高级使用

Sublime Text Orgmode插件常见问题解决方案:从安装到高级使用

Sublime Text Orgmode插件常见问题解决方案:从安装到高级使用 【免费下载链接】orgmode orgmode is for keeping notes, maintaining TODO lists, planning projects, and authoring documents with a fast and effective plain-text system. 项目地址: https://g…

2026/7/4 21:52:12 阅读更多 →
YOLOv5 vs YOLOv7 vs YOLOv8:gh_mirrors/yo/yolo_research项目中的模型对比与选择策略 [特殊字符]

YOLOv5 vs YOLOv7 vs YOLOv8:gh_mirrors/yo/yolo_research项目中的模型对比与选择策略 [特殊字符]

YOLOv5 vs YOLOv7 vs YOLOv8:gh_mirrors/yo/yolo_research项目中的模型对比与选择策略 🚀 【免费下载链接】yolo_research based on yolo-high-level project (detect\pose\classify\segment\):include yolov5\yolov7\yolov8\ core ,improvement researc…

2026/7/4 21:50:11 阅读更多 →

日新闻

Memcached 1.6.43 发布:关键安全修复版本,多项问题得到解决

Memcached 1.6.43 发布:关键安全修复版本,多项问题得到解决

Memcached 1.6.43 正式发布,这是一个关键的安全修复版本,修复了多个方面的问题,还对部分功能进行了优化。 安全修复亮点 此次发布在安全修复上表现突出。binprot 避免了项目引用计数溢出,mcmc 因安全问题提升了上游版本号&#xf…

2026/7/4 0:04:29 阅读更多 →
终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案

终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案

终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案 【免费下载链接】HMCL A Minecraft Launcher which is multi-functional, cross-platform and popular 项目地址: https://gitcode.com/gh_mirrors/hm/HMCL HMCL(Hello Minecraft! Lau…

2026/7/4 0:06:29 阅读更多 →
KMX63与PIC18F66K40在嵌入式HMI中的硬件协同与低功耗设计

KMX63与PIC18F66K40在嵌入式HMI中的硬件协同与低功耗设计

1. KMX63与PIC18F66K40的硬件协同架构解析KMX63作为一款三轴加速度计和磁力计组合传感器,与PIC18F66K40微控制器的搭配堪称嵌入式HMI开发的黄金组合。这套硬件组合的核心优势在于KMX63提供的高精度运动感知能力与PIC18F66K40强大的信号处理能力形成了完美互补。KMX6…

2026/7/4 0:06:29 阅读更多 →

周新闻

月新闻