一、线程的核心概论
在 Linux 系统中,线程是轻量级的进程,它并非独立存在,而是隶属于某个进程。一个进程可以包含多个线程,这些线程共享进程的大部分资源(如代码段、数据段、打开的文件描述符等),同时拥有自己独立的执行上下文。
线程的核心作用和进程一致,都是为了实现并发执行,尤其适用于处理相对耗时的任务。比如在一个网络服务程序中,主线程负责监听客户端连接,子线程负责处理每个客户端的请求,这种多线程模型可以大幅提升程序的响应效率。
二、线程与进程的核心区别
理解线程的关键,是厘清它与进程的核心差异 —— 这也是线程所有特性的根源,具体可从以下维度对比:
| 维度 | 进程 | 线程 |
|---|---|---|
| 资源分配单位 | 系统最小资源分配单位 | 系统最小执行单位 |
| 资源归属 | 拥有独立的 3GB 用户空间,资源完全独立 | 共享所属进程资源,仅独立拥有 8MB 栈区 |
| 稳定性 | 进程崩溃不影响其他进程,稳定性高 | 单个线程崩溃会导致整个进程崩溃,稳定性低 |
| 创建开销 | 需分配完整 3GB 空间,开销极大 | 仅开辟 8MB 栈区,开销远低于进程 |
| 并发能力 | 创建 / 切换开销大,并发度低 | 创建 / 切换开销小,并发度远高于进程 |
2.1 资源共享与独立性
- 进程:资源完全对立,每个进程拥有独立的地址空间、文件描述符、全局变量等,进程间无法直接访问彼此资源,需通过管道、共享内存等 IPC 机制通信。
- 线程:同一进程内的线程共享绝大部分资源(代码段、数据段、堆区、全局变量、打开的文件等),仅栈区独立(每个线程有专属 8MB 栈区,存储局部变量和函数调用栈)。这种特性让线程间通信成本极低,但也要求编程时注意资源竞争问题。
2.2 稳定性差异
进程是操作系统独立的资源分配单元,一个进程的崩溃(如段错误)只会释放自身资源,不会影响其他进程;而线程共享进程的资源空间,若某个线程触发内存非法访问等错误,操作系统会终止整个进程,导致所有线程一同退出。
2.3 创建开销与并发度
- 创建开销:新建进程需要完整分配 3GB 用户空间,涉及页表、资源拷贝等大量操作;新建线程仅需在进程已有空间内开辟 8MB 栈区,几乎无额外资源分配成本。
- 并发度:线程的创建和上下文切换开销远小于进程,系统可同时调度成百上千个线程,而进程数量受限于内存和系统资源,因此线程的并发能力远高于进程。
2.4 执行与层级特性
补充说明线程的核心执行特征:
- 进程中的所有线程为平级关系,无父子线程之分;每个进程默认包含一个主线程,
main函数运行在主线程中。 - 进程是 “资源容器”,线程是容器内的 “执行流”—— 没有进程的资源支撑,线程无法独立存在;没有线程,进程只是一个静态的资源集合,无法执行任何逻辑。
三、Linux 下查看线程信息的命令
在 Linux 中,我们可以通过以下命令查看线程的运行状态,这对调试多线程程序非常有帮助:
ps -eLo pid,ppid,lwp,stat,comm该命令可以查看系统中所有线程的关键信息,各字段含义如下:pid:线程所属进程的 IDppid:进程的父进程 IDlwp:线程的 ID(轻量级进程 ID)stat:线程的运行状态comm:线程对应的命令名称
ps -eLf该命令会以更详细的格式列出所有线程的信息,包括线程的优先级、CPU 占用时间等。
四、POSIX 线程编程步骤与核心函数
Linux 下的线程编程遵循 POSIX 标准,对应的线程库为pthread,编程的核心步骤为:创建多线程 → 线程空间操作 → 线程资源回收。下面逐一介绍核心函数的使用方法,并为每个函数提供独立的可运行示例代码。
4.1 线程创建:pthread_create
函数说明
#include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);- 功能:创建一个新的线程
- 参数说明
thread:指向pthread_t类型变量的指针,用于存储新创建线程的 ID(由函数返回)。attr:线程属性,一般设为NULL,表示使用默认属性。start_routine:函数指针,指向线程的执行函数(回调函数),该函数的返回值和参数均为void*类型,是线程的核心执行逻辑。arg:传递给回调函数start_routine的参数。
- 返回值:成功返回 0;失败返回对应的错误码。
独立示例代码
#include <stdio.h> #include <pthread.h> #include <string.h> #include <unistd.h> // 线程执行函数 void *thread_func(void *arg) { char *msg = (char *)arg; printf("子线程ID:%lu,接收参数:%s\n", pthread_self(), msg); sleep(2); // 模拟线程执行耗时操作 printf("子线程执行完成\n"); return NULL; } int main() { pthread_t tid; char *msg = "Hello Thread"; // 创建线程 int ret = pthread_create(&tid, NULL, thread_func, msg); if (ret != 0) { printf("创建线程失败:%s\n", strerror(ret)); return -1; } printf("主线程:创建线程成功,线程ID:%lu\n", tid); sleep(3); // 主线程等待子线程执行完成 printf("主线程执行完成\n"); return 0; }编译运行
gcc pthread_create_demo.c -o pthread_create_demo -lpthread ./pthread_create_demo4.2 获取线程 ID:pthread_self
函数说明
#include <pthread.h> pthread_t pthread_self(void);- 功能:获取当前线程的 ID
- 参数:无
- 返回值:成功返回当前线程的 ID(类型为
pthread_t,可通过%lu格式符打印);失败返回非 0 值。
独立示例代码
#include <stdio.h> #include <pthread.h> #include <unistd.h> void *thread_func(void *arg) { // 获取子线程ID pthread_t tid = pthread_self(); printf("子线程:自身ID = %lu\n", tid); return NULL; } int main() { pthread_t tid; // 创建线程 pthread_create(&tid, NULL, thread_func, NULL); // 获取主线程ID printf("主线程:自身ID = %lu\n", pthread_self()); printf("主线程:创建的子线程ID = %lu\n", tid); sleep(1); // 等待子线程执行 return 0; }编译运行
gcc pthread_self_demo.c -o pthread_self_demo -lpthread ./pthread_self_demo4.3 线程退出:pthread_exit
函数说明
#include <pthread.h> void pthread_exit(void *retval);- 功能:子线程自行退出
- 参数:
retval:线程退出时的返回状态,可被后续的资源回收函数获取。 - 返回值:无
独立示例代码
#include <stdio.h> #include <pthread.h> #include <unistd.h> void *thread_func(void *arg) { printf("子线程:开始执行,准备退出\n"); sleep(2); // 定义退出返回值 int *ret = (int *)malloc(sizeof(int)); *ret = 99; // 线程退出,传递返回值 pthread_exit((void *)ret); } int main() { pthread_t tid; void *ret_val; pthread_create(&tid, NULL, thread_func, NULL); // 等待子线程退出并回收资源 pthread_join(tid, &ret_val); printf("主线程:子线程退出,返回值 = %d\n", *(int *)ret_val); free(ret_val); // 释放子线程分配的内存 return 0; }编译运行
gcc pthread_exit_demo.c -o pthread_exit_demo -lpthread ./pthread_exit_demo4.4 线程取消:pthread_cancel
函数说明
#include <pthread.h> int pthread_cancel(pthread_t thread);- 功能:请求结束一个指定的线程
- 参数:
thread:需要被取消的线程的 ID - 返回值:成功返回 0;失败返回非 0 值。
独立示例代码
#include <stdio.h> #include <pthread.h> #include <unistd.h> #include <string.h> void *thread_func(void *arg) { printf("子线程:开始执行,进入循环\n"); // 线程设置为可取消(默认状态) pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); // 设置取消类型为立即取消(接收到取消信号后立即退出) pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); while (1) { sleep(1); printf("子线程:循环执行中...\n"); } return NULL; } int main() { pthread_t tid; pthread_create(&tid, NULL, thread_func, NULL); // 主线程等待3秒后取消子线程 sleep(3); int ret = pthread_cancel(tid); if (ret != 0) { printf("取消线程失败:%s\n", strerror(ret)); return -1; } printf("主线程:已发送取消线程的请求\n"); // 回收子线程资源 pthread_join(tid, NULL); printf("主线程:子线程已被取消并回收\n"); return 0; }编译运行
gcc pthread_cancel_demo.c -o pthread_cancel_demo -lpthread ./pthread_cancel_demo4.5 线程资源回收:pthread_joinvspthread_detach
线程退出后,其栈区等资源不会默认释放,必须通过特定方式回收。pthread_join和pthread_detach是两种核心回收方式,二者核心区别如下:
| 特性 | pthread_join | pthread_detach |
|---|---|---|
| 执行方式 | 阻塞式(等待目标线程退出后才返回) | 非阻塞式(设置属性后立即返回) |
| 资源回收时机 | 线程退出后,由调用方主动回收 | 线程退出后,由系统自动回收 |
| 退出状态获取 | 可通过retval获取线程退出状态 | 无法获取线程退出状态 |
| 调用限制 | 只能被一个线程调用,重复调用会报错 | 仅需调用一次,线程退出后自动生效 |
| 适用场景 | 需要获取线程执行结果、需同步等待线程结束 | 无需获取线程结果、希望异步释放资源 |
4.5.1pthread_join(阻塞式回收)
函数说明
#include <pthread.h> int pthread_join(pthread_t thread, void **retval);- 功能:回收指定线程的资源,并获取线程的退出状态
- 参数说明
thread:需要回收的子线程的 IDretval:指向void*类型的指针,用于存储线程的退出状态(即pthread_exit传递的参数)
- 返回值:成功返回 0;失败返回非 0 值。
独立示例代码
#include <stdio.h> #include <pthread.h> #include <unistd.h> #include <string.h> void *thread_func(void *arg) { int num = *(int *)arg; printf("子线程:接收参数 = %d,开始执行\n", num); sleep(3); // 模拟耗时操作 // 退出并返回计算结果 int *ret = malloc(sizeof(int)); *ret = num * 2; pthread_exit((void *)ret); } int main() { pthread_t tid; int arg = 10; void *ret_val; // 创建线程 int ret = pthread_create(&tid, NULL, thread_func, &arg); if (ret != 0) { printf("创建线程失败:%s\n", strerror(ret)); return -1; } printf("主线程:等待子线程执行完成...\n"); // 阻塞回收子线程,获取返回值 ret = pthread_join(tid, &ret_val); if (ret != 0) { printf("回收线程失败:%s\n", strerror(ret)); return -1; } printf("主线程:子线程回收成功,返回值 = %d\n", *(int *)ret_val); free(ret_val); // 释放内存 return 0; }编译运行
gcc pthread_join_demo.c -o pthread_join_demo -lpthread ./pthread_join_demo4.5.2pthread_detach(分离式回收)
函数说明
#include <pthread.h> int pthread_detach(pthread_t thread);- 功能:设置线程的分离属性
- 参数:
thread:需要设置分离属性的线程的 ID(可以是当前线程,如pthread_detach(pthread_self())) - 返回值:成功返回 0;失败返回非 0 值。
独立示例代码
#include <stdio.h> #include <pthread.h> #include <unistd.h> #include <string.h> void *thread_func(void *arg) { // 为当前线程设置分离属性 int ret = pthread_detach(pthread_self()); if (ret != 0) { printf("子线程:设置分离属性失败:%s\n", strerror(ret)); pthread_exit(NULL); } printf("子线程:已设置分离属性,开始执行\n"); sleep(2); printf("子线程:执行完成,即将退出(资源会被系统自动回收)\n"); pthread_exit(NULL); } int main() { pthread_t tid; // 创建线程 int ret = pthread_create(&tid, NULL, thread_func, NULL); if (ret != 0) { printf("创建线程失败:%s\n", strerror(ret)); return -1; } printf("主线程:创建分离线程成功,ID = %lu\n", tid); // 尝试join分离线程(会失败) ret = pthread_join(tid, NULL); if (ret != 0) { printf("主线程:尝试join分离线程失败(预期结果):%s\n", strerror(ret)); } // 主线程等待子线程执行完成 sleep(3); printf("主线程:程序执行结束\n"); return 0; }编译运行
gcc pthread_detach_demo.c -o pthread_detach_demo -lpthread ./pthread_detach_demo五、总结
线程作为 Linux 下的轻量级进程,凭借其低开销、高并发的特性,成为实现并发程序的重要工具。与进程相比,线程共享进程资源(仅栈区独立)、创建开销仅为开辟 8MB 栈区(进程需 3GB 空间)、并发度更高,但稳定性更弱 —— 单个线程崩溃会导致整个进程退出。
在 POSIX 线程编程中,每个核心函数都有明确的定位:
pthread_create是线程创建的入口,需指定执行函数和参数;pthread_self用于获取线程自身 ID,便于调试和线程标识;pthread_exit让线程主动退出并传递返回值,pthread_cancel可被动终止线程;pthread_join(阻塞回收)适合需要获取线程执行结果的场景,pthread_detach(分离回收)适合无需同步等待的场景。