C++ 如何从更基本的同步原语制作多读/单写锁?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/27860685/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to make a multiple-read/single-write lock from more basic synchronization primitives?
提问by sbi
We have found that we have several spots in our code where concurrent reads of data protected by a mutex are rather common, while writes are rare. Our measurements seem to say that using a simple mutex seriously hinders the performance of the code reading that data. So what we would need is a multiple-read/single-write mutex. I know that this can be built atop of simpler primitives, but before I try myself at this, I'd rather ask for existing knowledge:
我们发现在我们的代码中有几个地方,并发读取受互斥锁保护的数据很常见,而写入很少见。我们的测量似乎表明,使用简单的互斥锁严重阻碍了代码读取数据的性能。所以我们需要的是多读/单写互斥锁。我知道这可以建立在更简单的原语之上,但在我尝试自己之前,我宁愿询问现有知识:
What is an approved way to build a multiple-read/single-write lock out of simpler synchronization primitives?
从更简单的同步原语中构建多读/单写锁的认可方法是什么?
I do have an idea how to make it, but I'd rather have answers unbiased by what I (probably wrongly) came up with. (Note: What I expect is an explanation how to do it, probably in pseudo code, not a full-fledged implementation. I can certainly write the code myself.)
我确实有一个想法,但我宁愿得到不带偏见的答案(可能是错误的)。(注意:我期望的是如何做到这一点的解释,可能是伪代码,而不是成熟的实现。我当然可以自己编写代码。)
Caveats:
注意事项:
This needs to have reasonable performance. (What I have in mind would require two lock/unlock operations per access. Now that might not be good enough, but needing many of them instead seems unreasonable.)
Commonly, reads are more numerous, but writes are more important and performance-sensitive than reads. Readers must not starve writers.
We are stuck on a rather old embedded platform (proprietary variant of VxWorks 5.5), with a rather old compiler (GCC 4.1.2), and boost 1.52 – except for most of boost's parts relying on POSIX, because POSIX isn't fully implemented on that platform. The locking primitives available basically are several kind of semaphores (binary, counting etc.), on top of which we have already created mutexes, conditions variables, and monitors.
This is IA32, single-core.
这需要有合理的性能。(我的想法是每次访问需要两次锁定/解锁操作。现在这可能还不够好,但需要很多操作似乎不合理。)
通常,读取数量更多,但写入比读取更重要且对性能更敏感。读者不能饿死作家。
我们被困在一个相当旧的嵌入式平台(VxWorks 5.5 的专有变体),一个相当旧的编译器(GCC 4.1.2)和 boost 1.52——除了依赖 POSIX 的大部分 boost 部分,因为 POSIX 没有完全实现在那个平台上。可用的锁定原语基本上是几种信号量(二进制、计数等),在它们之上我们已经创建了互斥锁、条件变量和监视器。
这是 IA32,单核。
采纳答案by qqibrow
It seems like you only have mutex and condition_variable as synchronization primitives. therefore, I write a reader-writer lock here, which starves readers. it uses one mutex, two conditional_variable and three integer.
似乎您只有 mutex 和 condition_variable 作为同步原语。因此,我在这里写了一个读写锁,让读者挨饿。它使用一个互斥锁、两个conditional_variable 和三个整数。
readers - readers in the cv readerQ plus the reading reader
writers - writers in cv writerQ plus the writing writer
active_writers - the writer currently writing. can only be 1 or 0.
It starve readers in this way. If there are several writers want to write, readers will never get the chance to read until all writers finish writing. This is because later readers need to check writers
variable. At the same time, the active_writers
variable will guarantee that only one writer could write at a time.
它以这种方式饿死读者。如果有几个作者想写,那么在所有作者都写完之前,读者将永远没有机会阅读。这是因为后面的读者需要检查writers
变量。同时,该active_writers
变量将保证一次只有一个作者可以写入。
class RWLock {
public:
RWLock()
: shared()
, readerQ(), writerQ()
, active_readers(0), waiting_writers(0), active_writers(0)
{}
void ReadLock() {
std::unique_lock<std::mutex> lk(shared);
while( waiting_writers != 0 )
readerQ.wait(lk);
++active_readers;
lk.unlock();
}
void ReadUnlock() {
std::unique_lock<std::mutex> lk(shared);
--active_readers;
lk.unlock();
writerQ.notify_one();
}
void WriteLock() {
std::unique_lock<std::mutex> lk(shared);
++waiting_writers;
while( active_readers != 0 || active_writers != 0 )
writerQ.wait(lk);
++active_writers;
lk.unlock();
}
void WriteUnlock() {
std::unique_lock<std::mutex> lk(shared);
--waiting_writers;
--active_writers;
if(waiting_writers > 0)
writerQ.notify_one();
else
readerQ.notify_all();
lk.unlock();
}
private:
std::mutex shared;
std::condition_variable readerQ;
std::condition_variable writerQ;
int active_readers;
int waiting_writers;
int active_writers;
};
回答by Howard Hinnant
At first glance I thought I recognized this answeras the same algorithm that Alexander Terekhov introduced. But after studying it I believe that it is flawed. It is possible for two writers to simultaneously wait on m_exclusive_cond
. When one of those writers wakes and obtains the exclusive lock, it will set exclusive_waiting_blocked = false
on unlock
, thus setting the mutex into an inconsistent state. After that, the mutex is likely hosed.
乍一看,我以为我认出这个答案与 Alexander Terekhov 介绍的算法相同。但经过研究后,我认为它是有缺陷的。两个写入器可以同时等待m_exclusive_cond
。当其中一个编写者唤醒并获得排他锁时,它将设置exclusive_waiting_blocked = false
on unlock
,从而将互斥锁设置为不一致状态。在那之后,互斥体很可能被冲洗掉了。
N2406, which first proposed std::shared_mutex
contains a partial implementation, which is repeated below with updated syntax.
最初提出的N2406std::shared_mutex
包含部分实现,下面将使用更新的语法重复该实现。
class shared_mutex
{
mutex mut_;
condition_variable gate1_;
condition_variable gate2_;
unsigned state_;
static const unsigned write_entered_ = 1U << (sizeof(unsigned)*CHAR_BIT - 1);
static const unsigned n_readers_ = ~write_entered_;
public:
shared_mutex() : state_(0) {}
// Exclusive ownership
void lock();
bool try_lock();
void unlock();
// Shared ownership
void lock_shared();
bool try_lock_shared();
void unlock_shared();
};
// Exclusive ownership
void
shared_mutex::lock()
{
unique_lock<mutex> lk(mut_);
while (state_ & write_entered_)
gate1_.wait(lk);
state_ |= write_entered_;
while (state_ & n_readers_)
gate2_.wait(lk);
}
bool
shared_mutex::try_lock()
{
unique_lock<mutex> lk(mut_, try_to_lock);
if (lk.owns_lock() && state_ == 0)
{
state_ = write_entered_;
return true;
}
return false;
}
void
shared_mutex::unlock()
{
{
lock_guard<mutex> _(mut_);
state_ = 0;
}
gate1_.notify_all();
}
// Shared ownership
void
shared_mutex::lock_shared()
{
unique_lock<mutex> lk(mut_);
while ((state_ & write_entered_) || (state_ & n_readers_) == n_readers_)
gate1_.wait(lk);
unsigned num_readers = (state_ & n_readers_) + 1;
state_ &= ~n_readers_;
state_ |= num_readers;
}
bool
shared_mutex::try_lock_shared()
{
unique_lock<mutex> lk(mut_, try_to_lock);
unsigned num_readers = state_ & n_readers_;
if (lk.owns_lock() && !(state_ & write_entered_) && num_readers != n_readers_)
{
++num_readers;
state_ &= ~n_readers_;
state_ |= num_readers;
return true;
}
return false;
}
void
shared_mutex::unlock_shared()
{
lock_guard<mutex> _(mut_);
unsigned num_readers = (state_ & n_readers_) - 1;
state_ &= ~n_readers_;
state_ |= num_readers;
if (state_ & write_entered_)
{
if (num_readers == 0)
gate2_.notify_one();
}
else
{
if (num_readers == n_readers_ - 1)
gate1_.notify_one();
}
}
The algorithm is derived from an old newsgroup posting of Alexander Terekhov. It starves neither readers nor writers.
该算法源自 Alexander Terekhov 的旧新闻组帖子。它既不会饿死读者,也不会饿死作家。
There are two "gates", gate1_
and gate2_
. Readers and writers have to pass gate1_
, and can get blocked in trying to do so. Once a reader gets past gate1_
, it has read-locked the mutex. Readers can get past gate1_
as long as there are not a maximum number of readers with ownership, and as long as a writer has not gotten past gate1_
.
有两个“门”,gate1_
和gate2_
。读者和作者必须通过gate1_
,并且在尝试这样做时可能会被阻止。一旦读者通过gate1_
,它就已经读锁定了互斥锁。读者可以拿过去gate1_
,只要不存在具有所有权的读者的最大数量,并且只要一个作家没有得到过去gate1_
。
Only one writer at a time can get past gate1_
. And a writer can get past gate1_
even if readers have ownership. But once past gate1_
, a writer still does not have ownership. It must first get past gate2_
. A writer can not get past gate2_
until all readers with ownership have relinquished it. Recall that new readers can't get past gate1_
while a writer is waiting at gate2_
. And neither can a new writer get past gate1_
while a writer is waiting at gate2_
.
一次只能通过一个作家gate1_
。gate1_
即使读者拥有所有权,作家也可以通过。但是一旦过去gate1_
,作家仍然没有所有权。它必须先过去gate2_
。gate2_
直到所有拥有所有权的读者都放弃了作者,作者才能过去。回想一下,gate1_
当作家在等待时,新读者无法通过gate2_
。gate1_
当作家在等待时,新作家也不能过去gate2_
。
The characteristic that both readers and writers are blocked at gate1_
with (nearly) identical requirements imposed to get past it, is what makes this algorithm fair to both readers and writers, starving neither.
读者和作者都gate1_
因(几乎)相同的要求而被阻止以通过它的特征是使该算法对读者和作者都公平的原因,既不挨饿。
The mutex "state" is intentionally kept in a single word so as to suggest that the partial use of atomics (as an optimization) for certain state changes is a possibility (i.e. for an uncontended "fast path"). However that optimization is not demonstrated here. One example would be if a writer thread could atomically change state_
from 0 to write_entered
then he obtains the lock without having to block or even lock/unlock mut_
. And unlock()
could be implemented with an atomic store. Etc. These optimizations are not shown herein because they are much harder to implement correctly than this simple description makes it sound.
互斥锁“状态”有意保留在一个词中,以表明可以部分使用原子(作为优化)用于某些状态更改(即,对于无竞争的“快速路径”)。但是,此处未演示该优化。一个例子是,如果一个写线程可以原子地state_
从 0更改为 0 ,write_entered
那么他无需阻塞甚至锁定/解锁就可以获得锁mut_
。并且unlock()
可以用原子存储来实现。等等。这里没有显示这些优化,因为它们比这个简单的描述听起来更难正确实现。
回答by Maxim Egorushkin
Concurrent reads of data protected by a mutex are rather common, while writes are rare
由互斥锁保护的数据的并发读取相当普遍,而写入则很少见
That sounds like an ideal scenario for User-space RCU:
这听起来像是用户空间 RCU的理想场景:
URCU is similar to its Linux-kernel counterpart, providing a replacement for reader-writer locking, among other uses. This similarity continues with readers not synchronizing directly with RCU updaters, thus making RCU read-side code paths exceedingly fast, while furthermore permitting RCU readers to make useful forward progress even when running concurrently with RCU updaters—and vice versa.
URCU 类似于它的 Linux 内核对应物,提供读写锁定的替代品,以及其他用途。这种相似性在阅读器不直接与 RCU 更新器同步的情况下继续存在,从而使 RCU 读取端代码路径非常快,同时允许 RCU 阅读器即使在与 RCU 更新器同时运行时也能取得有用的前进进展,反之亦然。
回答by bazza
There's some good tricks you can do to help.
您可以采取一些很好的技巧来提供帮助。
First, good performance. VxWorks is notable for its very good context switch times. Whatever the locking solution you use it will likely involve semaphores. I wouldn't be afraid of using semaphores (plural) for this, they're pretty well optimsed in VxWorks, and the fast context switch times help mimimise the degradation in performance from assessing many semaphore states, etc.
一是性能好。VxWorks 以其非常好的上下文切换时间而著称。无论您使用哪种锁定解决方案,它都可能涉及信号量。我不会害怕为此使用信号量(复数),它们在 VxWorks 中得到了很好的优化,并且快速的上下文切换时间有助于最大限度地减少评估许多信号量状态等导致的性能下降。
Also I would forget using POSIX semaphores, which are simply going to be layered on top of VxWork's own semaphores. VxWorks provices native counting, binary and mutex semaphores; using the one that suits makes it all a bit faster. The binary ones can be quite useful sometimes; posted to many times, never exceed the value of 1.
此外,我会忘记使用 POSIX 信号量,它们只是在 VxWork 自己的信号量之上分层。VxWorks 提供原生计数、二进制和互斥信号量;使用适合的方法可以让一切变得更快。二进制文件有时非常有用;多次发布,永远不会超过1的值。
Second, writes being more important than reads. When I've had this kind of requirement in VxWorks and have been using a semaphore(s) to control access, I've used task priority to indicate which task is more important and should get first access to the resource. This works quite well; literally everything in VxWorks is a task (well, thread) like any other, including all the device drivers, etc.
其次,写比读更重要。当我在 VxWorks 中有这种需求并一直使用信号量来控制访问时,我使用任务优先级来指示哪个任务更重要并且应该首先访问资源。这很有效;从字面上看,VxWorks 中的所有内容都是一项任务(好吧,线程),就像其他任何任务一样,包括所有设备驱动程序等。
VxWorks also resolves priority inversions (the kind of thing that Linus Torvalds hates). So if you implement your locking with a semaphore(s), you can rely on the OS scheduler to chivvy up lower priority readers if they're blocking a higher priority writer. It can lead to much simpler code, and you're getting the most of the OS too.
VxWorks 还解决了优先级倒置(Linus Torvalds 讨厌的那种事情)。因此,如果您使用信号量实现锁定,则您可以依靠操作系统调度程序在较低优先级的读取器阻塞较高优先级的写入器时对其进行调度。它可以导致更简单的代码,并且您也可以充分利用操作系统。
So a potential solutionis to have a single VxWorks counting semaphore protecting the resource, initialised to a value equal to the number of readers. Each time a reader wants to read, it takes the semaphore (reducing the count by 1. Each time a read is done it posts the semaphore, increasing the count by 1. Each time the writer wants to write it takes the semaphore n (n = number of readers) times, and posts it n times when done. Finally make the writer task of higher priority than any of the readers, and rely on the OS fast context switch time and priority inversion.
因此,一个潜在的解决方案是使用单个 VxWorks 计数信号量来保护资源,并将其初始化为等于读取器数量的值。每次读者想要读取时,它都需要信号量(将计数减少 1。每次读取完成时,它都会发布信号量,将计数增加 1。每次写入者想要写入时,它需要信号量 n (n = 读者数)次,完成后发布 n 次。最后使 writer 任务的优先级高于任何一个 reader,并依靠 OS 快速上下文切换时间和优先级反转。
Remember that you're programming on a hard-realtime OS, not Linux. Taking / posting a native VxWorks semaphore doesn't involve the same amount of runtime as a similar act on Linux, though even Linux is pretty good these days (I'm using PREEMPT_RT nowadays). The VxWorks scheduler and all the device drivers can be relied upon to behave. You can even make your writer task the highest priority in the whole system if you wish, higher even than all the device drivers!
请记住,您是在硬实时操作系统上编程,而不是在 Linux 上。获取/发布本机 VxWorks 信号量与 Linux 上的类似操作不涉及相同数量的运行时,尽管如今即使 Linux 也相当不错(我现在使用 PREEMPT_RT)。可以依赖 VxWorks 调度程序和所有设备驱动程序来运行。如果您愿意,您甚至可以将您的编写器任务设为整个系统中的最高优先级,甚至高于所有设备驱动程序!
To help things along, also consider what it is that each of your threads are doing. VxWorks allows you to indicate that a task is/isn't using the FPU. If you're using native VxWorks TaskSpawn routines instead of pthread_create then you get an opportunity to specify this. What it means is that if your thread/task isn't doing any floating point maths, and you've said as such in your call to TaskSpawn, the context switch times will be even faster because the scheduler won't bother to preserve / restore the FPU state.
为了帮助解决问题,还要考虑您的每个线程正在做什么。VxWorks 允许您指示任务是否使用 FPU。如果您使用本地 VxWorks TaskSpawn 例程而不是 pthread_create,那么您有机会指定它。这意味着如果您的线程/任务没有进行任何浮点数学运算,并且您在调用 TaskSpawn 时已经说过,上下文切换时间将更快,因为调度程序不会费心保留 /恢复 FPU 状态。
This stands a reasonable chance of being the best solution on the platform you're developing on. It's playing to the OS's strengths (fast semaphores, fast context switch times) without introducing a load of extra code to recreate an alternate (and possibly more elegant) solution commonly found on other platforms.
这很有可能成为您正在开发的平台上的最佳解决方案。它发挥了操作系统的优势(快速信号量、快速上下文切换时间),而不会引入额外的代码负载来重新创建其他平台上常见的替代(并且可能更优雅)的解决方案。
Third, stuck with old GCC and old Boost. Basically I can't help there other than low value suggestions about phoning up WindRiver and discussing buying an upgrade. Personally speaking when I've been programming for VxWorks I've used VxWork's native API rather than POSIX. Ok, so the code hasn't be very portable, but it has ended up being fast; POSIX is merely layer on top of the native API anyway and that will always slow things down.
第三,坚持使用旧的 GCC 和旧的 Boost。除了关于打电话给 WindRiver 和讨论购买升级的低价值建议之外,基本上我无能为力。就我个人而言,当我为 VxWorks 编程时,我使用的是 VxWork 的原生 API 而不是 POSIX。好的,所以代码不是很便携,但它最终很快;无论如何,POSIX 只是本机 API 之上的一层,这总是会减慢速度。
That said, POSIX counting and mutex semaphores are very similar to VxWork's native counting and mutex semaphores. That probably means that the POSIX layering isn't very thick.
也就是说,POSIX 计数和互斥信号量与 VxWork 的原生计数和互斥信号量非常相似。这可能意味着 POSIX 分层不是很厚。
General Notes About Programming for VxWorks
VxWorks 编程的一般注意事项
DebuggingI always sought to use the development tools (Tornado) available for Solaris. This is by far the best multi-threaded debugging environment I've ever come across. Why? It allows you to start up multiple debug sessions, one for each thread/task in the system. You end up with a debug window per thread, and you are individually and independently debugging each one. Step over a blocking operation, that debug window gets blocked. Move mouse focus to another debugging window, step over the operation that will release the block and watch the first window complete its step.
调试我一直试图使用可用于 Solaris 的开发工具 (Tornado)。这是迄今为止我遇到的最好的多线程调试环境。为什么?它允许您启动多个调试会话,系统中的每个线程/任务一个。您最终会为每个线程提供一个调试窗口,并且您可以单独且独立地调试每个线程。跳过阻塞操作,该调试窗口被阻塞。将鼠标焦点移到另一个调试窗口,跳过将释放块的操作并观察第一个窗口完成其步骤。
You end up with a lot of debug windows, but it's by far the best way to debug multi-threaded stuff. It made it veeeeery easy to write really quite complex stuff and see problems. You can easily explore the different dynamic interactions in your application because you had simple and all powerful control over what each thread is doing at any time.
您最终会看到很多调试窗口,但它是迄今为止调试多线程内容的最佳方式。它使编写非常复杂的东西和发现问题变得非常容易。您可以轻松地探索应用程序中不同的动态交互,因为您可以随时对每个线程所做的事情进行简单而强大的控制。
Ironically the Windows version of Tornado didn't let you do this; one miserable single debug windows per system, just like any other boring old IDE such as Visual Studio, etc. I've never seen even modern IDEs come anywhere close to being as good as Tornado on Solaris for multi-threaded debugging.
具有讽刺意味的是,Windows 版本的 Tornado 并没有让你这样做。每个系统一个悲惨的单个调试窗口,就像任何其他无聊的旧 IDE(例如 Visual Studio 等)一样。我从未见过现代 IDE 在多线程调试方面与 Solaris 上的 Tornado 一样出色。
HardDrivesIf your readers and writers are using files on disk, consider that VxWorks 5.5 is pretty old. Things like NCQ aren't going to be supported. In this case my proposed solution (outlined above) might be better done with a single mutex semaphore to stop multiple readers tripping over each other in their struggle to read different parts of the disk. It depends on what exactly your readers are doing, but if they're reading contiguous data from a file this would avoid thrashing the read/write head to and fro across the disk surface (very slow).
磁碟机如果你的读者和作家使用磁盘上的文件,考虑的VxWorks 5.5是很老。不支持像 NCQ 这样的东西。在这种情况下,我提出的解决方案(上面概述)可能会更好地使用单个互斥信号量来完成,以阻止多个读取器在读取磁盘的不同部分时相互绊倒。这取决于您的读者究竟在做什么,但如果他们从文件中读取连续数据,这将避免在磁盘表面上来回摆动读/写头(非常慢)。
In my case I was using this trick to shape traffic across a network interface; each task was sending a different sort of data, and the task priority reflected the priority of the data on the network. It was very elegant, no message was ever fragmented, but the important messages got the lions share of the available bandwidth.
就我而言,我使用这个技巧来调整网络接口上的流量;每个任务都在发送不同种类的数据,任务优先级反映了网络上数据的优先级。它非常优雅,没有消息碎片化,但重要的消息获得了可用带宽的最大份额。
回答by Doug Binks
As always the best solution will depend on details. A read-write spin lock may be what you're looking for, but other approaches such as read-copy-update as suggested above might be a solution - though on an old embedded platform the extra memory used might be an issue. With rare writes I often arrange the work using a tasking system such that the writes can only occur when there are no reads from that data structure, but this is algorithm dependent.
一如既往,最佳解决方案取决于细节。读写自旋锁可能是您正在寻找的,但其他方法(例如上面建议的读取复制更新)可能是一种解决方案 - 尽管在旧的嵌入式平台上使用的额外内存可能是一个问题。对于罕见的写入,我经常使用任务系统安排工作,这样写入只能在没有从该数据结构读取时发生,但这取决于算法。
回答by wilx
One algorithm for this based on semaphores and mutexes is described in Concurrent Control with Readers and Writers; P.J. Courtois, F. Heymans, and D.L. Parnas; MBLE Research Laboratory; Brussels, Belgium.
Concurrent Control with Readers and Writers 中描述了一种基于信号量和互斥体的算法 ;PJ Courtois、F. Heymans 和 DL Parnas;MBLE研究实验室;比利时布鲁塞尔。
回答by QuestionC
This is a simplified answer based on my Boost headers (I would call Boost an approved way). It only requires Condition Variables and Mutexes. I rewrote it using Windows primitives because I find them descriptive and very simple, but view this as Pseudocode.
这是基于我的 Boost 标头的简化答案(我将 Boost 称为已批准的方式)。它只需要条件变量和互斥体。我使用 Windows 原语重写了它,因为我发现它们具有描述性且非常简单,但将其视为伪代码。
This is a very simple solution, which does not support things like mutex upgrading, or try_lock() operations. I can add those if you want. I also took out some frills like disabling interrupts that aren't strictly necessary.
这是一个非常简单的解决方案,它不支持互斥量升级或 try_lock() 操作之类的东西。如果你愿意,我可以添加这些。我还去掉了一些多余的东西,比如禁用不是绝对必要的中断。
Also, it's worth checking out boost\thread\pthread\shared_mutex.hpp
(this being based on that). It's human-readable.
此外,值得一试boost\thread\pthread\shared_mutex.hpp
(这是基于此)。它是人类可读的。
class SharedMutex {
CRITICAL_SECTION m_state_mutex;
CONDITION_VARIABLE m_shared_cond;
CONDITION_VARIABLE m_exclusive_cond;
size_t shared_count;
bool exclusive;
// This causes write blocks to prevent further read blocks
bool exclusive_wait_blocked;
SharedMutex() : shared_count(0), exclusive(false)
{
InitializeConditionVariable (m_shared_cond);
InitializeConditionVariable (m_exclusive_cond);
InitializeCriticalSection (m_state_mutex);
}
~SharedMutex()
{
DeleteCriticalSection (&m_state_mutex);
DeleteConditionVariable (&m_exclusive_cond);
DeleteConditionVariable (&m_shared_cond);
}
// Write lock
void lock(void)
{
EnterCriticalSection (&m_state_mutex);
while (shared_count > 0 || exclusive)
{
exclusive_waiting_blocked = true;
SleepConditionVariableCS (&m_exclusive_cond, &m_state_mutex, INFINITE)
}
// This thread now 'owns' the mutex
exclusive = true;
LeaveCriticalSection (&m_state_mutex);
}
void unlock(void)
{
EnterCriticalSection (&m_state_mutex);
exclusive = false;
exclusive_waiting_blocked = false;
LeaveCriticalSection (&m_state_mutex);
WakeConditionVariable (&m_exclusive_cond);
WakeAllConditionVariable (&m_shared_cond);
}
// Read lock
void lock_shared(void)
{
EnterCriticalSection (&m_state_mutex);
while (exclusive || exclusive_waiting_blocked)
{
SleepConditionVariableCS (&m_shared_cond, m_state_mutex, INFINITE);
}
++shared_count;
LeaveCriticalSection (&m_state_mutex);
}
void unlock_shared(void)
{
EnterCriticalSection (&m_state_mutex);
--shared_count;
if (shared_count == 0)
{
exclusive_waiting_blocked = false;
LeaveCriticalSection (&m_state_mutex);
WakeConditionVariable (&m_exclusive_cond);
WakeAllConditionVariable (&m_shared_cond);
}
else
{
LeaveCriticalSection (&m_state_mutex);
}
}
};
Behavior
行为
Okay, there is some confusion about the behavior of this algorithm, so here is how it works.
好的,这个算法的行为有些混乱,所以这里是它的工作原理。
During a Write Lock- Both readers and writers are blocked.
在写锁定期间- 读取器和写入器都被阻止。
At the end of a Write Lock- Reader threads and one writer thread will raceto see which one starts.
在写锁结束时- 读取器线程和一个写入器线程将竞争以查看哪个开始。
During a Read Lock- Writers are blocked. Readers are also blocked if and only ifa Writer is blocked.
在读锁定期间-写入者被阻止。当且仅当Writer 被阻止时,Reader 也会被阻止。
At the release of the final Read Lock- Reader threads and one writer thread will raceto see which one starts.
在最后一个 Read Lock 释放时- Reader 线程和一个 writer 线程将竞争以查看哪个启动。
This couldcause readers to starve writers if the processor frequently context switches over to a m_shared_cond
thread before an m_exclusive_cond
during notification, but I suspect that issue is theoretical and not practical since it's Boost's algorithm.
如果处理器在通知期间频繁上下文切换到线程,这可能会导致读者饿死作家,但我怀疑这个问题是理论上的而不是实际的,因为它是 Boost 的算法。m_shared_cond
m_exclusive_cond
回答by zmbq
Now that Microsoft has opened up the .NET source code, you can look at their ReaderWRiterLockSlimimplementation.
现在微软已经开放了 .NET 源代码,你可以看看他们的ReaderWRiterLockSlim实现。
I'm not sure the more basic primitives they use are available to you, some of them are also part of the .NET library and their code is also available.
我不确定他们使用的更基本的原语是否可供您使用,其中一些也是 .NET 库的一部分,它们的代码也可用。
Microsoft has spent quite a lot of time on improving the performance of their locking mechanisms, so this can be a good starting point.
Microsoft 花费了大量时间来改进其锁定机制的性能,因此这可能是一个很好的起点。