Linux 多线程新手进阶实战清单
实战 1:条件变量(pthread_cond_t)—— 让线程 “等条件满足再执行”
适用场景
比如生产者 - 消费者模型:生产者生产数据后,通知消费者来取;消费者没数据时就等待,不浪费 CPU 资源。
完整代码
c
运行
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 共享数据区
int buffer = 0; // 0表示无数据,1表示有数据
// 互斥锁+条件变量(必须搭配使用)
pthread_mutex_t mutex;
pthread_cond_t cond;
// 生产者线程:生产数据
void* producer(void* arg) {
for (int i = 0; i < 5; i++) {
pthread_mutex_lock(&mutex); // 先加锁
// 生产数据(模拟耗时)
buffer = 1;
printf("生产者:生产了数据,buffer=%d\n", buffer);
// 通知等待的消费者:数据准备好了
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
sleep(1); // 生产间隔
}
return NULL;
}
// 消费者线程:消费数据
void* consumer(void* arg) {
for (int i = 0; i < 5; i++) {
pthread_mutex_lock(&mutex);
// 没数据就等待(会自动释放锁,被唤醒后重新加锁)
while (buffer == 0) {
printf("消费者:没数据,等待中...\n");
pthread_cond_wait(&cond, &mutex);
}
// 消费数据
buffer = 0;
printf("消费者:消费了数据,buffer=%d\n", buffer);
pthread_mutex_unlock(&mutex);
sleep(1); // 消费间隔
}
return NULL;
}
int main() {
pthread_t tid_pro, tid_con;
// 初始化锁和条件变量
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
// 创建线程
pthread_create(&tid_pro, NULL, producer, NULL);
pthread_create(&tid_con, NULL, consumer, NULL);
// 等待线程结束
pthread_join(tid_pro, NULL);
pthread_join(tid_con, NULL);
// 释放资源
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
关键解释
pthread_cond_wait(&cond, &mutex):核心函数,调用时会自动释放互斥锁,让生产者能加锁生产;被唤醒后会重新加锁,保证消费数据时的安全。pthread_cond_signal(&cond):唤醒一个等待该条件变量的线程;如果要唤醒所有,用pthread_cond_broadcast(&cond)。- 必须用
while判断条件:避免 “虚假唤醒”(线程被唤醒后,条件可能又不满足了)。
编译运行
bash
运行
gcc cond_demo.c -o cond_demo -lpthread
./cond_demo
输出效果
plaintext
消费者:没数据,等待中...
生产者:生产了数据,buffer=1
消费者:消费了数据,buffer=0
消费者:没数据,等待中...
生产者:生产了数据,buffer=1
消费者:消费了数据,buffer=0
...(循环5次)
实战 2:信号量(sem_t)—— 控制同时访问资源的线程数
适用场景
比如限制最多 3 个线程同时读取文件,避免文件被频繁读写导致出错;信号量比互斥锁更灵活(互斥锁只能 1 个线程用,信号量可设 N 个)。
完整代码
c
运行
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#define MAX_THREAD 5 // 总线程数
#define MAX_ACCESS 2 // 最多2个线程同时访问资源
sem_t sem; // 信号量
void* access_resource(void* arg) {
int tid = *(int*)arg;
printf("线程%d:申请访问资源\n", tid);
// P操作:信号量-1,若为0则等待
sem_wait(&sem);
// 访问资源(模拟耗时)
printf("线程%d:成功访问资源,开始干活\n", tid);
sleep(2);
printf("线程%d:完成工作,释放资源\n", tid);
// V操作:信号量+1,唤醒等待的线程
sem_post(&sem);
return NULL;
}
int main() {
pthread_t tid[MAX_THREAD];
int tid_num[MAX_THREAD];
// 初始化信号量:参数1=0(线程间共享),参数2=最大同时访问数
sem_init(&sem, 0, MAX_ACCESS);
// 创建5个线程
for (int i = 0; i < MAX_THREAD; i++) {
tid_num[i] = i+1;
pthread_create(&tid[i], NULL, access_resource, &tid_num[i]);
}
// 等待所有线程结束
for (int i = 0; i < MAX_THREAD; i++) {
pthread_join(tid[i], NULL);
}
// 销毁信号量
sem_destroy(&sem);
return 0;
}
关键解释
sem_init(&sem, 0, MAX_ACCESS):初始化信号量,第三个参数是 “初始值”(即最多允许多少线程同时访问)。sem_wait(&sem):申请资源,信号量减 1;如果信号量≤0,线程阻塞。sem_post(&sem):释放资源,信号量加 1;如果有线程等待,会唤醒其中一个。
编译运行
bash
运行
gcc sem_demo.c -o sem_demo -lpthread
./sem_demo
输出效果
plaintext
线程1:申请访问资源
线程1:成功访问资源,开始干活
线程2:申请访问资源
线程2:成功访问资源,开始干活
线程3:申请访问资源
线程4:申请访问资源
线程5:申请访问资源
线程1:完成工作,释放资源
线程3:成功访问资源,开始干活
线程2:完成工作,释放资源
线程4:成功访问资源,开始干活
...(依次释放)
实战 3:简易线程池 —— 复用线程,避免频繁创建销毁
适用场景
服务器开发中,每次收到请求就创建线程会消耗大量资源;线程池提前创建一批线程,任务来临时直接分配,效率翻倍。
完整代码(简易版)
c
运行
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#define THREAD_POOL_SIZE 3 // 线程池大小(3个工作线程)
#define MAX_TASKS 10 // 任务队列最大容量
// 任务结构体
typedef struct {
void (*func)(void*); // 任务函数
void* arg; // 任务参数
} Task;
// 任务队列
Task task_queue[MAX_TASKS];
int queue_front = 0; // 队头
int queue_rear = 0; // 队尾
int task_count = 0; // 任务数
// 同步相关
pthread_mutex_t queue_mutex;
pthread_cond_t queue_cond;
// 工作线程函数:循环取任务执行
void* worker_thread(void* arg) {
while (1) {
pthread_mutex_lock(&queue_mutex);
// 任务队列为空,等待
while (task_count == 0) {
pthread_cond_wait(&queue_cond, &queue_mutex);
}
// 取出队头任务
Task task = task_queue[queue_front];
queue_front = (queue_front + 1) % MAX_TASKS;
task_count--;
pthread_mutex_unlock(&queue_mutex);
// 执行任务
task.func(task.arg);
}
return NULL;
}
// 添加任务到队列
void add_task(void (*func)(void*), void* arg) {
pthread_mutex_lock(&queue_mutex);
// 任务队列满了,简单处理:提示并返回(实际项目可阻塞/扩容)
if (task_count >= MAX_TASKS) {
printf("任务队列满,无法添加新任务!\n");
pthread_mutex_unlock(&queue_mutex);
return;
}
// 添加任务到队尾
task_queue[queue_rear] = (Task){func, arg};
queue_rear = (queue_rear + 1) % MAX_TASKS;
task_count++;
// 通知工作线程:有新任务了
pthread_cond_signal(&queue_cond);
pthread_mutex_unlock(&queue_mutex);
}
// 示例任务:打印任务ID
void task_func(void* arg) {
int task_id = *(int*)arg;
printf("工作线程%ld:执行任务%d\n", pthread_self() % 1000, task_id);
sleep(1); // 模拟任务耗时
free(arg); // 释放参数内存
}
int main() {
pthread_t pool[THREAD_POOL_SIZE];
// 初始化同步变量
pthread_mutex_init(&queue_mutex, NULL);
pthread_cond_init(&queue_cond, NULL);
// 创建线程池(3个工作线程)
for (int i = 0; i < THREAD_POOL_SIZE; i++) {
pthread_create(&pool[i], NULL, worker_thread, NULL);
}
// 添加10个任务到队列
for (int i = 0; i < 10; i++) {
int* task_id = malloc(sizeof(int));
*task_id = i+1;
add_task(task_func, task_id);
printf("主线程:添加任务%d\n", i+1);
}
// 等待所有任务执行完(简易版:休眠5秒,实际项目需加退出机制)
sleep(5);
// 销毁资源(实际项目需先终止工作线程)
pthread_mutex_destroy(&queue_mutex);
pthread_cond_destroy(&queue_cond);
return 0;
}
关键解释
- 线程池核心逻辑:提前创建固定数量的工作线程,线程启动后循环等待任务;主线程只负责添加任务到队列,由条件变量通知工作线程执行。
- 任务队列:用数组实现循环队列,避免内存碎片;
queue_front/queue_rear控制队头队尾。 - 退出机制:简易版用
sleep(5)等待任务执行,实际项目可加 “退出标志”,让工作线程检测到标志后主动退出。
编译运行
bash
运行
gcc thread_pool.c -o thread_pool -lpthread
./thread_pool
输出效果
plaintext
主线程:添加任务1
主线程:添加任务2
主线程:添加任务3
...(添加到任务10)
工作线程123:执行任务1
工作线程456:执行任务2
工作线程789:执行任务3
工作线程123:执行任务4
工作线程456:执行任务5
...(依次执行)
实战清单总结
- 条件变量:解决 “线程等待特定条件” 的问题,必须和互斥锁搭配,核心是
pthread_cond_wait/signal; - 信号量:比互斥锁灵活,可控制 N 个线程同时访问资源,核心是
sem_wait/post; - 线程池:复用线程减少创建销毁开销,核心是 “任务队列 + 工作线程 + 条件变量通知”;
- 所有代码编译都要加
-lpthread,用完同步变量(锁、条件变量、信号量)必须销毁,避免内存泄漏。

浙公网安备 33010602011771号