面试题答案
一键面试条件变量与互斥锁配合避免竞争条件的原理
- 互斥锁:互斥锁用于保证在同一时刻只有一个线程或进程能够访问共享资源,防止多个线程或进程同时修改共享数据,从而避免竞争条件。但互斥锁本身只能解决“同时访问”的问题,对于需要根据某些条件来决定是否访问共享资源的情况,单纯的互斥锁无法满足。
- 条件变量:条件变量是线程或进程间的一种同步机制,它允许线程或进程在某个条件满足时被唤醒。条件变量通常和互斥锁配合使用,线程在获取互斥锁后,检查共享资源的状态是否满足特定条件。如果不满足,线程会释放互斥锁并在条件变量上等待。当其他线程改变了共享资源的状态使得条件满足时,会通知条件变量,等待在条件变量上的线程会被唤醒,重新获取互斥锁,然后检查条件是否真的满足并继续执行。这样就确保了线程在合适的时机访问共享资源,避免竞争条件。
举例场景
以生产者 - 消费者模型为例:
- 场景描述:有一个共享缓冲区,生产者进程向缓冲区中写入数据,消费者进程从缓冲区中读取数据。缓冲区有一定的容量限制,当缓冲区满时,生产者需要等待;当缓冲区空时,消费者需要等待。
- 代码示例(以C语言的POSIX线程库为例):
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE];
int in = 0;
int out = 0;
int count = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t not_full = PTHREAD_COND_INITIALIZER;
pthread_cond_t not_empty = PTHREAD_COND_INITIALIZER;
void *producer(void *arg) {
int i;
for (i = 0; i < 10; i++) {
pthread_mutex_lock(&mutex);
while (count == BUFFER_SIZE) {
pthread_cond_wait(¬_full, &mutex);
}
buffer[in] = i;
printf("Produced: %d\n", buffer[in]);
in = (in + 1) % BUFFER_SIZE;
count++;
pthread_cond_signal(¬_empty);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
void *consumer(void *arg) {
int i;
for (i = 0; i < 10; i++) {
pthread_mutex_lock(&mutex);
while (count == 0) {
pthread_cond_wait(¬_empty, &mutex);
}
int data = buffer[out];
printf("Consumed: %d\n", data);
out = (out + 1) % BUFFER_SIZE;
count--;
pthread_cond_signal(¬_full);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main() {
pthread_t producer_thread, consumer_thread;
pthread_create(&producer_thread, NULL, producer, NULL);
pthread_create(&consumer_thread, NULL, consumer, NULL);
pthread_join(producer_thread, NULL);
pthread_join(consumer_thread, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(¬_full);
pthread_cond_destroy(¬_empty);
return 0;
}
在这个例子中,互斥锁 mutex
用于保护共享缓冲区 buffer
以及相关的变量 in
、out
和 count
。当生产者要向缓冲区写入数据时,先获取互斥锁,然后检查缓冲区是否已满(count == BUFFER_SIZE
),如果满了就通过 pthread_cond_wait
释放互斥锁并在 not_full
条件变量上等待。当消费者从缓冲区取出数据后,会通过 pthread_cond_signal
唤醒等待在 not_full
条件变量上的生产者。同理,消费者在读取数据前,先获取互斥锁,检查缓冲区是否为空,为空则在 not_empty
条件变量上等待,生产者写入数据后唤醒消费者。通过这种方式,避免了生产者和消费者对共享缓冲区的竞争条件。