同步方案设计
- 数据结构与锁的选择:
- 使用互斥锁(
std::mutex
在 C++ 中)来保护共享数据结构,确保同一时间只有一个线程可以访问它。
- 使用条件变量(
std::condition_variable
在 C++ 中)来协调读写线程的操作。
- 读线程逻辑:
- 读线程在读取数据前,先获取互斥锁。
- 如果有写线程正在修改数据,读线程通过条件变量等待,直到写操作完成并通知读线程。
- 读取数据后,释放互斥锁。
- 写线程逻辑:
- 写线程在修改数据前,先获取互斥锁。
- 在修改数据前,检查是否有读线程正在读取数据,如果有,则等待读线程全部读完(通过条件变量通知读线程数据已修改)。
- 修改数据后,通过条件变量通知所有等待的读线程。
- 释放互斥锁。
- 避免死锁:
- 所有线程获取锁的顺序必须一致,避免出现循环等待的情况。
- 对于条件变量的等待,要在持有互斥锁的情况下调用
wait
函数,wait
函数会自动释放互斥锁并等待,当被唤醒时会重新获取互斥锁。
关键代码示例(以 C++ 为例)
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>
std::mutex mtx;
std::condition_variable cv;
bool data_ready = false;
int shared_data;
void read_thread() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return data_ready; });
std::cout << "Read data: " << shared_data << std::endl;
data_ready = false;
}
void write_thread(int value) {
std::unique_lock<std::mutex> lock(mtx);
shared_data = value;
data_ready = true;
cv.notify_all();
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 3; ++i) {
threads.emplace_back(read_thread);
}
for (int i = 0; i < 3; ++i) {
threads.emplace_back(write_thread, i * 10);
}
for (auto& th : threads) {
th.join();
}
return 0;
}
性能优化点解释
- 减少锁的持有时间:
- 读线程在读取数据后尽快释放互斥锁,写线程在修改数据并通知后尽快释放互斥锁,这样可以减少其他线程等待锁的时间,提高并发性能。
- 条件变量的使用:
- 条件变量使得读线程在数据未准备好时进入等待状态,避免了无效的忙等待,从而降低 CPU 占用率。同时,通过条件变量的通知机制,能够及时唤醒等待的线程,提高线程间的协作效率。
- 批量操作:
- 如果可能,尽量对共享数据进行批量读写操作,减少锁的获取和释放次数,进一步提高性能。例如,在写线程中可以一次性更新多个相关数据,然后统一通知读线程。