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