Linux多线程编程完全指南
多线程编程是现代软件开发的核心技能之一。通过多线程,程序可以充分利用多核CPU的并行计算能力,提高系统吞吐量和响应速度。本文将全面解析Linux多线程编程的核心概念、API使用和实战技巧。
一、线程基础概念
1.1 什么是线程
线程是进程内部的执行单元,是CPU调度的基本单位。每个线程包含:
- 独立的执行栈和寄存器状态
- 线程局部存储(Thread Local Storage)
- 调度优先级和策略
同一进程内的多个线程共享:
- 代码段、数据段、堆内存
- 文件描述符、信号处理
- 当前工作目录、用户ID等
1.2 进程与线程的关系
| 特性 | 进程 | 线程 |
|---|---|---|
| 地址空间 | 独立 | 共享 |
| 创建开销 | 大(需复制资源) | 小(共享资源) |
| 切换开销 | 大 | 小 |
| 通信方式 | IPC | 共享内存 |
| 安全性 | 高(相互隔离) | 低(需同步) |
1.3 为什么需要多线程
优势:
- 资源共享:线程共享进程资源,通信高效
- 轻量级:创建和切换开销远小于进程
- 并行计算:充分利用多核CPU
- 响应性:后台任务不阻塞主线程
挑战:
- 线程安全问题
- 竞态条件和死锁
- 调试复杂度高
二、POSIX线程库(pthread)
2.1 线程创建
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg);
参数:
- thread:存储线程ID
- attr:线程属性(NULL表示默认)
- start_routine:线程函数
- arg:传递给线程函数的参数
返回值:成功返回0,失败返回错误码
编译选项:需要链接pthread库:-lpthread
2.2 线程创建示例
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* thread_func(void* arg) {
int* p = (int*)arg;
printf("线程启动,参数: %d\n", *p);
for (int i = 0; i < 5; i++) {
printf("线程运行中... i=%d\n", i);
sleep(1);
}
return NULL;
}
int main() {
pthread_t tid;
int arg = 100;
// 创建线程
if (pthread_create(&tid, NULL, thread_func, &arg) != 0) {
perror("pthread_create failed");
return -1;
}
printf("主线程: 线程ID = %lu\n", tid);
// 等待线程结束
pthread_join(tid, NULL);
printf("线程已结束\n");
return 0;
}
2.3 线程参数传递注意事项
错误示例:
// 错误:传递局部变量地址
void* thread_func(void* arg) {
int* p = (int*)arg;
printf("%d\n", *p); // 可能访问已释放的内存
}
int main() {
pthread_t tid;
for (int i = 0; i < 5; i++) {
pthread_create(&tid, NULL, thread_func, &i); // 危险!
}
}
正确做法1:动态分配
void* thread_func(void* arg) {
int* p = (int*)arg;
printf("%d\n", *p);
free(p); // 线程负责释放
return NULL;
}
int main() {
pthread_t tid;
for (int i = 0; i < 5; i++) {
int* p = malloc(sizeof(int));
*p = i;
pthread_create(&tid, NULL, thread_func, p);
}
}
正确做法2:传递值(利用指针大小)
void* thread_func(void* arg) {
int value = (int)(long)arg; // 64位系统需要long
printf("%d\n", value);
return NULL;
}
int main() {
pthread_t tid;
for (int i = 0; i < 5; i++) {
pthread_create(&tid, NULL, thread_func, (void*)(long)i);
}
}
2.4 线程等待(pthread_join)
int pthread_join(pthread_t thread, void **retval);
功能:等待指定线程结束,并回收资源。
参数:
- thread:要等待的线程ID
- retval:存储线程返回值
特性:
- 阻塞调用,直到目标线程结束
- 只能等待joinable状态的线程
- 同一线程只能被join一次
获取线程返回值:
void* thread_func(void* arg) {
static int result = 42;
return &result; // 或 pthread_exit(&result);
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
void* ret;
pthread_join(tid, &ret);
printf("线程返回值: %d\n", *(int*)ret);
}
2.5 线程退出
方式1:return返回
void* thread_func(void* arg) {
// 正常返回
return NULL;
}
方式2:pthread_exit()
void* thread_func(void* arg) {
pthread_exit(NULL); // 仅退出当前线程
}
方式3:exit() - 不推荐
void* thread_func(void* arg) {
exit(0); // 退出整个进程!所有线程都会终止
}
pthread_exit()详解:
#include <pthread.h>
void pthread_exit(void *retval);
- 仅退出当前线程,不影响其他线程
- retval会被pthread_join获取
- 主线程调用pthread_exit()后,其他线程继续运行
2.6 线程分离(detached状态)
线程有两种终止状态:
- joinable(可结合):默认状态,需要被join回收资源
- detached(分离):自动回收资源,不能被join
设置分离状态的方式:
方式1:创建时设置属性
pthread_t tid;
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);
// 不需要也不可以调用pthread_join
方式2:线程内自我分离
void* thread_func(void* arg) {
pthread_detach(pthread_self());
// 线程结束后自动回收
return NULL;
}
方式3:创建后分离
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
pthread_detach(tid); // 分离线程
2.7 线程ID与比较
pthread_t pthread_self(void); // 获取当前线程ID
int pthread_equal(pthread_t t1, pthread_t t2); // 比较线程ID
示例:
void* thread_func(void* arg) {
printf("我的线程ID: %lu\n", pthread_self());
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
printf("创建的线程ID: %lu\n", tid);
if (pthread_equal(tid, pthread_self())) {
printf("这是主线程\n");
}
}
三、线程安全与数据混乱
3.1 数据混乱的原因
多线程环境下,数据混乱通常由以下原因引起:
- 竞态条件(Race Condition):多个线程同时访问共享数据
- 原子性缺失:操作被中断,部分完成
- 可见性问题:一个线程的修改对其他线程不可见
- 指令重排:编译器或CPU优化导致执行顺序改变
3.2 经典案例:卖票问题
#include <stdio.h>
#include <pthread.h>
int tickets = 1000; // 共享数据
void* sell_ticket(void* arg) {
while (1) {
if (tickets > 0) {
usleep(1000); // 模拟耗时
tickets--;
printf("售出一张票,剩余: %d\n", tickets);
} else {
break;
}
}
return NULL;
}
int main() {
pthread_t t1, t2, t3;
pthread_create(&t1, NULL, sell_ticket, NULL);
pthread_create(&t2, NULL, sell_ticket, NULL);
pthread_create(&t3, NULL, sell_ticket, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
return 0;
}
问题:可能出现负数票或重复售票!
3.3 线程安全概念
线程安全:多线程环境下,函数或代码在并发执行时能正确处理共享变量,保证数据的一致性和正确性。
线程不安全的常见情况:
- 使用全局或静态变量
- 修改共享数据没有同步
- 调用非线程安全函数
保证线程安全的方法:
- 使用同步机制(锁、信号量)
- 使用线程局部存储
- 避免共享状态
四、同步与互斥
4.1 基本概念
互斥(Mutual Exclusion):
- 保证同一时刻只有一个线程访问临界资源
- 解决"能不能访问"的问题
- 关注唯一性和排他性
同步(Synchronization):
- 协调多个线程的执行顺序
- 解决"什么时候访问"的问题
- 关注时序性和协作性
关系:
- 互斥是同步的特例
- 同步包含互斥
- 同步更复杂,需要条件和状态
4.2 临界区与临界资源
临界资源:一次只允许一个线程访问的共享资源
临界区:访问临界资源的代码段
临界区访问原则:
- 空闲让进:无人在临界区时,允许进入
- 忙则等待:有人时,必须等待
- 有限等待:等待时间有限,避免饥饿
- 让权等待:等待时释放CPU
五、互斥锁(Mutex)
5.1 互斥锁原理
互斥锁是一个0/1的计数器,表示临界资源是否可访问:
- 值为1:资源空闲,可以访问
- 值为0:资源被占用,需要等待
加锁操作是原子操作,不可被中断。
5.2 互斥锁API
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 静态初始化
int pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutexattr_t *attr); // 动态初始化
int pthread_mutex_destroy(pthread_mutex_t *mutex); // 销毁
int pthread_mutex_lock(pthread_mutex_t *mutex); // 加锁(阻塞)
int pthread_mutex_trylock(pthread_mutex_t *mutex); // 尝试加锁(非阻塞)
int pthread_mutex_unlock(pthread_mutex_t *mutex); // 解锁
5.3 使用互斥锁解决卖票问题
#include <stdio.h>
#include <pthread.h>
int tickets = 1000;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* sell_ticket(void* arg) {
while (1) {
pthread_mutex_lock(&mutex); // 加锁
if (tickets > 0) {
tickets--;
printf("线程%lu售出一张票,剩余: %d\n", pthread_self(), tickets);
pthread_mutex_unlock(&mutex); // 解锁
usleep(1000);
} else {
pthread_mutex_unlock(&mutex); // 别忘了解锁!
break;
}
}
return NULL;
}
int main() {
pthread_t t1, t2, t3;
pthread_create(&t1, NULL, sell_ticket, NULL);
pthread_create(&t2, NULL, sell_ticket, NULL);
pthread_create(&t3, NULL, sell_ticket, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
5.4 更优雅的写法
void* sell_ticket(void* arg) {
while (1) {
pthread_mutex_lock(&mutex);
int has_ticket = (tickets > 0);
if (has_ticket) {
tickets--;
printf("剩余: %d\n", tickets);
}
pthread_mutex_unlock(&mutex);
if (!has_ticket) break;
usleep(1000);
}
return NULL;
}
六、死锁
6.1 死锁定义
死锁是指两个或多个线程互相等待对方释放资源,导致所有线程都无法继续执行。
6.2 死锁产生条件
- 互斥条件:资源不能共享
- 请求与保持:持有资源同时请求新资源
- 不可剥夺:资源不能被强制抢占
- 循环等待:存在环路等待链
6.3 死锁示例
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutexA = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutexB = PTHREAD_MUTEX_INITIALIZER;
void* thread1(void* arg) {
pthread_mutex_lock(&mutexA);
printf("线程1锁住A\n");
sleep(1);
pthread_mutex_lock(&mutexB); // 等待B,但线程2持有B
printf("线程1锁住B\n");
pthread_mutex_unlock(&mutexB);
pthread_mutex_unlock(&mutexA);
return NULL;
}
void* thread2(void* arg) {
pthread_mutex_lock(&mutexB);
printf("线程2锁住B\n");
sleep(1);
pthread_mutex_lock(&mutexA); // 等待A,但线程1持有A
printf("线程2锁住A\n");
pthread_mutex_unlock(&mutexA);
pthread_mutex_unlock(&mutexB);
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, thread1, NULL);
pthread_create(&t2, NULL, thread2, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
return 0;
}
6.4 死锁预防策略
策略1:固定加锁顺序
// 所有线程都按相同顺序加锁
void* thread1(void* arg) {
pthread_mutex_lock(&mutexA); // 先A
pthread_mutex_lock(&mutexB); // 后B
// ...
}
void* thread2(void* arg) {
pthread_mutex_lock(&mutexA); // 也是先A
pthread_mutex_lock(&mutexB); // 后B
// ...
}
策略2:trylock回退
void* thread1(void* arg) {
pthread_mutex_lock(&mutexA);
if (pthread_mutex_trylock(&mutexB) != 0) {
pthread_mutex_unlock(&mutexA); // 获取失败,释放A
// 重试或其他处理
return NULL;
}
// 成功获取两把锁
pthread_mutex_unlock(&mutexB);
pthread_mutex_unlock(&mutexA);
return NULL;
}
策略3:使用层次锁
// 为每个锁分配层级编号,只能从低到高加锁
#define LOCK_LEVEL_A 1
#define LOCK_LEVEL_B 2
// 线程只能先获取level低的锁
七、读写锁
7.1 读写锁特点
读写锁区分读操作和写操作:
- 读锁(共享锁):多个线程可同时持有
- 写锁(独占锁):同一时刻只能有一个线程持有
适用场景:读多写少的情况
7.2 读写锁API
#include <pthread.h>
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
int pthread_rwlock_init(pthread_rwlock_t *rwlock, NULL);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); // 加读锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); // 加写锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); // 解锁
7.3 读写锁示例
#include <stdio.h>
#include <pthread.h>
int data = 0;
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
void* reader(void* arg) {
for (int i = 0; i < 10; i++) {
pthread_rwlock_rdlock(&rwlock);
printf("读者%lu: data = %d\n", pthread_self(), data);
pthread_rwlock_unlock(&rwlock);
usleep(100000);
}
return NULL;
}
void* writer(void* arg) {
for (int i = 0; i < 5; i++) {
pthread_rwlock_wrlock(&rwlock);
data++;
printf("写者%lu: data = %d\n", pthread_self(), data);
pthread_rwlock_unlock(&rwlock);
usleep(200000);
}
return NULL;
}
int main() {
pthread_t r1, r2, r3, w1, w2;
pthread_create(&r1, NULL, reader, NULL);
pthread_create(&r2, NULL, reader, NULL);
pthread_create(&r3, NULL, reader, NULL);
pthread_create(&w1, NULL, writer, NULL);
pthread_create(&w2, NULL, writer, NULL);
pthread_join(r1, NULL);
pthread_join(r2, NULL);
pthread_join(r3, NULL);
pthread_join(w1, NULL);
pthread_join(w2, NULL);
return 0;
}
八、条件变量
8.1 条件变量作用
条件变量用于线程间的等待/通知机制:
- 一个线程等待某个条件成立
- 另一个线程改变条件并通知等待者
8.2 条件变量API
#include <pthread.h>
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *cond, NULL);
int pthread_cond_destroy(pthread_cond_t *cond);
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); // 唤醒所有等待线程
8.3 生产者-消费者模型
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#define BUFFER_SIZE 10
int buffer[BUFFER_SIZE];
int count = 0;
int in = 0, out = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t not_full = PTHREAD_COND_INITIALIZER;
pthread_cond_t not_empty = PTHREAD_COND_INITIALIZER;
void* producer(void* arg) {
for (int i = 0; i < 20; i++) {
pthread_mutex_lock(&mutex);
while (count == BUFFER_SIZE) {
pthread_cond_wait(¬_full, &mutex);
}
buffer[in] = i;
in = (in + 1) % BUFFER_SIZE;
count++;
printf("生产: %d, 缓冲区: %d\n", i, count);
pthread_cond_signal(¬_empty);
pthread_mutex_unlock(&mutex);
usleep(100000);
}
return NULL;
}
void* consumer(void* arg) {
for (int i = 0; i < 20; i++) {
pthread_mutex_lock(&mutex);
while (count == 0) {
pthread_cond_wait(¬_empty, &mutex);
}
int item = buffer[out];
out = (out + 1) % BUFFER_SIZE;
count--;
printf("消费: %d, 缓冲区: %d\n", item, count);
pthread_cond_signal(¬_full);
pthread_mutex_unlock(&mutex);
usleep(150000);
}
return NULL;
}
int main() {
pthread_t prod, cons;
pthread_create(&prod, NULL, producer, NULL);
pthread_create(&cons, NULL, consumer, NULL);
pthread_join(prod, NULL);
pthread_join(cons, NULL);
return 0;
}
8.4 条件变量使用规范
等待方:
pthread_mutex_lock(&mutex);
while (condition_is_false) { // 使用while而非if!
pthread_cond_wait(&cond, &mutex);
}
// 条件成立,执行操作
pthread_mutex_unlock(&mutex);
通知方:
pthread_mutex_lock(&mutex);
// 改变条件
condition_is_false = false;
pthread_cond_signal(&cond); // 或 pthread_cond_broadcast
pthread_mutex_unlock(&mutex);
为什么用while而不是if:
- 防止虚假唤醒
- 条件可能在唤醒后再次改变
九、信号量
9.1 POSIX信号量
#include <semaphore.h>
sem_t sem;
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);
int sem_wait(sem_t *sem); // P操作,减1
int sem_post(sem_t *sem); // V操作,加1
9.2 信号量实现生产者-消费者
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#define BUFFER_SIZE 10
int buffer[BUFFER_SIZE];
int in = 0, out = 0;
sem_t empty; // 空槽位数
sem_t full; // 数据个数
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* producer(void* arg) {
for (int i = 0; i < 20; i++) {
sem_wait(&empty); // 等待空槽位
pthread_mutex_lock(&mutex);
buffer[in] = i;
in = (in + 1) % BUFFER_SIZE;
printf("生产: %d\n", i);
pthread_mutex_unlock(&mutex);
sem_post(&full); // 增加数据计数
usleep(100000);
}
return NULL;
}
void* consumer(void* arg) {
for (int i = 0; i < 20; i++) {
sem_wait(&full); // 等待数据
pthread_mutex_lock(&mutex);
int item = buffer[out];
out = (out + 1) % BUFFER_SIZE;
printf("消费: %d\n", item);
pthread_mutex_unlock(&mutex);
sem_post(&empty); // 增加空槽位
usleep(150000);
}
return NULL;
}
int main() {
sem_init(&empty, 0, BUFFER_SIZE);
sem_init(&full, 0, 0);
pthread_t prod, cons;
pthread_create(&prod, NULL, producer, NULL);
pthread_create(&cons, NULL, consumer, NULL);
pthread_join(prod, NULL);
pthread_join(cons, NULL);
sem_destroy(&empty);
sem_destroy(&full);
return 0;
}
十、总结
Linux多线程编程的核心要点:
- 线程基础:理解进程与线程的关系,掌握创建、等待、退出操作
- 线程安全:识别竞态条件,使用同步机制保护共享资源
- 互斥锁:最常用的同步机制,保护临界区
- 死锁预防:固定加锁顺序,使用trylock
- 读写锁:适用于读多写少场景
- 条件变量:实现线程间等待/通知
- 信号量:灵活的同步工具,可控制并发数
最佳实践:
- 最小化临界区范围
- 使用RAII管理锁
- 避免嵌套锁
- 合理设置线程数量(CPU核心数 × 2 左右)
- 使用线程池避免频繁创建销毁
1016

被折叠的 条评论
为什么被折叠?



