Linux C线程编程全指南
Linux C语言线程编程详解
一、POSIX 线程基础
1.1 什么是线程?
-
线程(Thread)是进程内的执行单元,是CPU调度的基本单位
-
同一进程内的多个线程共享:
- 进程代码段
- 数据段(全局变量)
- 打开的文件描述符
- 信号处理器
- 当前工作目录
- 用户ID和组ID
-
每个线程独有:
- 线程ID
- 寄存器状态(包括程序计数器和栈指针)
- 栈空间
- errno变量
- 信号屏蔽字
- 优先级
1.2 编译链接
使用pthread库需要链接 -lpthread:
gcc -o program program.c -lpthread
头文件:
#include <pthread.h>
二、核心函数详解
2.1 pthread_create() - 创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine)(void *), void *arg);
参数详解:
-
pthread_t *thread- 指向线程标识符的指针
- 函数成功返回后,新线程的ID会被写入该指针指向的内存
pthread_t是一个不透明类型,不要直接操作其内容
-
const pthread_attr_t *attr- 线程属性对象指针
- 传入
NULL使用默认属性 - 可通过
pthread_attr_init()初始化后设置:- 分离状态(detached/joinable)
- 栈大小
- 调度策略
- 调度优先级
-
void *(*start_routine)(void *)- 线程启动函数指针
- 函数签名必须是:
void* function_name(void* arg) - 线程从这个函数开始执行
- 函数返回时线程终止
-
void *arg- 传递给启动函数的参数
- 可以传递任何类型的指针(需要类型转换)
- 传递多个参数时通常使用结构体
返回值:
- 成功返回
0 - 失败返回错误码(不设置errno):
EAGAIN: 系统资源不足EINVAL: 无效的属性设置EPERM: 无权限设置调度策略
示例:
void *thread_func(void *arg) {int id = *(int *)arg;printf("Thread %d running\n", id);return NULL;
}int main() {pthread_t tid;int thread_id = 1;if (pthread_create(&tid, NULL, thread_func, &thread_id) != 0) {perror("pthread_create failed");return 1;}pthread_join(tid, NULL);return 0;
}
2.2 pthread_join() - 等待线程结束
int pthread_join(pthread_t thread, void **retval);
参数详解:
-
pthread_t thread- 要等待的线程ID
- 必须是joinable状态的线程
- 不能重复join同一个线程
-
void **retval- 指向指针的指针,用于接收线程返回值
- 传入
NULL表示不关心返回值 - 线程通过
return或pthread_exit()返回值
返回值:
- 成功返回
0 - 失败返回错误码:
EDEADLK: 检测到死锁EINVAL: 线程不是joinable的ESRCH: 找不到该线程
示例:
void *thread_func(void *arg) {int *result = malloc(sizeof(int));*result = 42;return result;
}int main() {pthread_t tid;void *ret_val;pthread_create(&tid, NULL, thread_func, NULL);pthread_join(tid, &ret_val);printf("Thread returned: %d\n", *(int *)ret_val);free(ret_val);return 0;
}
2.3 pthread_exit() - 线程退出
void pthread_exit(void *retval);
参数详解:
void *retval- 线程退出状态,可被
pthread_join()获取 - 不要返回指向线程栈上的局部变量的指针
- 传入
NULL表示无返回值
- 线程退出状态,可被
注意事项:
- 如果主线程调用
pthread_exit(),其他线程会继续运行 - 如果主线程从
main()return,整个进程结束
2.4 pthread_self() - 获取当前线程ID
pthread_t pthread_self(void);
返回调用线程的ID,总是成功。
2.5 pthread_detach() - 分离线程
int pthread_detach(pthread_t thread);
参数详解:
pthread_t thread- 要分离的线程ID
- 可以是自己
pthread_detach(pthread_self())
作用:
- 将线程标记为detached状态
- 线程结束后资源自动释放,无需join
- detached的线程不能再被join
2.6 pthread_cancel() - 取消线程
int pthread_cancel(pthread_t thread);
向指定线程发送取消请求,但不会立即终止线程。线程需要到达取消点才会响应。
三、线程同步
3.1 互斥锁 (Mutex)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;// 初始化
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);// 加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);// 尝试加锁(非阻塞)
int pthread_mutex_trylock(pthread_mutex_t *mutex);// 解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);// 销毁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
3.2 条件变量 (Condition Variable)
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;// 初始化
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);// 等待条件
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);// 唤醒一个线程
int pthread_cond_signal(pthread_cond_t *cond);// 唤醒所有线程
int pthread_cond_broadcast(pthread_cond_t *cond);// 销毁
int pthread_cond_destroy(pthread_cond_t *cond);
四、常见陷阱和注意事项
4.1 ⚠️ 传递参数的陷阱
错误示例:
// 危险!循环变量的地址
for (int i = 0; i < 5; i++) {pthread_create(&tid[i], NULL, thread_func, &i); // ❌
}
问题: 所有线程可能读取到相同的值,因为 i 的地址是同一个。
正确做法:
// 方法1: 为每个线程分配独立内存
for (int i = 0; i < 5; i++) {int *arg = malloc(sizeof(int));*arg = i;pthread_create(&tid[i], NULL, thread_func, arg);
}// 方法2: 使用数组
int args[5];
for (int i = 0; i < 5; i++) {args[i] = i;pthread_create(&tid[i], NULL, thread_func, &args[i]);
}// 方法3: 直接传值(强制类型转换,仅适用于小整数)
for (int i = 0; i < 5; i++) {pthread_create(&tid[i], NULL, thread_func, (void *)(intptr_t)i);
}
4.2 ⚠️ 返回栈上的指针
错误示例:
void *thread_func(void *arg) {int result = 42;return &result; // ❌ 返回局部变量地址
}
正确做法:
// 方法1: 动态分配内存
void *thread_func(void *arg) {int *result = malloc(sizeof(int));*result = 42;return result; // 调用者负责释放
}// 方法2: 使用全局变量或静态变量
void *thread_func(void *arg) {static int result = 42;return &result;
}
4.3 ⚠️ 忘记Join或Detach
问题: 如果线程是joinable状态但从未被join,会导致资源泄漏。
正确做法:
// 要么join
pthread_join(tid, NULL);// 要么detach
pthread_detach(tid);// 或者创建时就设置为detached
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, thread_func, NULL);
pthread_attr_destroy(&attr);
4.4 ⚠️ 主线程过早退出
错误示例:
int main() {pthread_t tid;pthread_create(&tid, NULL, thread_func, NULL);return 0; // ❌ 主线程退出,整个进程结束
}
正确做法:
int main() {pthread_t tid;pthread_create(&tid, NULL, thread_func, NULL);pthread_join(tid, NULL); // 等待线程结束return 0;
}// 或者主线程调用pthread_exit
int main() {pthread_t tid;pthread_create(&tid, NULL, thread_func, NULL);pthread_exit(NULL); // 主线程退出,但进程继续
}
4.5 ⚠️ 竞态条件 (Race Condition)
错误示例:
int counter = 0;void *thread_func(void *arg) {for (int i = 0; i < 100000; i++) {counter++; // ❌ 非原子操作}return NULL;
}
正确做法:
int counter = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;void *thread_func(void *arg) {for (int i = 0; i < 100000; i++) {pthread_mutex_lock(&mutex);counter++;pthread_mutex_unlock(&mutex);}return NULL;
}
4.6 ⚠️ 死锁 (Deadlock)
常见死锁场景:
- 重复加锁
pthread_mutex_lock(&mutex);
pthread_mutex_lock(&mutex); // ❌ 死锁
- 循环等待
// 线程1
pthread_mutex_lock(&mutex1);
pthread_mutex_lock(&mutex2);// 线程2
pthread_mutex_lock(&mutex2); // 可能死锁
pthread_mutex_lock(&mutex1);
避免死锁:
- 按固定顺序获取锁
- 使用
pthread_mutex_trylock() - 设置超时
- 避免嵌套锁
4.7 ⚠️ 条件变量的虚假唤醒
错误示例:
pthread_cond_wait(&cond, &mutex);
// 假设条件满足
正确做法:
while (!condition_satisfied) {pthread_cond_wait(&cond, &mutex);
}
// 现在条件确实满足
使用 while 而不是 if 来检查条件。
4.8 ⚠️ 编译链接错误
常见错误:
undefined reference to `pthread_create`
解决方法:
gcc program.c -o program -lpthread # 注意 -lpthread 必须在最后
五、实用技巧
5.1 获取CPU核心数
#include <unistd.h>int num_cores = sysconf(_SC_NPROCESSORS_ONLN);
5.2 线程数组管理
#define NUM_THREADS 4pthread_t threads[NUM_THREADS];// 创建
for (int i = 0; i < NUM_THREADS; i++) {pthread_create(&threads[i], NULL, thread_func, &args[i]);
}// 等待
for (int i = 0; i < NUM_THREADS; i++) {pthread_join(threads[i], NULL);
}
5.3 线程局部存储
__thread int thread_local_var = 0; // 每个线程独立的变量
六、完整示例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>#define NUM_THREADS 5typedef struct {int id;int iterations;
} thread_data_t;pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int shared_counter = 0;void *worker_thread(void *arg) {thread_data_t *data = (thread_data_t *)arg;printf("Thread %d started\n", data->id);for (int i = 0; i < data->iterations; i++) {pthread_mutex_lock(&mutex);shared_counter++;pthread_mutex_unlock(&mutex);usleep(10000); // 模拟工作}printf("Thread %d finished\n", data->id);free(data); // 释放传入的参数return NULL;
}int main() {pthread_t threads[NUM_THREADS];// 创建线程for (int i = 0; i < NUM_THREADS; i++) {thread_data_t *data = malloc(sizeof(thread_data_t));data->id = i;data->iterations = 100;if (pthread_create(&threads[i], NULL, worker_thread, data) != 0) {fprintf(stderr, "Error creating thread %d\n", i);free(data);exit(1);}}// 等待所有线程完成for (int i = 0; i < NUM_THREADS; i++) {pthread_join(threads[i], NULL);}printf("All threads completed. Counter = %d\n", shared_counter);pthread_mutex_destroy(&mutex);return 0;
}
七、调试技巧
7.1 检测竞态条件
使用 Thread Sanitizer:
gcc -fsanitize=thread -g program.c -o program -lpthread
./program
7.2 使用GDB调试多线程
gdb ./program
(gdb) break thread_func
(gdb) run
(gdb) info threads # 查看所有线程
(gdb) thread 2 # 切换到线程2
(gdb) where # 查看当前线程的调用栈
八、性能考虑
- 避免过度加锁: 锁的粒度要合适,太细影响性能,太粗降低并发
- 线程数量: 通常设置为CPU核心数或核心数的倍数
- 避免频繁创建销毁线程: 考虑使用线程池
- 减少锁竞争: 使用无锁数据结构或读写锁
九、Linux线程底层常识
9.1 线程实现模型
Linux线程的本质:
- Linux内核并不区分线程和进程,统一称为"任务"(task)
- 线程是通过
clone()系统调用创建的轻量级进程(LWP) - POSIX线程是在用户空间实现的,通过NPTL(Native POSIX Thread Library)
1:1 线程模型:
每个用户线程 <---> 一个内核线程(LWP)
- Linux采用1:1模型,每个pthread对应一个内核调度实体
- 优点: 真正的并行执行,阻塞不影响其他线程
- 缺点: 创建开销较大,线程切换有内核开销
9.2 线程ID的真相
两种线程ID:
-
pthread_t (POSIX线程ID)
pthread_t tid = pthread_self(); // 用户空间的线程标识符- 不透明类型,可能是结构体、整数或指针
- 仅在进程内有效,不同进程中的值可能相同
- 用于pthread_*函数
-
LWP ID (轻量级进程ID / 内核线程ID)
#include <sys/syscall.h> pid_t tid = syscall(SYS_gettid); // 内核中的线程ID- 系统范围内唯一的整数
- 可用于CPU亲和性设置、调度策略等
- top命令显示的就是LWP ID
易错点:
// ❌ 错误: 不能直接打印pthread_t
printf("%d", pthread_self()); // ✅ 正确: 获取内核线程ID
printf("LWP ID: %ld", (long)syscall(SYS_gettid));// ✅ 或者比较pthread_t
if (pthread_equal(tid1, tid2)) { ... }
9.3 线程栈的秘密
默认栈大小:
#include <pthread.h>
#include <sys/resource.h>// 查看默认栈大小
pthread_attr_t attr;
size_t stacksize;
pthread_attr_init(&attr);
pthread_attr_getstacksize(&attr, &stacksize);
printf("Default stack: %zu bytes\n", stacksize); // 通常是8MB
栈溢出陷阱:
void *thread_func(void *arg) {char buffer[10 * 1024 * 1024]; // ❌ 10MB数组,可能栈溢出// ... 程序可能崩溃或产生段错误
}
正确做法:
// 方法1: 使用堆内存
void *thread_func(void *arg) {char *buffer = malloc(10 * 1024 * 1024);// 使用buffer...free(buffer);
}// 方法2: 增加栈大小
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, 16 * 1024 * 1024); // 16MB
pthread_create(&tid, &attr, thread_func, NULL);
pthread_attr_destroy(&attr);
栈地址查看:
void *thread_func(void *arg) {int local_var;printf("Stack address: %p\n", (void*)&local_var);pthread_attr_t attr;void *stackaddr;size_t stacksize;pthread_getattr_np(pthread_self(), &attr);pthread_attr_getstack(&attr, &stackaddr, &stacksize);printf("Stack: %p - %p (size: %zu)\n", stackaddr, (char*)stackaddr + stacksize, stacksize);pthread_attr_destroy(&attr);
}
9.4 线程调度与优先级
调度策略:
Linux支持三种实时调度策略:
SCHED_FIFO: 先进先出实时调度SCHED_RR: 时间片轮转实时调度SCHED_OTHER: 普通分时调度(默认)
#include <sched.h>// 设置线程调度策略和优先级
pthread_attr_t attr;
struct sched_param param;pthread_attr_init(&attr);
pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
param.sched_priority = 50; // 1-99,数字越大优先级越高
pthread_attr_setschedparam(&attr, ¶m);
pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);pthread_create(&tid, &attr, thread_func, NULL);
陷阱:
// ❌ 忘记设置PTHREAD_EXPLICIT_SCHED,调度策略可能被忽略
pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
// 需要root权限或CAP_SYS_NICE能力
9.5 CPU亲和性 (CPU Affinity)
将线程绑定到特定CPU核心:
#define _GNU_SOURCE
#include <sched.h>
#include <pthread.h>void set_cpu_affinity(int cpu_id) {cpu_set_t cpuset;CPU_ZERO(&cpuset);CPU_SET(cpu_id, &cpuset);pthread_t current = pthread_self();pthread_setaffinity_np(current, sizeof(cpu_set_t), &cpuset);
}void *thread_func(void *arg) {int cpu = *(int*)arg;set_cpu_affinity(cpu); // 绑定到指定CPU// 验证绑定cpu_set_t cpuset;CPU_ZERO(&cpuset);pthread_getaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);for (int i = 0; i < CPU_SETSIZE; i++) {if (CPU_ISSET(i, &cpuset)) {printf("Thread running on CPU %d\n", i);}}
}
应用场景:
- 高性能计算,减少缓存失效
- 实时系统,避免线程迁移
- NUMA架构优化
9.6 信号处理的坑
问题: 信号会被任意线程接收
// ❌ 危险: 信号可能发送给任意线程
void signal_handler(int sig) {printf("Caught signal in thread %ld\n", pthread_self());
}int main() {signal(SIGINT, signal_handler);// 创建多个线程...// Ctrl+C 时,不确定哪个线程会处理信号
}
正确做法: 屏蔽信号,专门线程处理
#include <signal.h>void *signal_thread(void *arg) {sigset_t *set = (sigset_t *)arg;int sig;while (1) {sigwait(set, &sig); // 等待信号printf("Signal %d received by dedicated thread\n", sig);if (sig == SIGINT) break;}return NULL;
}int main() {sigset_t set;pthread_t sig_tid;// 1. 阻塞所有线程的信号sigemptyset(&set);sigaddset(&set, SIGINT);sigaddset(&set, SIGTERM);pthread_sigmask(SIG_BLOCK, &set, NULL);// 2. 创建工作线程(继承信号屏蔽字)// pthread_create(...);// 3. 创建专门的信号处理线程pthread_create(&sig_tid, NULL, signal_thread, &set);pthread_join(sig_tid, NULL);return 0;
}
9.7 线程取消的陷阱
取消点 (Cancellation Point):
并非所有函数都是取消点,线程不会立即取消:
void *thread_func(void *arg) {// 这个循环可能无法被取消!while (1) {// 密集计算,没有取消点}
}// 主线程
pthread_cancel(tid); // 发送取消请求
// 但线程可能永远不会取消!
解决方法:
// 方法1: 手动添加取消点
void *thread_func(void *arg) {while (1) {pthread_testcancel(); // 显式取消点// 做一些工作...}
}// 方法2: 使用异步取消(危险!)
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);// 方法3: 使用标志位(推荐)
volatile int stop_flag = 0;void *thread_func(void *arg) {while (!stop_flag) {// 工作...}
}// 主线程
stop_flag = 1;
pthread_join(tid, NULL);
取消清理处理器:
void cleanup_handler(void *arg) {printf("Cleanup: releasing resource %p\n", arg);free(arg);
}void *thread_func(void *arg) {void *resource = malloc(1024);pthread_cleanup_push(cleanup_handler, resource); // 注册清理函数// 可能被取消的代码...pthread_testcancel();pthread_cleanup_pop(1); // 1=执行清理函数, 0=不执行return NULL;
}
9.8 fork() 与多线程的危险组合
重大陷阱: 多线程程序中调用fork()
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int counter = 0;void *thread_func(void *arg) {while (1) {pthread_mutex_lock(&mutex);counter++;pthread_mutex_unlock(&mutex);usleep(1000);}
}int main() {pthread_t tid;pthread_create(&tid, NULL, thread_func, NULL);sleep(1);pid_t pid = fork(); // ⚠️ 危险!if (pid == 0) {// 子进程pthread_mutex_lock(&mutex); // 可能永远死锁!printf("Child: %d\n", counter);pthread_mutex_unlock(&mutex);}
}
问题原因:
- fork()后,子进程只复制调用线程,其他线程消失
- 如果某个锁在fork时被其他线程持有,子进程中该锁永远无法释放
- 子进程继承了父进程的锁状态,但持有锁的线程已不存在
解决方案:
// 方法1: fork前准备,fork后清理
pthread_atfork(prepare, parent, child);void prepare(void) {pthread_mutex_lock(&mutex); // fork前获取所有锁
}void parent(void) {pthread_mutex_unlock(&mutex); // 父进程释放锁
}void child(void) {pthread_mutex_unlock(&mutex); // 子进程释放锁
}// 方法2: 子进程fork后立即exec(推荐)
if (fork() == 0) {execl("/bin/ls", "ls", NULL); // 不使用任何父进程资源exit(1);
}// 方法3: 避免在多线程程序中使用fork
9.9 errno 与线程安全
errno 是线程局部的:
#include <errno.h>void *thread_func(void *arg) {int fd = open("/nonexistent", O_RDONLY);if (fd < 0) {printf("Thread %ld: errno = %d\n", pthread_self(), errno); // 每个线程独立的errno}
}
但并非所有函数都是线程安全的:
// ❌ 非线程安全函数
strtok() // 使用静态缓冲区
localtime() // 返回静态结构
gethostbyname()// ✅ 线程安全替代版本
strtok_r() // reentrant版本
localtime_r()
gethostbyname_r()
识别线程安全函数:
- 查看man手册的"ATTRIBUTES"部分
- 通常
_r后缀表示可重入(reentrant)版本
9.10 内存模型与可见性
问题: 编译器和CPU可能重排序指令
int data = 0;
int flag = 0;// 线程1
void *writer(void *arg) {data = 42; // ⚠️ 可能被重排序flag = 1; // 线程2可能先看到flag=1,但data还是0
}// 线程2
void *reader(void *arg) {while (flag == 0) ; // 自旋等待printf("Data: %d\n", data); // 可能打印0而不是42!
}
解决方案: 使用内存屏障或锁
// 方法1: 使用互斥锁(隐含内存屏障)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;void *writer(void *arg) {pthread_mutex_lock(&mutex);data = 42;flag = 1;pthread_mutex_unlock(&mutex); // 确保可见性
}// 方法2: 使用C11原子操作
#include <stdatomic.h>
atomic_int flag = 0;void *writer(void *arg) {data = 42;atomic_store(&flag, 1); // release语义
}void *reader(void *arg) {while (atomic_load(&flag) == 0) ; // acquire语义printf("Data: %d\n", data);
}// 方法3: volatile + 内存屏障(GCC)
volatile int flag = 0;void *writer(void *arg) {data = 42;__sync_synchronize(); // 内存屏障flag = 1;
}
9.11 线程局部存储 (TLS)
三种TLS实现方式:
// 方法1: __thread关键字(GNU C扩展,最高效)
__thread int thread_id = 0;void *thread_func(void *arg) {thread_id = *(int*)arg; // 每个线程独立printf("Thread %d\n", thread_id);
}// 方法2: pthread_key_t(POSIX标准,可添加析构函数)
pthread_key_t key;void destructor(void *value) {free(value);
}int main() {pthread_key_create(&key, destructor);// 每个线程中int *data = malloc(sizeof(int));pthread_setspecific(key, data);int *retrieved = pthread_getspecific(key);
}// 方法3: C11 thread_local关键字
#include <threads.h>
thread_local int counter = 0;
应用场景:
- 线程特定的错误码
- 每线程的随机数生成器状态
- 数据库连接池中的连接
- 性能计数器
9.12 线程数量的权衡
不是越多越好!
// ❌ 创建过多线程
for (int i = 0; i < 10000; i++) {pthread_create(&tids[i], NULL, task, NULL);
}
// 问题: 上下文切换开销巨大,内存消耗严重
经验法则:
-
CPU密集型任务: 线程数 = CPU核心数 + 1
int ncores = sysconf(_SC_NPROCESSORS_ONLN); int nthreads = ncores + 1; -
IO密集型任务: 可以更多,通常2-4倍核心数
int nthreads = ncores * 2; -
使用线程池: 预创建固定数量的线程,复用执行任务
监控线程数量:
# 查看进程的线程数
ps -eLf | grep process_name | wc -l# 或使用top
top -H -p <pid># 查看线程详情
cat /proc/<pid>/status | grep Threads
9.13 调试工具
Valgrind - Helgrind (检测竞态条件)
valgrind --tool=helgrind ./program
Thread Sanitizer (检测数据竞争)
gcc -fsanitize=thread -g program.c -o program -lpthread
./program
GDB多线程调试
gdb ./program
(gdb) set scheduler-locking on # 只运行当前线程
(gdb) thread apply all bt # 所有线程的调用栈
(gdb) info threads # 线程列表
SystemTap查看线程活动
stap -e 'probe process("/path/to/program").thread.* { println(pp()) }'
十、性能优化技巧
10.1 减少锁竞争
// ❌ 粗粒度锁
pthread_mutex_lock(&big_lock);
process_data_1();
process_data_2();
pthread_mutex_unlock(&big_lock);// ✅ 细粒度锁
pthread_mutex_lock(&lock1);
process_data_1();
pthread_mutex_unlock(&lock1);pthread_mutex_lock(&lock2);
process_data_2();
pthread_mutex_unlock(&lock2);
10.2 读写锁
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;// 读多写少的场景
void *reader(void *arg) {pthread_rwlock_rdlock(&rwlock); // 多个读者可并发// 读取数据pthread_rwlock_unlock(&rwlock);
}void *writer(void *arg) {pthread_rwlock_wrlock(&rwlock); // 独占写入// 修改数据pthread_rwlock_unlock(&rwlock);
}
10.3 自旋锁 vs 互斥锁
// 短期持有 + 低竞争 -> 自旋锁
pthread_spinlock_t spinlock;
pthread_spin_init(&spinlock, 0);
pthread_spin_lock(&spinlock);
// 快速操作
pthread_spin_unlock(&spinlock);// 长期持有 + 高竞争 -> 互斥锁
pthread_mutex_lock(&mutex);
// 耗时操作
pthread_mutex_unlock(&mutex);
十一、总结
核心原则:
✅ 编译必须链接 -lpthread
✅ 理解线程=LWP,pthread_t≠内核TID
✅ 栈大小有限,大数组用堆
✅ errno是线程局部的,但非所有函数线程安全
✅ 信号在多线程中要专门处理
✅ 多线程+fork=灾难,避免或用pthread_atfork
✅ 内存可见性需要同步机制保证
✅ 线程取消不一定立即生效
✅ 线程数量要根据任务类型和CPU核心数权衡
✅ 使用工具检测竞态和死锁
调试检查清单:
- 编译时启用
-Wall -Wextra - 使用ThreadSanitizer检测数据竞争
- 使用Helgrind检测同步错误
- 检查所有共享变量是否有锁保护
- 确认joinable线程都被join或detach
- 验证没有返回栈上变量地址
- 测试长时间运行是否有资源泄漏
- 压力测试多线程并发场景
记住: 并发编程的难点不在于创建线程,而在于正确地协调它们!
