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;
}
 

关键解释

  1. pthread_cond_wait(&cond, &mutex):核心函数,调用时会自动释放互斥锁,让生产者能加锁生产;被唤醒后会重新加锁,保证消费数据时的安全。
  2. pthread_cond_signal(&cond):唤醒一个等待该条件变量的线程;如果要唤醒所有,用pthread_cond_broadcast(&cond)
  3. 必须用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;
}
 

关键解释

  1. sem_init(&sem, 0, MAX_ACCESS):初始化信号量,第三个参数是 “初始值”(即最多允许多少线程同时访问)。
  2. sem_wait(&sem):申请资源,信号量减 1;如果信号量≤0,线程阻塞。
  3. 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;
}
 

关键解释

  1. 线程池核心逻辑:提前创建固定数量的工作线程,线程启动后循环等待任务;主线程只负责添加任务到队列,由条件变量通知工作线程执行。
  2. 任务队列:用数组实现循环队列,避免内存碎片;queue_front/queue_rear控制队头队尾。
  3. 退出机制:简易版用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
...(依次执行)
 

实战清单总结

  1. 条件变量:解决 “线程等待特定条件” 的问题,必须和互斥锁搭配,核心是pthread_cond_wait/signal
  2. 信号量:比互斥锁灵活,可控制 N 个线程同时访问资源,核心是sem_wait/post
  3. 线程池:复用线程减少创建销毁开销,核心是 “任务队列 + 工作线程 + 条件变量通知”;
  4. 所有代码编译都要加 -lpthread,用完同步变量(锁、条件变量、信号量)必须销毁,避免内存泄漏。
posted @ 2025-12-26 17:01  Python也不过如此  阅读(1)  评论(0)    收藏  举报