Linux C并发编程基础(线程管理)
线程 Thread专业术语称之为程序执行流的最小单元 。线程是不会执行程序的可以理解成线程就是一个载体将要执行的代码运送到CPU进行处理。多线程就是多个线程同时并发执行。与进程相比线程Lightweight ProcessLWP是轻量级的执行单元共享进程的地址空间代码段、数据段、堆等仅拥有独立的栈空间、寄存器和线程 ID开销远低于进程。1.线程的核心基础概念1.1.线程的定义与核心特性定义线程是进程内的执行流一个进程可以包含多个线程至少有一个主线程所有线程共享进程的核心资源同时拥有自身的私有资源。核心特性资源共享同一进程内的所有线程共享进程地址空间代码、全局变量、堆、打开的文件描述符、信号处理方式等这是线程高效通信的基础也是线程安全问题的根源。资源私有每个线程拥有独立的线程栈局部变量、函数调用上下文、程序计数器PC、寄存器集合、线程 IDTID、errno 变量确保线程的独立执行不相互干扰。轻量级线程的创建、销毁、切换开销远低于进程无需分配独立地址空间仅需初始化少量私有资源。统一调度线程是内核调度的基本单位Linux 中无真正的 “线程”而是通过轻量级进程实现内核对线程和进程的调度策略一致。1.2.线程的关键标识线程 IDTID分类Linux 中有两种线程 ID概念不同切勿混淆内核态 TID内核调度层面的标识对应轻量级进程的 ID全局唯一。可通过 syscall(SYS_gettid) 获取或通过 ps -L pid 命令查看LWP 列即为内核态 TID。用户态 TIDpthread_t由 POSIX 线程库pthread分配的标识仅在当前进程内唯一用于用户态代码中管理线程如线程等待、线程终止。可通过 pthread_self() 函数获取。注意主线程的内核态 TID 等于进程 IDPID其他线程的内核态 TID 与 PID 不同。1.3.线程与进程的核心区别2.Linux 线程的实现模型轻量级进程LWPLinux 没有提供专门的线程内核实现而是采用 **“轻量级进程Light Weight ProcessLWP”** 模型实现线程核心特点如下内核视角没有 “线程” 概念只有 “进程”每个 LWP 都是一个独立的内核调度实体拥有自己的内核态 TID、调度信息和私有资源。用户视角通过 POSIX 线程库pthread 库用户态库将多个 LWP 封装为 “线程”对外提供统一的线程操作接口pthread_create 等隐藏底层 LWP 实现细节。资源共享实现多个 LWP 共享同一个进程的地址空间和文件描述符表通过内核的 “进程描述符共享机制” 实现这也是线程共享资源的底层原理。核心优势复用了进程的内核调度机制无需为线程单独设计调度逻辑实现简单且高效。补充除了 LWP 模型还有 “用户级线程”完全在用户态实现内核无感知调度由用户态库完成但 Linux 中主流使用的是 LWP 模型内核级线程也是我们开发中接触的 pthread 线程。3.线程管理的核心接口pthread 库Linux 中线程管理依赖 POSIX 线程库pthread所有接口均以 pthread_ 为前缀编译时需链接 -lpthread 库如 gcc thread_demo.c -o thread_demo -lpthread。4.线程管理的核心接口pthread 库Linux 中线程管理依赖 POSIX 线程库pthread所有接口均以 pthread_ 为前缀编译时需链接 -lpthread 库如 gcc thread_demo.c -o thread_demo -lpthread。4.1.线程创建pthread_create()函数原型// 创建一个新线程成功返回 0失败返回非 0 错误码不设置 errno int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);参数说明thread输出参数指向 pthread_t 变量用于存储新线程的用户态 TID。attr线程属性如栈大小、分离状态传入 NULL 表示使用默认属性。start_routine线程入口函数函数指针线程创建后会执行该函数格式为 void *(*)(void *)。arg传递给线程入口函数的参数类型为 void *需根据实际需求进行类型转换。关键注意点新线程的执行时机不确定可能在 pthread_create() 返回前就开始执行也可能在返回后执行。线程入口函数的返回值 void * 会作为线程退出状态供 pthread_join() 获取。4.2.线程等待pthread_join()函数原型// 等待指定线程终止回收线程资源成功返回 0失败返回非 0 错误码 int pthread_join(pthread_t thread, void **retval);参数说明thread要等待的线程的用户态 TID由 pthread_create() 输出。retval输出参数指向 void * 指针用于存储线程入口函数的返回值线程退出状态传入 NULL 表示不关心线程退出状态。核心特性阻塞性调用 pthread_join() 的线程通常是主线程会阻塞直到目标线程终止。资源回收若不调用 pthread_join()终止的线程会成为 “僵尸线程”占用进程内的线程资源类似进程的僵尸进程。仅能等待 “可结合状态” 的线程默认创建的线程是可结合状态无法被多次等待。4.3.线程退出pthread_exit()函数原型// 终止当前线程返回指定的退出状态不返回类似进程的 _exit() void pthread_exit(void *retval);参数说明retval线程退出状态会被 pthread_join() 的 retval 参数捕获若无需返回状态可传入 NULL。关键注意点仅终止当前线程不会影响同一进程内的其他线程除非当前线程是最后一个线程此时进程会退出。若线程入口函数执行完毕并返回等效于调用 pthread_exit()返回值即为函数的返回值。不能在主线程中调用 pthread_exit() 后期望继续执行主线程后续代码调用后主线程直接终止。4.4.获取当前线程 IDpthread_self()函数原型// 返回当前线程的用户态 TIDpthread_t 类型 pthread_t pthread_self(void);用途常用于打印线程标识、判断线程是否为自身、记录线程信息等场景。注意返回值是用户态 TID仅在当前进程内有效不可用于内核态调度内核态需用 syscall(SYS_gettid)。4.5.线程分离pthread_detach()函数原型// 将指定线程设置为“分离状态”成功返回 0失败返回非 0 错误码 int pthread_detach(pthread_t thread);核心特性分离状态的线程终止后会自动回收资源无需调用 pthread_join()也无法被 pthread_join() 等待。适用场景无需关心线程退出状态、不需要阻塞等待线程终止的场景如后台任务线程避免僵尸线程产生。注意线程一旦设置为分离状态无法再恢复为可结合状态。4.6.发送取消请求pthread_cancel()函数原型// 向目标线程发送取消请求成功返回 0失败返回非 0 错误码 int pthread_cancel(pthread_t thread);参数说明thread目标线程的用户态 TID由 pthread_create() 输出。关键注意点非阻塞调用后立即返回不等待目标线程终止仅完成 “取消请求标记”。不保证一定终止目标线程若处于 PTHREAD_CANCEL_DISABLE 状态会挂起该请求直到状态切换为 ENABLE。默认行为目标线程会在下次取消点处响应请求执行清理函数后终止。4.7.设置线程可取消性类型pthread_setcanceltype()函数原型// 设置当前线程的可取消性状态延迟/异步成功返回 0失败返回非 0 错误码 int pthread_setcanceltype(int type, int *oldtype);参数说明type新的可取消性状态可选 PTHREAD_CANCEL_DEFERRED延迟或 PTHREAD_CANCEL_ASYNCHRONOUS异步。oldtype输出参数用于存储原来的可取消性状态传入 NULL 表示不保存原状态。4.8.设置线程可取消性启用 / 禁用pthread_setcancelstate()函数原型// 设置当前线程的可取消性类型允许/禁止成功返回 0失败返回非 0 错误码 int pthread_setcancelstate(int state, int *oldstate);参数说明state新的可取消性类型可选 PTHREAD_CANCEL_ENABLE允许或 PTHREAD_CANCEL_DISABLE禁止。oldstate输出参数用于存储原来的可取消性类型传入 NULL 表示不保存原状态。4.9.注册 / 注销清理函数pthread_cleanup_push()/pthread_cleanup_pop()函数原型// 注册取消清理函数入栈 void pthread_cleanup_push(void (*routine)(void *), void *arg); // 注销取消清理函数出栈 void pthread_cleanup_pop(int execute);参数说明routine清理函数指针格式为 void (*)(void *)线程被取消时会调用该函数。arg传递给清理函数的参数。execute是否在注销时执行清理函数1 执行0 不执行。关键注意点必须成对出现二者在语法上必须位于同一代码块中且一一对应否则编译报错。清理函数的参数 arg 需保证有效性避免传递局部变量地址可能被提前销毁。