在多线程编程中,确保数据的一致性和完整性是非常重要的。为了实现这一点,通常需要使用锁来控制对共享资源的访问。读写锁是一种允许多个线程同时读取共享资源,但只允许一个线程写入的锁。本文将介绍如何在C++中实现线程安全的读写锁,包括自定义的RWMutex类和C++17中的std::shared_mutex。
RWMutex是一种读写锁的实现,它允许多个线程同时读取共享资源,但只允许一个线程写入。这种锁的实现基于以下原理:
RWMutex的实现包括以下关键组件:
以下是一个简单的RWMutex实现示例:
class RWMutex {
private:
std::mutex mutex;
int readCount;
std::condition_variable cond;
public:
void lockRead() {
std::unique_lock lock(mutex);
cond.wait(lock, [this] { return writeCount == 0; });
readCount++;
}
void unlockRead() {
std::unique_lock lock(mutex);
readCount--;
if (readCount == 0) {
cond.notify_all();
}
}
void lockWrite() {
std::unique_lock lock(mutex);
cond.wait(lock, [this] { return readCount == 0 && writeCount == 0; });
writeCount++;
}
void unlockWrite() {
std::unique_lock lock(mutex);
writeCount--;
cond.notify_all();
}
};
从C++17开始,标准库提供了std::shared_mutex,它提供了一种更简洁和高效的方式来实现读写锁。std::shared_mutex允许多个线程同时读取共享资源,但只允许一个线程写入。
std::shared_mutex的使用非常简单,以下是一个示例:
std::shared_mutex mutex;
void readerFunction() {
std::shared_lock lock(mutex);
// 读取共享资源
}
void writerFunction() {
std::unique_lock lock(mutex);
// 写入共享资源
}
tlock类是一个模板类,它封装了RWMutex或std::shared_mutex,提供了一种更高级的接口来实现线程安全的读写锁。tlock类的主要特点包括:
以下是一个tlock类的实现示例:
template<typename T>
class tlock {
private:
mutable T t;
mutable RWMutex m;
public:
tlock(Args... args) : t(args...) {}
proxy r() const {
return proxy(&t, &m, 1);
}
proxy w() {
return proxy(&t, &m, 2);
}
void readlock(std::function<void(const T&)> f) const {
proxy mx(&t, &m, 1);
f(*mx.getp());
}
void writelock(std::function<void(T&)> f) {
proxy mx(&t, &m, 2);
f(*mx.getp());
}
proxy operator->() {
return w();
}
const proxy operator->() const {
return r();
}
};
在没有使用tlock的情况下,直接在多线程环境中访问共享资源可能会导致数据不一致。以下是一个错误用法示例:
std::vector<int> s;
std::thread t1([&]() { s.push_back(0); });
std::thread t2([&]() { s.push_back(1); });
std::thread t3([&]() { s.push_back(2); });
std::thread t4([&]() { s.push_back(3); });
std::thread t5([&]() { s.push_back(4); });
t1.join(); t2.join(); t3.join(); t4.join(); t5.join();
在这个示例中,多个线程同时向s中添加元素,可能会导致数据不一致。
使用tlock类可以确保在多线程环境中安全地访问共享资源。以下是一个正确用法示例:
tlock<std::vector<int>> s;
std::thread t1([&]() { s->push_back(0); });
std::thread t2([&]() { s->push_back(1); });
std::thread t3([&]() { s->push_back(2); });
std::thread t4([&]() { s->push_back(3); });
std::thread t5([&]() { s->push_back(4); });
t1.join(); t2.join(); t3.join(); t4.join(); t5.join();