You can also do this using regular mutexes and variable conditions, rather than general ones. Presumably shared_mutex has higher overhead, so I'm not sure if it will be faster. With Gallik’s decision, you are supposed to pay to lock the shared mutex with every write call; I got the impression that write receives a call more than it is being read, so maybe this is undesirable.
int* data; // initialized somewhere std::atomic<size_t> size = 0; std::atomic<bool> reading = false; std::atomic<int> num_writers = 0; std::mutex entering; std::mutex leaving; std::condition_variable cv; void write(int x) { ++num_writers; if (reading) { --num_writers; if (num_writers == 0) { std::lock_guard l(leaving); cv.notify_one(); } { std::lock_guard l(entering); } ++num_writers; } auto slot = size.fetch_add(1, std::memory_order_acquire); data[slot] = x; --num_writers; if (reading && num_writers == 0) { std::lock_guard l(leaving); cv.notify_one(); } } int* read() { int* other_data = new int[capacity]; { std::unique_lock enter_lock(entering); reading = true; std::unique_lock leave_lock(leaving); cv.wait(leave_lock, [] () { return num_writers == 0; }); swap(data, other_data); size = 0; reading = false; } return other_data; }
It's a bit complicated, and it took me a while to work, but I think it should serve the purpose well.
In the normal case, when only writing occurs, reading is always false. Thus, you do the usual thing and pay for two additional atomic increments and two inactive branches. Thus, there is no need to block any mutexes for a common path, unlike a solution using a common mutex, it is supposedly expensive: http://permalink.gmane.org/gmane.comp.lib.boost.devel/211180 .
Now suppose read is called. First, an expensive, slow heap allocation occurs, and recording continues uninterrupted. Then an input lock is entered, which has no immediate effect. Now the reading parameter is set to true. Immediately, any new calls for recording enter the first branch and, in the end, hit an input lock that they cannot receive (as has already been accepted), and these threads then go to sleep.
Meanwhile, the read stream is now waiting, provided that the number of writers is 0. If we are lucky, it can really go right away. If, however, there are entries in one of two places between the increment and decrease of num_writers, then it will not. Each time the write stream decreases num_writers , it checks to see if this number has been reduced to zero, and when it does, a condition variable will be signaled. Since num_writers is atomic, which prevents various reordering frauds, it is guaranteed that the last thread sees num_writers == 0 ; it can also be notified more than once, but this is normal and cannot lead to bad behavior.
As soon as this condition variable has been signaled, it shows that all authors either fall into the first branch or are modified. So, the read stream can now safely exchange data, and then unlock everything, and then return what it needs.
As mentioned earlier, in a typical operation there are no locks, the branches simply expand and decompress. Even when reading occurs, the read stream will have one lock and one condition variable, while a typical write stream will have about one mutex lock / unlock and that is all (one or a small number of write streams will also notify the condition variable).