互斥锁和条件变量的应用
互斥锁和条件变量的应用

条件变量和互斥锁是多线程编程中的常用同步工具,通常一起使用来解决线程之间的通信和同步问题。它们主要用于以下几种应用场景:

1. 生产者-消费者问题

在生产者-消费者问题中,多个生产者线程生成数据,多个消费者线程处理数据。生产者和消费者之间需要同步,以避免以下情况:

  • 消费者尝试从空缓冲区读取数据。
  • 生产者尝试向满缓冲区写入数据。

使用条件变量和互斥锁可以有效地解决这些问题。

示例

假设有一个共享的缓冲区,生产者将数据写入缓冲区,而消费者从缓冲区中读取数据。

 1#include <pthread.h>
 2#include <stdio.h>
 3#include <stdlib.h>
 4
 5#define BUFFER_SIZE 10
 6
 7int buffer[BUFFER_SIZE];
 8int count = 0;  // 当前缓冲区中的数据数量
 9
10pthread_mutex_t mutex;
11pthread_cond_t cond_producer;
12pthread_cond_t cond_consumer;
13
14void *producer(void *arg) {
15    for (int i = 0; i < 20; i++) {  // 模拟生产20个数据
16        pthread_mutex_lock(&mutex);
17        while (count == BUFFER_SIZE) {
18            // 缓冲区已满,生产者等待
19            pthread_cond_wait(&cond_producer, &mutex);
20        }
21
22        // 将数据放入缓冲区
23        buffer[count++] = i;
24        printf("Produced: %d\n", i);
25
26        // 通知消费者可以消费
27        pthread_cond_signal(&cond_consumer);
28        pthread_mutex_unlock(&mutex);
29    }
30    return NULL;
31}
32
33void *consumer(void *arg) {
34    for (int i = 0; i < 20; i++) {  // 模拟消费20个数据
35        pthread_mutex_lock(&mutex);
36        while (count == 0) {
37            // 缓冲区为空,消费者等待
38            pthread_cond_wait(&cond_consumer, &mutex);
39        }
40
41        // 从缓冲区取出数据
42        int data = buffer[--count];
43        printf("Consumed: %d\n", data);
44
45        // 通知生产者可以生产
46        pthread_cond_signal(&cond_producer);
47        pthread_mutex_unlock(&mutex);
48    }
49    return NULL;
50}
51
52int main() {
53    pthread_t producer_thread, consumer_thread;
54
55    // 初始化互斥锁和条件变量
56    pthread_mutex_init(&mutex, NULL);
57    pthread_cond_init(&cond_producer, NULL);
58    pthread_cond_init(&cond_consumer, NULL);
59
60    // 创建生产者和消费者线程
61    pthread_create(&producer_thread, NULL, producer, NULL);
62    pthread_create(&consumer_thread, NULL, consumer, NULL);
63
64    // 等待线程结束
65    pthread_join(producer_thread, NULL);
66    pthread_join(consumer_thread, NULL);
67
68    // 销毁互斥锁和条件变量
69    pthread_mutex_destroy(&mutex);
70    pthread_cond_destroy(&cond_producer);
71    pthread_cond_destroy(&cond_consumer);
72
73    return 0;
74}

解释

  • 互斥锁 (pthread_mutex_t): 用于保护对共享资源(如缓冲区和计数器 count)的访问,以避免竞争条件。
  • 条件变量 (pthread_cond_t): 用于在缓冲区状态发生变化(例如满或空)时通知相应的线程。
  • 生产者线程在缓冲区已满时使用 pthread_cond_wait 进行等待,当缓冲区有空间时被消费者线程通过 pthread_cond_signal 唤醒。
  • 消费者线程在缓冲区为空时使用 pthread_cond_wait 进行等待,当有新的数据被生产者生产时被唤醒。

2. 多线程任务队列

在某些应用中,需要将任务放入一个队列,然后由多个线程并发地从队列中取出任务并执行。互斥锁和条件变量也非常适合这种场景。

示例

