实战 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,用完同步变量(锁、条件变量、信号量)必须销毁,避免内存泄漏。