
在 Linux 多线程开发中,“线程控制” 是贯穿始终的核心技能 —— 从线程的创建、终止,到等待、分离,每一步操作都直接影响程序的性能、稳定性和资源利用率。而要熟练掌握线程控制,首先必须理清一个关键问题:进程和线程究竟哪些资源共享、哪些资源独占?这是理解线程控制逻辑的底层基石。 很多开发者在编写多线程程序时,常会陷入这样的困境:明明调用了
pthread_create却创建失败,线程退出后出现资源泄漏,用pthread_join等待线程却始终阻塞,甚至因误操作导致整个进程崩溃。这些问题的根源,往往是对线程与进程的资源关系理解不深,或是对 POSIX 线程库的控制接口使用不当。 本文将从 “进程与线程的资源划分” 入手,层层递进讲解 Linux 线程的完整控制流程 —— 包括 POSIX 线程库的使用、线程创建、终止、等待、分离等核心操作,全程结合实战代码和底层原理,用通俗的语言拆解复杂概念,让你不仅 “会用” 线程控制接口,更能 “懂原理”,轻松避开多线程开发的各种坑。下面就让我们正式开始吧!
要理解线程控制的本质,首先要明确:线程是进程内部的执行流,进程是资源分配的基本单位,线程是调度的基本单位。这一定位决定了线程与进程的资源关系 —— 线程共享进程的大部分资源,同时拥有少量私有资源。
在 Linux 系统中,进程和线程的核心区别可以用一句话概括:

