-
Notifications
You must be signed in to change notification settings - Fork 1
/
AIReadWriteMutex.h
154 lines (140 loc) · 6.76 KB
/
AIReadWriteMutex.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/**
* threadsafe -- Threading utilities: object oriented (read/write) locking and more.
*
* @file
* @brief Implementation of AIReadWriteMutex.
*
* @Copyright (C) 2010, 2016, 2017 Carlo Wood.
*
* pub dsa3072/C155A4EEE4E527A2 2018-08-16 Carlo Wood (CarloWood on Libera) <[email protected]>
* fingerprint: 8020 B266 6305 EE2F D53E 6827 C155 A4EE E4E5 27A2
*
* This file is part of threadsafe.
*
* Threadsafe is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Threadsafe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with threadsafe. If not, see <http://www.gnu.org/licenses/>.
*
* CHANGELOG
* and additional copyright holders.
*
* 2015/03/01
* - Moved code from Singularity to separate repository.
* - Changed the license to the GNU Affero General Public License.
* - Major rewrite to make it more generic and use C++11 thread support.
*
* 2016/12/17
* - Transfered copyright to Carlo Wood.
*/
#pragma once
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <thread>
#include <exception>
class AIReadWriteMutex
{
public:
AIReadWriteMutex() : m_readers_count(0), m_waiting_writers(0), m_rd2wr_count(0) { }
private:
std::mutex m_state_mutex; ///< Guards all member variables below.
std::condition_variable m_condition_unlocked; ///< Condition variable used to wait for no readers or writers left (to tell waiting writers).
std::condition_variable m_condition_no_writer_left; ///< Condition variable used to wait for no writers left (to tell waiting readers).
std::condition_variable m_condition_one_reader_left; ///< Condition variable used to wait for one reader left (to tell that reader that it can become a writer).
std::condition_variable m_condition_rd2wr_count_zero; ///< Condition variable used to wait until m_rd2wr_count is zero.
int m_readers_count; ///< Number of readers or -1 if a writer locked this object.
int m_waiting_writers; ///< Number of threads that are waiting for a write lock. Used to block readers from waking up.
int m_rd2wr_count; ///< Number of threads that try to go from a read lock to a write lock.
public:
void rdlock()
{
std::unique_lock<std::mutex> lk(m_state_mutex); // Get exclusive access.
m_condition_no_writer_left.wait(lk, [this]{return m_readers_count >= 0;}); // Wait till m_readers_count is no longer -1.
++m_readers_count; // One more reader.
}
void rdunlock()
{
std::unique_lock<std::mutex> lk(m_state_mutex); // Get exclusive access.
if (--m_readers_count <= 1) // Decrease reader count. Was this the (second) last reader?
{
bool one_reader_left = m_readers_count == 1;
// In most practical cases there are no race conditions, so it is more efficient to first unlock m_state_mutex and only then kick waiting threads:
// if we did that the other way around then threads woken up would immediately block again on trying to obtain m_state_mutex before leaving wait().
// In the case that we try to wake up threads who can't be woken up because another thread now locked this object then the wait predicate will stop
// them from being woken up.
lk.unlock();
if (one_reader_left) // Still one reader left, so only notify m_condition_one_reader_left.
m_condition_one_reader_left.notify_one();
else
m_condition_unlocked.notify_one(); // No readers left, tell waiting writers.
}
}
void wrlock()
{
std::unique_lock<std::mutex> lk(m_state_mutex); // Get exclusive access.
++m_waiting_writers; // Stop readers from being woken up.
m_condition_unlocked.wait(lk, [this]{return m_readers_count == 0;}); // Wait until m_readers_count is 0 (nobody else has the lock).
--m_waiting_writers;
m_readers_count = -1; // We are a writer now.
}
void rd2wrlock()
{
std::unique_lock<std::mutex> lk(m_state_mutex); // Get exclusive access.
if (++m_rd2wr_count > 1) // Only the first thread that calls rd2wrlock will get passed this.
{
--m_rd2wr_count;
// It is impossible to recover from this: two threads have a read lock
// and require to turn that into a write lock. The only way out of this
// is to throw an exception and let the caller solve the mess.
// To solve this situation, either only use rd2wrlock() from a single
// thread (other threads may still call the other member functions), or
// catch this exception and call rdunlock() and then rd2wryield().
// After that a new attempt can be made.
throw std::exception();
}
++m_waiting_writers; // Stop readers from being woken up.
m_condition_one_reader_left.wait(lk, [this]{return m_readers_count == 1;}); // Wait till m_readers_count is 1 (only this thead has its read lock).
--m_waiting_writers;
m_readers_count = -1; // We are a writer now.
if (--m_rd2wr_count == 0)
m_condition_rd2wr_count_zero.notify_one(); // Allow additional calls to rd2wrlock().
}
void rd2wryield()
{
std::this_thread::yield();
std::unique_lock<std::mutex> lk(m_state_mutex); // Get exclusive access.
m_condition_rd2wr_count_zero.wait(lk, [this]{return m_rd2wr_count == 0;});
}
void wrunlock()
{
m_state_mutex.lock(); // Get exclusive access.
m_readers_count = 0; // We have no writer anymore.
int waiting_writer = m_waiting_writers;
m_state_mutex.unlock(); // Release m_state_mutex so that threads can leave their respective wait() immediately.
if (waiting_writer)
m_condition_unlocked.notify_one(); // Tell waiting writers.
else
m_condition_no_writer_left.notify_all(); // Tell waiting readers.
}
void wr2rdlock()
{
m_state_mutex.lock(); // Get exclusive access.
m_readers_count = 1; // Turn writer into a reader.
int waiting_writer = m_waiting_writers;
m_state_mutex.unlock(); // Release m_state_mutex so that threads can leave their respective wait() immediately.
// Don't call m_condition_one_reader_left.notify_one() because it is impossible that any thread
// is waiting there: they'd need to have been a reader before and that is not allowed while
// we had the write lock.
if (!waiting_writer)
m_condition_no_writer_left.notify_all(); // Tell waiting readers.
}
};