Skip to content

Commit 7df6279

Browse files
committed
Merge branch 'develop' into release/0.1.0
2 parents 15733fc + 3243f2d commit 7df6279

File tree

1 file changed

+140
-0
lines changed

1 file changed

+140
-0
lines changed

README.md

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# Sync
2+
This header-only library is a small wrapper around [`std::mutex`](https://en.cppreference.com/w/cpp/thread/mutex) and [`std::shared_timed_mutex`](https://en.cppreference.com/w/cpp/thread/shared_timed_mutex) in order to better express which data they protect.
3+
The design of the library is heavily influenced by the Rust [`std::sync`](https://doc.rust-lang.org/std/sync) in particular [`std::sync::Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html) and [`std::sync::RwLock`](https://doc.rust-lang.org/std/sync/struct.RwLock.html).
4+
5+
## Requirements
6+
* C++14
7+
* CMake 3.14 (only when using CPM for installation)
8+
9+
## Installation
10+
Either,
11+
12+
* copy the content of the `include` folder somewhere and add it to your include directories
13+
14+
* using CPM
15+
```cmake
16+
CPMAddPackage("gh:soehrl/[email protected]")
17+
target_link_libraries(your-target PRIVATE sync::sync)
18+
```
19+
20+
## `sync::mutex<T>`
21+
In contrast to `std::mutex`, `sync::mutex<T>` takes an additional type parameter `T` that represents the data that is protected by the mutex.
22+
When constructing the mutex, you have to supply an initial value for `T`.
23+
Accessing the value of `T` can be done by calling `mutex::lock()` or `mutex::try_lock()` which return a RAII-style lock guard.
24+
The lock guard then provides the `*` and `->` operator to access the value protected by the mutex.
25+
This makes it very clear which data is protected by which mutex and makes it hard to accidentally access the data without locking the corresponding mutex.
26+
Especially, it prevents the issue where you create a `std::unique_lock` but forget to give it a name, so the lock is immediately released.
27+
28+
<table>
29+
<tr>
30+
<th>
31+
32+
`std::mutex`
33+
34+
</th>
35+
<th>
36+
37+
`sync::mutex<T>`
38+
39+
</th>
40+
</tr>
41+
<tr>
42+
<td>
43+
44+
```c++
45+
#include <chrono>
46+
#include <thread>
47+
#include <mutex>
48+
49+
int g_num = 0; // protected by g_num_mutex
50+
std::mutex g_num_mutex;
51+
52+
using namespace std::literals::chrono_literals;
53+
void increment(int id)
54+
{
55+
for (int i = 0; i < 1000; ++i) {
56+
std::unique_lock<std::mutex> lock(g_num_mutex);
57+
++g_num;
58+
std::this_thread::sleep_for(10ms);
59+
}
60+
}
61+
62+
int main()
63+
{
64+
std::thread t1{increment, 0};
65+
std::thread t2{increment, 1};
66+
t1.join();
67+
t2.join();
68+
}
69+
```
70+
71+
</td>
72+
<td>
73+
74+
```c++
75+
#include <chrono>
76+
#include <thread>
77+
#include "sync/mutex.hpp"
78+
79+
sync::mutex<int> g_num(0);
80+
81+
using namespace std::literals::chrono_literals;
82+
void increment(int id)
83+
{
84+
for (int i = 0; i < 1000; ++i) {
85+
*g_num.lock() += 1;
86+
std::this_thread::sleep_for(10ms);
87+
}
88+
}
89+
90+
int main()
91+
{
92+
std::thread t1{increment, 0};
93+
std::thread t2{increment, 1};
94+
t1.join();
95+
t2.join();
96+
}
97+
```
98+
99+
</td>
100+
</tr>
101+
</table>
102+
103+
## `sync::read_write_lock`
104+
A small wrapper around an `std::shared_timed_lock`.
105+
It works similarly to `sync::mutex` but it allows to lock the mutex either for reading (`std::shared_lock`) via `read_write_lock::read()` or for writing (`std::unique_lock`) via `read_write_lock::write()`.
106+
The lock guard return for reading gives access to a `const T&` while the lock guard returned for writing gives access to a non-const `T&`.
107+
At any time there can be either multiple readers or a single writer for one `read_write_lock`.
108+
This assumes, that it is safe to access const methods from multiple threads at a time.
109+
According to the citation of the C++ standard from the accepted answer of [this stackoverflow post](https://stackoverflow.com/questions/14127379/does-const-mean-thread-safe-in-c11) this is true for the C++ standard library, however, you should be careful when using it with third-party libraries or your own types.
110+
111+
Usage:
112+
```c++
113+
sync::read_write_lock<int> rwlock(100);
114+
115+
SECTION("Simultaneous reads") {
116+
auto value = rwlock.read();
117+
REQUIRE(*value == 100);
118+
119+
auto value2 = rwlock.try_read();
120+
REQUIRE(value2.owns_lock());
121+
REQUIRE(*value2 == 100);
122+
}
123+
124+
SECTION("Try write while read") {
125+
auto value = rwlock.read();
126+
REQUIRE(*value == 100);
127+
128+
auto value2 = rwlock.try_write();
129+
REQUIRE(!value2.owns_lock());
130+
}
131+
132+
SECTION("Try read while write") {
133+
auto value = rwlock.write();
134+
REQUIRE(value.owns_lock());
135+
REQUIRE(*value == 100);
136+
137+
auto value2 = rwlock.try_read();
138+
REQUIRE(!value2.owns_lock());
139+
}
140+
```

0 commit comments

Comments
 (0)