举个通俗的例子:如果把计算机系统比作一个大型工厂,进程就是工厂里的一个个车间 —— 每个车间都有自己的场地、设备、工具(对应进程的资源);而线程就是车间里的工人 —— 工人共享车间的所有资源,同时各自执行不同的任务,协同完成车间的整体目标。一个车间至少有一个工人(单线程进程),也可以有多个工人(多线程进程)。
虽然线程共享进程的大部分资源,但为了保证线程的独立执行,每个线程都拥有自己的 “私有财产”,这些资源不会被其他线程共享:
mmap系统调用分配,默认大小一般为 8MB,且不能动态增长。errno副本,避免多个线程同时修改导致错误码混乱。同一个进程的所有线程,共享进程的全部核心资源,这是线程高效协作的基础:
SIG_IGN忽略、SIG_DFL默认处理、自定义处理函数)对所有线程有效。线程可以修改信号处理方式,但修改后会作用于整个进程。chdir()修改工作目录,其他线程的工作目录也会随之改变。很多开发者会疑惑:之前学习的单进程程序,和线程有什么关系?
答案很简单:单进程本质上是 “只有一个线程执行流的进程”。这个线程就是主线程(main 线程),它拥有进程的全部资源,独自执行main函数中的代码。当我们创建新的线程后,进程就变成了多线程进程,多个线程共享进程资源,协同执行不同的任务。
理解这一点很重要:线程控制的所有操作,本质上都是在 “进程的资源框架内” 对执行流进行管理,不会改变进程的资源分配状态。
Linux 系统中,线程控制的接口主要由POSIX 线程库(pthread 库) 提供,这是一套标准的线程操作 API,绝大多数函数都以pthread_为前缀。要使用这些函数,必须掌握其编译链接方式、错误处理规则,这是线程控制的基础。
POSIX 线程库不是 Linux 系统的默认库,因此在编译多线程程序时,必须通过-lpthread选项明确链接该库,否则会出现 “未定义引用” 错误。
编译命令示例:
# 编译单个文件
gcc thread_demo.c -o thread_demo -lpthread
# 编译多个文件
gcc thread1.c thread2.c -o thread_app -lpthread 所有 pthread 库的函数声明、数据类型(如pthread_t、pthread_attr_t)都定义在<pthread.h>头文件中,使用时必须包含:
#include <pthread.h> 传统的 Linux 系统调用(如open、read、fork)通常返回 - 1 表示失败,并设置全局变量errno来指示错误原因。但 pthread 库的函数错误处理方式不同:
errno,而是直接返回错误码(非 0 值)。strerror()函数将错误码转换为人类可读的错误信息。错误处理示例:
#include <stdio.h>
#include <pthread.h>
#include <string.h> // 包含strerror函数
int main() {
pthread_t tid;
// 故意传递错误的参数(如NULL线程函数),触发创建失败
int ret = pthread_create(&tid, NULL, NULL, NULL);
if (ret != 0) {
// 用strerror将错误码转换为错误信息
fprintf(stderr, "创建线程失败:%s\n", strerror(ret));
return 1;
}
return 0;
}运行结果:
创建线程失败:Invalid argument 注意:pthread 库也提供了线程内的errno副本,以兼容依赖errno的代码,但对于 pthread 函数的错误,建议直接通过返回值判断,效率更高。
线程创建是线程控制的第一步,通过pthread_create函数创建新线程,新线程会执行指定的函数,与主线程并行运行。
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void*), void *arg);pthread_t类型的变量,用于存储新创建线程的用户级 ID(进程内唯一)。后续对该线程的操作(如终止、等待)都需要通过这个 ID。NULL,表示使用默认属性。void*,参数类型也为void*,这是 POSIX 标准规定的线程函数签名,便于传递任意类型的数据。start_routine函数的参数,可以是任意类型的指针。若需要传递多个参数,可封装为结构体,将结构体指针作为arg传入。EINVAL(参数无效,如start_routine为 NULL)、EAGAIN(系统资源不足,无法创建新线程)等。#include <stdio.h>
#include <pthread.h>
#include <unistd.h> // 包含sleep函数
// 线程执行函数
void *thread_func(void *arg) {
// 循环打印线程信息
for (int i = 0; i < 5; i++) {
printf("子线程:我正在运行,线程ID = %lu\n", (unsigned long)pthread_self());
sleep(1); // 休眠1秒
}
return NULL; // 线程退出,返回NULL
}
int main() {
pthread_t tid;
// 创建线程
int ret = pthread_create(&tid, NULL, thread_func, NULL);
if (ret != 0) {
fprintf(stderr, "创建线程失败:%s\n", strerror(ret));
return 1;
}
printf("主线程:成功创建子线程,子线程ID = %lu\n", (unsigned long)tid);
// 主线程循环打印信息
for (int i = 0; i < 5; i++) {
printf("主线程:我正在运行,线程ID = %lu\n", (unsigned long)pthread_self());
sleep(1);
}
// 等待子线程结束(后续详细讲解)
pthread_join(tid, NULL);
printf("主线程:子线程已退出,程序结束\n");
return 0;
}编译运行:
gcc thread_simple.c -o thread_simple -lpthread
./thread_simple运行结果(主线程和子线程交替执行):
主线程:成功创建子线程,子线程ID = 140703347508992
主线程:我正在运行,线程ID = 140703355896640
子线程:我正在运行,线程ID = 140703347508992
主线程:我正在运行,线程ID = 140703355896640
子线程:我正在运行,线程ID = 140703347508992
主线程:我正在运行,线程ID = 140703355896640
子线程:我正在运行,线程ID = 140703347508992
主线程:我正在运行,线程ID = 140703355896640
子线程:我正在运行,线程ID = 140703347508992
主线程:我正在运行,线程ID = 140703355896640
子线程:我正在运行,线程ID = 140703347508992
主线程:子线程已退出,程序结束#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 线程执行函数,接收一个整数参数
void *thread_with_arg(void *arg) {
// 将void*类型转换为int类型
int num = *(int*)arg;
for (int i = 0; i < num; i++) {
printf("子线程:第%d次运行,线程ID = %lu\n", i+1, (unsigned long)pthread_self());
sleep(1);
}
return NULL;
}
int main() {
pthread_t tid;
int run_count = 3; // 要传递给线程的参数
// 创建线程,传递run_count的地址
int ret = pthread_create(&tid, NULL, thread_with_arg, &run_count);
if (ret != 0) {
fprintf(stderr, "创建线程失败:%s\n", strerror(ret));
return 1;
}
// 等待子线程结束
pthread_join(tid, NULL);
printf("主线程:子线程执行完毕,程序结束\n");
return 0;
}#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
// 定义要传递给线程的参数结构体
typedef struct {
char name[20]; // 线程名称
int age; // 自定义整数参数
float score; // 自定义浮点数参数
} ThreadArgs;
// 线程执行函数,接收结构体参数
void *thread_with_struct_arg(void *arg) {
ThreadArgs *args = (ThreadArgs*)arg;
printf("子线程:线程名称 = %s,年龄 = %d,分数 = %.2f\n",
args->name, args->age, args->score);
// 模拟业务逻辑
sleep(2);
printf("子线程:执行完毕\n");
return NULL;
}
int main() {
pthread_t tid;
// 初始化参数结构体
ThreadArgs args = {"Worker-1", 25, 98.5};
// 创建线程,传递结构体指针
int ret = pthread_create(&tid, NULL, thread_with_struct_arg, &args);
if (ret != 0) {
fprintf(stderr, "创建线程失败:%s\n", strerror(ret));
return 1;
}
// 等待子线程结束
pthread_join(tid, NULL);
printf("主线程:程序结束\n");
return 0;
}在 Linux 中,线程有两种 ID,用途不同,必须区分清楚:
pthread_create的thread参数获取,或通过pthread_self()函数获取当前线程的 ID。pthread_t的具体实现可能是整数、指针等,不建议直接用%d打印,推荐用%lu(转换为unsigned long)。ps -aL命令查看,LWP 列显示的就是内核级线程 ID。查看线程信息的 bash 命令:
# 编译运行线程程序后,查看线程信息
ps -aL | grep 程序名示例:运行示例 1 的thread_simple程序后,执行命令:
ps -aL | grep thread_simple输出结果:
12345 12345 pts/0 00:00:00 thread_simple # 主线程:PID=12345,LWP=12345
12345 12346 pts/0 00:00:00 thread_simple # 子线程:PID=12345,LWP=12346void*,可以返回任意类型的数据,但返回的内存必须是全局变量或堆内存(malloc分配),不能是局部变量(线程退出后局部变量会被释放,导致野指针)。pthread_create返回EAGAIN错误。可以通过修改/etc/security/limits.conf文件调整限制。线程终止是指线程停止执行,释放自己的私有资源(如栈、寄存器上下文等),但进程的共享资源不会被释放。Linux 提供了三种合法的线程终止方式,以及一种不推荐的强制终止方式。
线程函数执行完毕后,通过return语句返回,线程正常终止。这种方式最优雅,可以自然地清理线程内的局部变量,并且可以返回线程的执行结果。
示例:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
// 线程函数返回一个整数结果
void *thread_return(void *arg) {
int *result = (int*)malloc(sizeof(int));
*result = 100; // 线程执行结果
printf("子线程:执行完毕,返回结果 = %d\n", *result);
return (void*)result; // 返回堆内存的指针
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_return, NULL);
// 等待线程结束,获取返回值
void *ret;
pthread_join(tid, &ret);
printf("主线程:获取子线程返回值 = %d\n", *(int*)ret);
free(ret); // 释放子线程分配的堆内存
return 0;
} 线程可以通过调用pthread_exit函数主动终止自己,该函数不会返回,相当于线程的 “自杀” 函数。pthread_exit的参数可以传递线程的退出状态,供其他线程通过pthread_join获取。
函数原型:
void pthread_exit(void *value_ptr);value_ptr:指向线程退出状态的指针,与线程函数return的返回值作用相同。该指针指向的内存必须是全局变量或堆内存,不能是局部变量。示例:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
void *thread_exit_demo(void *arg) {
int *result = (int*)malloc(sizeof(int));
*result = 200;
printf("子线程:调用pthread_exit终止\n");
pthread_exit((void*)result); // 终止线程,传递结果
// 以下代码不会执行
printf("子线程:这行代码永远不会被执行\n");
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_exit_demo, NULL);
void *ret;
pthread_join(tid, &ret);
printf("主线程:获取子线程退出状态 = %d\n", *(int*)ret);
free(ret);
return 0;
}main函数 return) 主线程执行完main函数的代码后return,相当于调用exit函数,会终止整个进程,进程内的所有线程都会随之退出。
示例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *thread_func(void *arg) {
while (1) {
printf("子线程:正在运行...\n");
sleep(1);
}
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
// 主线程休眠3秒后退出
sleep(3);
printf("主线程:退出,所有子线程将被终止\n");
return 0; // 主线程退出,子线程也会随之终止
} 一个线程可以调用pthread_cancel函数,强制终止同一进程中的另一个线程。这种方式相当于 “杀死” 其他线程,使用时需要谨慎,因为可能导致资源泄漏(如线程持有互斥锁未释放)。
函数原型:
int pthread_cancel(pthread_t thread);thread:要终止的线程的用户级 ID(pthread_t类型)。注意事项:
pthread_cancel只是向目标线程发送一个 “取消请求”,目标线程不会立即终止,而是在到达 “取消点” 时才会响应请求。sleep、read、write、pthread_join等系统调用和库函数。pthread_setcancelstate函数设置自己的取消状态(启用或禁用),通过pthread_setcanceltype函数设置取消类型(立即取消或延迟取消)。示例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *thread_to_cancel(void *arg) {
int i = 0;
while (1) {
printf("子线程:第%d次运行,等待取消...\n", ++i);
sleep(1); // sleep是取消点
}
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_to_cancel, NULL);
// 主线程休眠3秒后,强制终止子线程
sleep(3);
printf("主线程:发送取消请求\n");
int ret = pthread_cancel(tid);
if (ret != 0) {
fprintf(stderr, "取消线程失败:%s\n", strerror(ret));
return 1;
}
// 等待子线程终止,获取取消状态
void *cancel_ret;
pthread_join(tid, &cancel_ret);
if (cancel_ret == PTHREAD_CANCELED) {
printf("主线程:子线程已被成功取消\n");
}
return 0;
}运行结果:
子线程:第1次运行,等待取消...
子线程:第2次运行,等待取消...
子线程:第3次运行,等待取消...
主线程:发送取消请求
主线程:子线程已被成功取消 线程中绝对不要调用exit函数!exit函数的作用是终止整个进程,而不是当前线程。一个线程调用exit,会导致进程内的所有线程(包括主线程)都被终止,这通常不是我们想要的结果。
错误示例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h> // 包含exit函数
void *bad_thread(void *arg) {
printf("子线程:调用exit终止\n");
exit(EXIT_FAILURE); // 错误:终止整个进程,主线程也会被终止
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, bad_thread, NULL);
sleep(1);
printf("主线程:这行代码永远不会被执行\n"); // 不会输出
return 0;
}线程终止后,其私有资源(如栈、线程控制块 TCB)不会自动释放,仍然占用进程的地址空间。如果主线程不处理这些资源,会导致系统资源泄漏。线程等待就是主线程(或其他线程)等待目标线程终止,回收其资源,并获取其退出状态的过程。

