emilib
read_write_mutex.hpp
1 // By Emil Ernerfeldt 2013-2016
2 // LICENSE:
3 // This software is dual-licensed to the public domain and under the following
4 // license: you are granted a perpetual, irrevocable license to copy, modify,
5 // publish, and distribute this file as you see fit.
6 // HISTORY
7 // - 2013-01-24 - Initial conception
8 // - 2016-01-28 - Cleaned up.
9 // - 2016-08-09 - Added to emilib.
10 // Version 1.0.0 - 2016-08-14 - Made into a drop-in std::shared_mutex replacement.
11 // Version 1.0.1 - 2016-08-24 - Bug fix in try_lock (thanks, Ninja101!)
12 #pragma once
13 
14 #include <atomic>
15 #include <condition_variable>
16 #include <mutex>
17 #include <thread>
18 
19 namespace emilib {
20 
21 /*
22 Two mutex classes that acts like C++17's std::shared_mutex, but are faster and C++11
23 
24 Mutex optimized for locking things that are often read, seldom written.
25  * Many can read at the same time.
26  * Only one can write at the same time.
27  * No-one can read while someone is writing.
28 
29 Is ReadWriteMutex right for you?
30  * If most access are writes, use std::mutex.
31  * Else if you almost always access the resource from one thread, ReadLock is slightly faster than std::mutex.
32  * Else (if most access are read-only, and there is more than one thread): ReadWriteMutex will be up to 100x faster then std::mutex.
33 
34 What about std::shared_timed_mutex?
35  * I benchmarked GCC5:s std::shared_timed_mutex in rw_mutex_benchmark.cpp, and it's SLOW. AVOID!
36 
37  What about std::shared_mutex?
38  * It is C++17, so it's currently from the future.
39  * I benched GCC6:s std::shared_mutex and:
40  * Unique locking is 50% faster than this library.
41  * Shared locking is 3x-4x slower than ReadWriteMutex.
42  * When mixing read/write locks, std::shared_mutex can be up to 6x slower than this library.
43 
44  The FastWriteLock will spin-wait for zero readers. This is the best choice when you expect the reader to be done quickly.
45  With SlowWriteLock there is a std::mutex instead of a spin-lock in WriteLock. This is useful to save CPU if the read operation can take a long time, e.g. file or network access.
46 
47  The mutex is *not* recursive, i.e. you must not lock it twice on the same thread.
48 
49  Example usage:
50 
51  class StringMonitor
52  {
53  public:
54  std:string get() const
55  {
56  FastReadLock lock(_mutex);
57  return _name;
58  }
59 
60  void set(std::string str)
61  {
62  FastWriteLock lock(_mutex);
63  _name.swap(str);
64  }
65 
66  private:
67  mutable FastReadWriteMutex _mutex;
68  std::string _name;
69  };
70  */
71 
72 // ----------------------------------------------------------------------------
73 
78 {
79 public:
80  FastReadWriteMutex() { }
81 
86  void lock()
87  {
88  _write_mutex.lock(); // Ensure we are the only one writing
89  _has_writer = true; // Steer new readers into a lock (provided by the above mutex)
90 
91  // Wait for all readers to finish.
92  // Busy spin-waiting
93  while (_num_readers != 0) {
94  std::this_thread::yield(); // Give the reader-threads a chance to finish.
95  }
96 
97  // All readers have finished - we are not locked exclusively!
98  }
99 
103  bool try_lock()
104  {
105  if (!_write_mutex.try_lock()) {
106  return false;
107  }
108 
109  _has_writer = true;
110 
111  if (_num_readers == 0) {
112  return true;
113  } else {
114  _write_mutex.unlock();
115  _has_writer = false;
116  return false;
117  }
118  }
119 
122  void unlock()
123  {
124  _has_writer = false;
125  _write_mutex.unlock();
126  }
127 
131  void lock_shared()
132  {
133  while (_has_writer) {
134  // First check here to stop readers while write is in progress.
135  // This is to ensure _num_readers can go to zero (needed for write to start).
136  std::lock_guard<std::mutex>{_write_mutex}; // wait for the writer to be done
137  }
138 
139  // If a writer starts here, it may think there are no readers, which is why we re-check _has_writer again.
140 
141  ++_num_readers; // Tell any writers that there is now someone reading
142 
143  // Check so no write began before we incremented _num_readers
144  while (_has_writer) {
145  // A write is in progress or is waiting to start!
146  --_num_readers; // We changed our mind
147 
148  std::lock_guard<std::mutex>{_write_mutex}; // wait for the writer to be done
149 
150  ++_num_readers; // Let's try again
151  }
152  }
153 
157  {
158  if (_has_writer) {
159  return false;
160  }
161 
162  ++_num_readers; // Tell any writers that there is now someone reading
163 
164  // Check so no write began before we incremented _num_readers
165  if (_has_writer) {
166  --_num_readers;
167  return false;
168  }
169 
170  return true;
171  }
172 
176  {
177  --_num_readers;
178  }
179 
180 private:
183  FastReadWriteMutex& operator=(FastReadWriteMutex&) = delete;
184  FastReadWriteMutex& operator=(FastReadWriteMutex&&) = delete;
185 
186  std::atomic<int> _num_readers{0};
187  std::atomic<bool> _has_writer{false}; // Is there a writer working (or trying to) ?
188  std::mutex _write_mutex;
189 };
190 
191 // ----------------------------------------------------------------------------
192 
197 {
198 public:
199  SlowReadWriteMutex() { }
200 
204  void lock()
205  {
206  _write_mutex.lock(); // Ensure we are the only one writing
207  _has_writer = true; // Steer new readers into a lock (provided by the above mutex)
208 
209  // Wait for all readers to finish.
210  if (_num_readers != 0) {
211  std::unique_lock<std::mutex> lock{_reader_done_mutex};
212  _reader_done_cond.wait(lock, [=]{ return _num_readers==0; });
213  }
214 
215  // All readers have finished - we are not locked exclusively!
216  }
217 
221  bool try_lock()
222  {
223  if (!_write_mutex.try_lock()) {
224  return false;
225  }
226 
227  _has_writer = true;
228 
229  if (_num_readers == 0) {
230  return true;
231  } else {
232  _write_mutex.unlock();
233  _has_writer = false;
234  return false;
235  }
236  }
237 
238  /*
239  Unlocks the mutex.
240  The mutex must be locked by the current thread of execution, otherwise, the behavior is undefined.
241  */
242  void unlock()
243  {
244  _has_writer = false;
245  _write_mutex.unlock();
246  }
247 
251  void lock_shared()
252  {
253  while (_has_writer) {
254  // First check here to stop readers while write is in progress.
255  // This is to ensure _num_readers can go to zero (needed for write to start).
256  std::lock_guard<std::mutex> lock{_write_mutex}; // wait for the writer to be done
257  }
258 
259  // If a writer starts here, it may think there are no readers, which is why we re-check _has_writer again.
260 
261  ++_num_readers; // Tell any writers that there is now someone reading
262 
263  // Check so no write began before we incremented _num_readers
264  while (_has_writer) {
265  // A write is in progress or is waiting to start!
266 
267  {
268  std::lock_guard<std::mutex> lock{_reader_done_mutex};
269  --_num_readers; // We changed our mind
270  }
271 
272  _reader_done_cond.notify_one(); // Tell the writer we did (he may be waiting for _num_readers to go to zero)
273 
274  std::lock_guard<std::mutex>{_write_mutex}; // wait for the writer to be done
275 
276  ++_num_readers; // Let's try again
277  }
278  }
279 
283  {
284  if (_has_writer) {
285  return false;
286  }
287 
288  ++_num_readers; // Tell any writers that there is now someone reading
289 
290  // Check so no write began before we incremented _num_readers
291  if (_has_writer) {
292  --_num_readers;
293  return false;
294  }
295 
296  return true;
297  }
298 
302  {
303  --_num_readers;
304 
305  if (_has_writer) {
306  // A writer is waiting for all readers to finish. Tell him one more has:
307  _reader_done_cond.notify_one();
308  }
309  }
310 
311 private:
314  SlowReadWriteMutex& operator=(SlowReadWriteMutex&) = delete;
315  SlowReadWriteMutex& operator=(SlowReadWriteMutex&&) = delete;
316 
317  std::atomic<int> _num_readers{0};
318  std::atomic<bool> _has_writer{false}; // Is there a writer working (or trying to) ?
319  std::mutex _write_mutex;
320  std::condition_variable _reader_done_cond; // Signals a waiting writer that a read has finishes
321  std::mutex _reader_done_mutex; // Synchronizes _num_readers vs _reader_done_cond
322 };
323 
324 // ----------------------------------------------------------------------------
325 
327 template<typename MutexType>
328 class ReadLock
329 {
330 public:
331  explicit ReadLock(MutexType& mut) : _rw_mutex(mut)
332  {
333  lock();
334  }
335 
337  ReadLock(MutexType& mut, std::defer_lock_t) : _rw_mutex(mut)
338  {
339  }
340 
341  ~ReadLock() { unlock(); }
342 
344  void lock()
345  {
346  if (!_locked) {
347  _rw_mutex.lock_shared();
348  _locked = true;
349  }
350  }
351 
353  bool try_lock()
354  {
355  if (!_locked) {
356  _locked = _rw_mutex.try_lock_shared();
357  }
358  return _locked;
359  }
360 
362  void unlock()
363  {
364  if (_locked) {
365  _rw_mutex.unlock_shared();
366  _locked = false;
367  }
368  }
369 
370 private:
371  ReadLock(ReadLock&) = delete;
372  ReadLock(ReadLock&&) = delete;
373  ReadLock& operator=(ReadLock&) = delete;
374  ReadLock& operator=(ReadLock&&) = delete;
375 
376  MutexType& _rw_mutex;
377  bool _locked{false};
378 };
379 
380 // ----------------------------------------------------------------------------
381 
383 template<typename MutexType>
385 {
386 public:
387  explicit WriteLock(MutexType& mut) : _rw_mutex(mut)
388  {
389  lock();
390  }
391 
393  WriteLock(MutexType& mut, std::defer_lock_t) : _rw_mutex(mut)
394  {
395  }
396 
397  ~WriteLock() { unlock(); }
398 
400  void lock()
401  {
402  if (!_locked) {
403  _rw_mutex.lock();
404  _locked = true;
405  }
406  }
407 
409  bool try_lock()
410  {
411  if (!_locked) {
412  _locked = _rw_mutex.try_lock();
413  }
414  return _locked;
415  }
416 
418  void unlock()
419  {
420  if (_locked) {
421  _rw_mutex.unlock();
422  _locked = false;
423  }
424  }
425 
426 private:
427  WriteLock(WriteLock&) = delete;
428  WriteLock(WriteLock&&) = delete;
429  WriteLock& operator=(WriteLock&) = delete;
430  WriteLock& operator=(WriteLock&&) = delete;
431 
432  MutexType& _rw_mutex;
433  bool _locked{false};
434 };
435 
436 // ----------------------------------------------------------------------------
437 
438 } // namespace emilib
bool try_lock()
Definition: read_write_mutex.hpp:103
void unlock()
unlock, unless already unlocked.
Definition: read_write_mutex.hpp:418
void lock()
Lock, unless already locked.
Definition: read_write_mutex.hpp:344
bool try_lock_shared()
Definition: read_write_mutex.hpp:282
Definition: read_write_mutex.hpp:77
void unlock()
Definition: read_write_mutex.hpp:122
bool try_lock()
Definition: read_write_mutex.hpp:221
bool try_lock()
Does not block. Returns true iff the mutex is locked by this thread after the call.
Definition: read_write_mutex.hpp:353
Definition: read_write_mutex.hpp:196
WriteLock(MutexType &mut, std::defer_lock_t)
Won&#39;t lock right away.
Definition: read_write_mutex.hpp:393
void lock()
Definition: read_write_mutex.hpp:86
void unlock_shared()
Definition: read_write_mutex.hpp:301
void unlock_shared()
Definition: read_write_mutex.hpp:175
This is a drop-in replacement for C++14&#39;s std::shared_lock.
Definition: read_write_mutex.hpp:328
ReadLock(MutexType &mut, std::defer_lock_t)
Won&#39;t lock right away.
Definition: read_write_mutex.hpp:337
bool try_lock_shared()
Definition: read_write_mutex.hpp:156
void lock()
Lock, unless already locked.
Definition: read_write_mutex.hpp:400
This is a drop-in replacement for C++11&#39;s std::unique_lock.
Definition: read_write_mutex.hpp:384
void lock_shared()
Definition: read_write_mutex.hpp:131
void unlock()
unlock, unless already unlocked.
Definition: read_write_mutex.hpp:362
bool try_lock()
Does not block. Returns true iff the mutex is locked by this thread after the call.
Definition: read_write_mutex.hpp:409
void lock_shared()
Definition: read_write_mutex.hpp:251
void lock()
Definition: read_write_mutex.hpp:204
Definition: coroutine.hpp:18