面试题答案
一键面试条件变量与互斥锁配合实现线程间同步的原理
- 互斥锁:用于保护共享资源,确保同一时间只有一个线程能够访问共享资源,避免数据竞争。线程在访问共享资源前需要获取互斥锁,访问结束后释放互斥锁。
- 条件变量:本身并不保护共享资源,它主要用于线程间的通信和同步。条件变量允许线程等待某个条件满足后再继续执行。线程通常在获取互斥锁后,检查条件是否满足,如果不满足,则通过条件变量进入等待状态,并同时释放之前获取的互斥锁,以便其他线程可以修改共享资源。当条件满足时,其他线程可以通过条件变量唤醒等待的线程,被唤醒的线程重新获取互斥锁后继续执行。
示例场景:生产者 - 消费者模型
- 场景描述:假设有一个生产者线程和多个消费者线程,生产者线程向一个共享缓冲区中生产数据,消费者线程从共享缓冲区中消费数据。共享缓冲区有一定的容量限制。
- 实现方式:
- 互斥锁:用于保护共享缓冲区,确保在同一时间只有一个线程(生产者或消费者)能够访问缓冲区,防止数据竞争。
- 条件变量:有两个条件变量,一个用于通知消费者缓冲区有新数据(
not_empty
),另一个用于通知生产者缓冲区有空闲空间(not_full
)。 - 生产者线程:
- 首先获取互斥锁。
- 检查缓冲区是否已满,如果已满,则通过
not_full
条件变量等待,同时释放互斥锁。 - 当被唤醒后,重新获取互斥锁,向缓冲区中写入数据。
- 写入完成后,释放互斥锁,并通过
not_empty
条件变量通知消费者线程缓冲区有新数据。
- 消费者线程:
- 首先获取互斥锁。
- 检查缓冲区是否为空,如果为空,则通过
not_empty
条件变量等待,同时释放互斥锁。 - 当被唤醒后,重新获取互斥锁,从缓冲区中读取数据。
- 读取完成后,释放互斥锁,并通过
not_full
条件变量通知生产者线程缓冲区有空闲空间。
以下是一个简化的伪代码示例(以C++ 为例,使用std::mutex
和std::condition_variable
):
#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable not_empty;
std::condition_variable not_full;
std::queue<int> buffer;
const int buffer_size = 5;
void producer(int id) {
for (int i = 0; i < 10; ++i) {
std::unique_lock<std::mutex> lock(mtx);
while (buffer.size() == buffer_size) {
not_full.wait(lock);
}
buffer.push(i);
std::cout << "Producer " << id << " produced " << i << std::endl;
lock.unlock();
not_empty.notify_one();
}
}
void consumer(int id) {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
while (buffer.empty()) {
not_empty.wait(lock);
}
int data = buffer.front();
buffer.pop();
std::cout << "Consumer " << id << " consumed " << data << std::endl;
lock.unlock();
not_full.notify_one();
if (data == 9) break;
}
}
int main() {
std::thread producer_thread1(producer, 1);
std::thread producer_thread2(producer, 2);
std::thread consumer_thread1(consumer, 1);
std::thread consumer_thread2(consumer, 2);
producer_thread1.join();
producer_thread2.join();
consumer_thread1.join();
consumer_thread2.join();
return 0;
}
在上述代码中,生产者和消费者线程通过互斥锁和条件变量实现了同步,确保共享缓冲区的正确使用。