以下是一个简单的多线程任务队列实现,每个线程从队列中取出任务并执行:

 1#include <pthread.h>
 2#include <stdio.h>
 3#include <stdlib.h>
 4
 5#define MAX_TASKS 10
 6
 7typedef struct {
 8    void (*function)(void* arg);  // 任务函数
 9    void* arg;                    // 任务参数
10} task_t;
11
12task_t task_queue[MAX_TASKS];
13int task_count = 0;  // 当前任务数量
14
15pthread_mutex_t mutex;
16pthread_cond_t cond_task;
17
18void execute_task(task_t task) {
19    task.function(task.arg);
20}
21
22void *worker(void *arg) {
23    while (1) {
24        pthread_mutex_lock(&mutex);
25        while (task_count == 0) {
26            // 如果没有任务,等待
27            pthread_cond_wait(&cond_task, &mutex);
28        }
29
30        // 从队列中取出任务
31        task_t task = task_queue[--task_count];
32        pthread_mutex_unlock(&mutex);
33
34        // 执行任务
35        execute_task(task);
36    }
37    return NULL;
38}
39
40void add_task(void (*function)(void*), void* arg) {
41    pthread_mutex_lock(&mutex);
42    if (task_count < MAX_TASKS) {
43        // 添加任务到队列
44        task_queue[task_count].function = function;
45        task_queue[task_count++].arg = arg;
46        pthread_cond_signal(&cond_task);  // 通知工作线程
47    }
48    pthread_mutex_unlock(&mutex);
49}
50
51// 示例任务
52void sample_task(void* arg) {
53    printf("Executing task with argument: %d\n", *(int*)arg);
54}
55
56int main() {
57    pthread_t threads[4];
58
59    // 初始化互斥锁和条件变量
60    pthread_mutex_init(&mutex, NULL);
61    pthread_cond_init(&cond_task, NULL);
62
63    // 创建工作线程
64    for (int i = 0; i < 4; i++) {
65        pthread_create(&threads[i], NULL, worker, NULL);
66    }
67
68    // 添加任务
69    for (int i = 0; i < 20; i++) {
70        int* arg = malloc(sizeof(int));
71        *arg = i;
72        add_task(sample_task, arg);
73    }
74
75    // 等待线程结束(在这个示例中不会真正结束)
76    for (int i = 0; i < 4; i++) {
77        pthread_join(threads[i], NULL);
78    }
79
80    // 销毁互斥锁和条件变量
81    pthread_mutex_destroy(&mutex);
82    pthread_cond_destroy(&cond_task);
83
84    return 0;
85}

解释

  • 任务队列: 一个全局数组 task_queue 保存任务,使用 task_count 记录当前任务数量。
  • 互斥锁: 用于保护对任务队列的访问。
  • 条件变量: 用于在任务队列为空时阻塞工作线程,并在有新任务添加时通知线程。
  • 工作线程: 调用 pthread_cond_wait 等待新任务的添加,一旦有任务被添加,就被 pthread_cond_signal 唤醒并执行任务。

3. 事件通知机制

有时候,一个线程需要等待其他线程完成某个任务或某个事件发生,这种场景下,条件变量和互斥锁非常有用。例如,主线程等待所有工作线程完成任务后才继续执行。

示例

以下代码演示如何使用条件变量来等待工作线程完成任务:

 1#include <pthread.h>
 2#include <stdio.h>
 3
 4#define NUM_THREADS 3
 5
 6pthread_mutex_t mutex;
 7pthread_cond_t cond;
 8int tasks_done = 0;  // 完成任务的计数
 9
10void *worker(void *arg) {
11    int id = *(int *)arg;
12    printf("Worker %d: Task started\n", id);
13    
14    // 模拟任务执行
15    sleep(2);
16    
17    printf("Worker %d: Task finished\n", id);
18    
19    pthread_mutex_lock(&mutex);
20    tasks_done++;
21    pthread_cond_signal(&cond);  // 通知主线程
22    pthread_mutex_unlock(&mutex);
23
24    return NULL;
25}
26
27int main() {
28    pthread_t threads[NUM_THREADS];
29    int thread_ids[NUM_THREADS];
30
31    // 初始化互斥锁和条件变量
32    pthread_mutex_init(&mutex, NULL);
33    pthread_cond_init(&cond, NULL);
34
35    // 创建工作线程
36    for (int i = 0; i < NUM_THREADS; i++) {
37        thread_ids[i] = i;
38        pthread_create(&threads[i], NULL, worker, &thread_ids[i]);
39    }
40
41    // 主线程等待所有工作线程完成
42    pthread_mutex_lock(&mutex);
43    while (tasks_done < NUM_THREADS) {
44        pthread_cond_wait(&cond, &mutex);
45    }
46    pthread_mutex_unlock(&mutex);
47
48    printf("Main thread: All tasks completed\n");
49
50    // 等待所有线程完成
51    for (int i = 0; i < NUM_THREADS; i++) {
52        pthread_join(threads[i], NULL);
53    }
54
55    // 销毁互斥锁和条件变量
56    pthread_mutex_destroy(&mutex);
57    pthread_cond_destroy(&cond);
58
59    return 0;
60}

解释

  • 互斥锁: 保护对共享变量 tasks_done 的访问。

  • 条件变量: 用于通知主线程有新的任务完成。

  • 工作线程: 完成任务后,更新共享变量并发出信号 pthread_cond_signal

  • 主线程: 使用 pthread_cond_wait 等待所有工作线程完成任务。

总结

条件变量和互斥锁常常结合使用来协调线程之间的操作。互斥锁用于保护共享数据的访问,防止数据竞争,而条件变量则用于在线程之间传递信号,指示何时可以执行特定的操作。通过正确地使用它们,可以确保多线程程序的安全和高效运行。


最后修改于 2024-09-11 18:35