我需要使用读取器/写入器锁来保护一组数据结构。我知道boost :: shared_lock,但是我想使用std :: mutex,std :: condition_variable和/或std :: atomic进行自定义实现,以便我能更好地理解它是如何工作的(稍后再调整) 。
每个数据结构(可移动但不可复制)将从名为Commons的类继承,该类封装了锁定。我希望公共界面看起来像这样:
class Commons {
public:
void read_lock();
bool try_read_lock();
void read_unlock();
void write_lock();
bool try_write_lock();
void write_unlock();
};
...以便某些人可以公开继承:
class DataStructure : public Commons {};
我正在编写科学代码,通常可以避免数据争用;这种锁定主要是为了防止我以后可能犯的错误。因此,我的优先级是低读取开销,所以我不会妨碍正确运行的程序太多。每个线程可能都在自己的CPU核心上运行。
你能告诉我(伪代码是好的)读者/作家锁吗?我现在所拥有的应该是防止作家饥饿的变种。到目前为止,我的主要问题是read_lock在检查读取是否可以安全地实际递增读取器计数之间的差距,之后write_lock知道等待。
void Commons::write_lock() {
write_mutex.lock();
reading_mode.store(false);
while(readers.load() > 0) {}
}
void Commons::try_read_lock() {
if(reading_mode.load()) {
//if another thread calls write_lock here, bad things can happen
++readers;
return true;
} else return false;
}
我对多线程有点新意,我真的很想理解它。在此先感谢您的帮助!
答案 0 :(得分:45)
这是使用互斥锁和条件变量的ver简单读取器/写入器锁的伪代码。互斥API应该是不言自明的。假设条件变量具有成员wait(Mutex&)
,其(原子地!)丢弃互斥锁并等待发出信号的条件。条件通过signal()
唤醒一个服务员,或signal_all()
唤醒所有服务员。
read_lock() {
mutex.lock();
while (writer)
unlocked.wait(mutex);
readers++;
mutex.unlock();
}
read_unlock() {
mutex.lock();
readers--;
if (readers == 0)
unlocked.signal_all();
mutex.unlock();
}
write_lock() {
mutex.lock();
while (writer || (readers > 0))
unlocked.wait(mutex);
writer = true;
mutex.unlock();
}
write_unlock() {
mutex.lock();
writer = false;
unlocked.signal_all();
mutex.unlock();
}
但是,这种实现有很多缺点。
如果大多数服务员都在等待写锁定,那就太浪费了 - 毕竟大多数服务员都无法获得锁定,并且继续等待。简单地使用signal()
不起作用,因为你做想要唤醒所有等待读锁解锁的人。所以要解决这个问题,你需要单独的条件变量来实现可读性和可写性。
你可以通过跟踪挂起的读写锁定数来解决这个问题,并且一旦有挂起的写锁定就停止获取读锁定(虽然你会让读者挨饿!),或者随机唤醒所有读者或者一个读者writer(假设您使用单独的条件变量,请参阅上面的部分)。
为了保证这一点,您需要一个真正的等待队列。你可以,例如为每个服务员创建一个条件变量,并在释放锁定后向队列头部的所有读者或单个写入者发出信号。
这个很难解决。一种方法是使用原子指令来获取读或写锁(通常是比较和交换)。如果由于锁定而导致采集失败,则必须回退到互斥锁。但是,正确地做到这一点非常困难。此外,仍然存在争用 - 原子指令远非免费,特别是在具有大量内核的机器上。
正确实现同步原语 hard 。实现高效且公平的同步原语甚至 更难。它几乎没有回报。 Linux上的pthreads,例如包含一个读写器锁,它使用futexes和原子指令的组合,因此可能比你在几天工作中想出的任何东西都要好。
答案 1 :(得分:5)
//
// Multi-reader Single-writer concurrency base class for Win32
//
// (c) 1999-2003 by Glenn Slayden (glenn@glennslayden.com)
//
//
#include "windows.h"
class MultiReaderSingleWriter
{
private:
CRITICAL_SECTION m_csWrite;
CRITICAL_SECTION m_csReaderCount;
long m_cReaders;
HANDLE m_hevReadersCleared;
public:
MultiReaderSingleWriter()
{
m_cReaders = 0;
InitializeCriticalSection(&m_csWrite);
InitializeCriticalSection(&m_csReaderCount);
m_hevReadersCleared = CreateEvent(NULL,TRUE,TRUE,NULL);
}
~MultiReaderSingleWriter()
{
WaitForSingleObject(m_hevReadersCleared,INFINITE);
CloseHandle(m_hevReadersCleared);
DeleteCriticalSection(&m_csWrite);
DeleteCriticalSection(&m_csReaderCount);
}
void EnterReader(void)
{
EnterCriticalSection(&m_csWrite);
EnterCriticalSection(&m_csReaderCount);
if (++m_cReaders == 1)
ResetEvent(m_hevReadersCleared);
LeaveCriticalSection(&m_csReaderCount);
LeaveCriticalSection(&m_csWrite);
}
void LeaveReader(void)
{
EnterCriticalSection(&m_csReaderCount);
if (--m_cReaders == 0)
SetEvent(m_hevReadersCleared);
LeaveCriticalSection(&m_csReaderCount);
}
void EnterWriter(void)
{
EnterCriticalSection(&m_csWrite);
WaitForSingleObject(m_hevReadersCleared,INFINITE);
}
void LeaveWriter(void)
{
LeaveCriticalSection(&m_csWrite);
}
};
我没有机会尝试,但代码看起来还不错。
答案 2 :(得分:0)
您可以按照here(我写的)中的确切Wikipedia算法来实现Readers-Writers锁:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
int g_sharedData = 0;
int g_readersWaiting = 0;
std::mutex mu;
bool g_writerWaiting = false;
std::condition_variable cond;
void reader(int i)
{
std::unique_lock<std::mutex> lg{mu};
while(g_writerWaiting)
cond.wait(lg);
++g_readersWaiting;
// reading
std::cout << "\n reader #" << i << " is reading data = " << g_sharedData << '\n';
// end reading
--g_readersWaiting;
while(g_readersWaiting > 0)
cond.wait(lg);
cond.notify_one();
}
void writer(int i)
{
std::unique_lock<std::mutex> lg{mu};
while(g_writerWaiting)
cond.wait(lg);
// writing
std::cout << "\n writer #" << i << " is writing\n";
g_sharedData += i * 10;
// end writing
g_writerWaiting = true;
while(g_readersWaiting > 0)
cond.wait(lg);
g_writerWaiting = false;
cond.notify_all();
}//lg.unlock()
int main()
{
std::thread reader1{reader, 1};
std::thread reader2{reader, 2};
std::thread reader3{reader, 3};
std::thread reader4{reader, 4};
std::thread writer1{writer, 1};
std::thread writer2{writer, 2};
std::thread writer3{writer, 3};
std::thread writer4{reader, 4};
reader1.join();
reader2.join();
reader3.join();
reader4.join();
writer1.join();
writer2.join();
writer3.join();
writer4.join();
return(0);
}