POSIX 线程库提供pthread_join函数实现线程等待,该函数会阻塞调用线程(通常是主线程),直到目标线程终止。
函数原型:
int pthread_join(pthread_t thread, void **value_ptr);pthread_t类型)。NULL。ESRCH:目标线程不存在,EDEADLK:死锁,如线程等待自己)。线程终止方式 | value_ptr存储的内容 |
|---|---|
从线程函数return返回 | 线程函数的返回值(void*类型) |
调用pthread_exit终止 | pthread_exit的参数(void*类型) |
被pthread_cancel取消 | 特殊常量PTHREAD_CANCELED |
线程异常终止(如段错误) | 无定义(进程会随之崩溃,无法通过pthread_join获取) |
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
void *thread_return_val(void *arg) {
int *ret = (int*)malloc(sizeof(int));
*ret = 300;
printf("子线程:return返回,结果 = %d\n", *ret);
return (void*)ret;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_return_val, NULL);
// 等待线程,获取返回值
void *value_ptr;
int ret = pthread_join(tid, &value_ptr);
if (ret != 0) {
fprintf(stderr, "等待线程失败:%s\n", strerror(ret));
return 1;
}
printf("主线程:子线程返回值 = %d\n", *(int*)value_ptr);
free(value_ptr); // 释放子线程分配的内存
return 0;
}#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
void *thread_exit_val(void *arg) {
char *msg = (char*)malloc(20);
strcpy(msg, "线程执行成功");
printf("子线程:pthread_exit退出\n");
pthread_exit((void*)msg);
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_exit_val, NULL);
void *value_ptr;
pthread_join(tid, &value_ptr);
printf("主线程:子线程退出状态 = %s\n", (char*)value_ptr);
free(value_ptr);
return 0;
}#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *thread_canceled(void *arg) {
while (1) {
printf("子线程:运行中...\n");
sleep(1); // 取消点
}
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_canceled, NULL);
sleep(2);
pthread_cancel(tid); // 发送取消请求
void *value_ptr;
pthread_join(tid, &value_ptr);
if (value_ptr == PTHREAD_CANCELED) {
printf("主线程:子线程已被取消\n");
} else {
printf("主线程:子线程正常退出\n");
}
return 0;
}#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *thread_no_val(void *arg) {
printf("子线程:执行任务...\n");
sleep(2);
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_no_val, NULL);
// 传递NULL给value_ptr,不关心退出状态
pthread_join(tid, NULL);
printf("主线程:子线程已终止,资源已回收\n");
return 0;
}joinable状态,必须通过pthread_join等待,否则会资源泄漏。如果线程设置为分离状态(detached),pthread_join会返回EINVAL错误。ESRCH错误)。pthread_join是阻塞函数,调用后会暂停当前线程的执行,直到目标线程终止。如果需要非阻塞等待,可以结合pthread_tryjoin_np函数(非标准函数,_np表示 non-portable)。 默认情况下,线程是joinable状态,需要通过pthread_join等待回收资源。但在某些场景下(如不关心线程的退出状态),pthread_join会成为一种负担。这时可以将线程设置为分离状态(detached),线程终止后会自动释放资源,无需其他线程等待。
pthread_join,避免资源泄漏。joinid字段会指向自身(pd->joinid = pd),内核会在线程终止时自动清理其资源,无需等待其他线程的pthread_join调用。 通过pthread_detach函数可以将线程设置为分离状态,该函数可以由其他线程调用,也可以由线程自身调用。
函数原型:
int pthread_detach(pthread_t thread);thread:要设置为分离状态的线程的用户级 ID。 线程在启动后,主动调用pthread_detach(pthread_self()),将自己设置为分离状态。这种方式最灵活,线程可以自主决定是否分离。
示例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *detached_thread_self(void *arg) {
// 将自己设置为分离状态
int ret = pthread_detach(pthread_self());
if (ret != 0) {
fprintf(stderr, "设置线程分离失败:%s\n", strerror(ret));
return NULL;
}
printf("分离线程:我是分离状态,终止后会自动回收资源\n");
sleep(2);
printf("分离线程:执行完毕,即将终止\n");
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, detached_thread_self, NULL);
// 主线程休眠3秒,确保子线程执行完毕
sleep(3);
printf("主线程:程序结束,无需调用pthread_join\n");
return 0;
} 主线程(或其他线程)在创建目标线程后,调用pthread_detach将其设置为分离状态。这种方式需要确保在目标线程终止前完成分离设置,否则可能导致资源泄漏。
示例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *detached_thread_other(void *arg) {
printf("分离线程:由主线程设置为分离状态\n");
sleep(2);
printf("分离线程:执行完毕\n");
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, detached_thread_other, NULL);
// 主线程将子线程设置为分离状态
int ret = pthread_detach(tid);
if (ret != 0) {
fprintf(stderr, "设置线程分离失败:%s\n", strerror(ret));
return 1;
}
sleep(3);
printf("主线程:程序结束\n");
return 0;
}joinable和detached状态。一旦设置为detached状态,就不能再通过pthread_join等待,否则会返回EINVAL错误。exit或main函数return),所有子线程都会被终止,分离状态仅影响线程终止后的资源回收,不影响线程的生命周期。#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
void *detached_thread_err(void *arg) {
pthread_detach(pthread_self()); // 设置为分离状态
sleep(2);
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, detached_thread_err, NULL);
sleep(1); // 确保子线程已设置为分离状态
// 错误:等待分离状态的线程
int ret = pthread_join(tid, NULL);
if (ret != 0) {
fprintf(stderr, "等待分离线程失败:%s\n", strerror(ret));
return 1;
}
return 0;
}运行结果:
等待分离线程失败:Invalid argument线程控制是 Linux 多线程开发的基础,掌握这些操作后,才能进一步学习线程同步(互斥锁、条件变量、信号量)、线程安全等高级主题。后续将继续讲解线程同步、线程安全、线程池等高级主题,敬请关注! 创作不易,若本文对你有帮助,欢迎点赞、收藏、关注!
