├── .appveyor.yml
├── .clang-format
├── .travis.yml
├── CMakeLists.txt
├── LICENSE
├── README.md
├── include
└── atomic
│ ├── atomic.h
│ ├── atomic_msvc.h
│ └── spinlock.h
└── test
├── CMakeLists.txt
├── atomic_test.cpp
└── doctest
├── CMakeLists.txt
├── LICENSE.txt
├── doctest_main.cpp
└── include
└── doctest.h
/.appveyor.yml:
--------------------------------------------------------------------------------
1 | build_script:
2 | - mkdir build
3 | - cd build
4 | - cmake ..
5 | - cmake --build . --config Release
6 | test_script:
7 | - ctest
8 |
9 |
--------------------------------------------------------------------------------
/.clang-format:
--------------------------------------------------------------------------------
1 | ---
2 | Language: Cpp
3 | BasedOnStyle: Chromium
4 | AccessModifierOffset: -2
5 | BinPackArguments: false
6 | BinPackParameters: false
7 | ColumnLimit: 80
8 | IncludeCategories:
9 | - Regex: '^<.*\.h>'
10 | Priority: 3
11 | - Regex: '^<.*'
12 | Priority: 2
13 | - Regex: '.*'
14 | Priority: 1
15 | SortIncludes: true
16 | ---
17 |
18 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: trusty
2 | sudo: false
3 | language: cpp
4 | compiler:
5 | - clang
6 | - gcc
7 | os:
8 | - linux
9 | - osx
10 | script:
11 | - rm -rf build && mkdir -p build
12 | - cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && cmake --build .
13 | - ctest
14 |
15 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.2)
2 |
3 | project(atomic CXX)
4 |
5 | # Optionally enable C++11 (we use C++11 as a fallback for some operations).
6 | set(CMAKE_CXX_STANDARD 11)
7 |
8 | # Work around a bug in CMake 3.2 find_package(Threads).
9 | if(${CMAKE_VERSION} VERSION_LESS "3.5.1")
10 | enable_language(C)
11 | endif()
12 |
13 | # Add the atomic library.
14 | add_library(atomic INTERFACE)
15 | target_sources(atomic INTERFACE
16 | ${CMAKE_CURRENT_SOURCE_DIR}/include/atomic/atomic.h
17 | ${CMAKE_CURRENT_SOURCE_DIR}/include/atomic/spinlock.h
18 | )
19 | target_include_directories(atomic INTERFACE include)
20 |
21 | # Add the unit tests.
22 | enable_testing()
23 | add_subdirectory(test)
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## ⚠️ This repository has moved to: https://gitlab.com/mbitsnbites/atomic
2 |
3 | # Atomic operations for C++
4 |
5 | This is a very lightweight library that implements thread safe, atomic
6 | operations for C++. It is portable and easy to use.
7 |
8 |
9 | ## CI status
10 | OS | master branch
11 | --- | ---
12 | Linux & macOS (GCC, Clang) | [](https://travis-ci.org/mbitsnbites/atomic)
13 | Windows (MSVC) | [](https://ci.appveyor.com/project/mbitsnbites/atomic/branch/master)
14 |
15 |
16 | ## Why avoid std::atomic?
17 |
18 | Because `` drags in loads of code in the pre-processing step (over 1MB
19 | of code for MSVC 2015). This library, which is based on compiler intrinsics
20 | (and a fallback to std::atomic), is much lighter.
21 |
22 | Another reason may be that you can not (for whatever reason) use C++11 in your
23 | project, but still need portable atomic operations.
24 |
25 |
26 | ## Example code
27 |
28 | ### Spinlock with an automatic lock guard
29 |
30 | ```c++
31 | #include "atomic/spinlock.h"
32 |
33 | static atomic::spinlock lock;
34 |
35 | void foo() {
36 | atomic::lock_guard guard(lock);
37 |
38 | // Stuff that is synchronized by the lock...
39 | }
40 | ```
41 |
42 | This is the generated machine code (clang 3.8, x86_64):
43 |
44 | ```assembly
45 | foo:
46 | movl $1, %ecx
47 | .spin:
48 | xorl %eax, %eax
49 | lock cmpxchgl %ecx, lock(%rip)
50 | jne .spin
51 |
52 | // Stuff that is synchronized by the lock...
53 |
54 | xorl %eax, %eax
55 | xchgl %eax, lock(%rip)
56 | retq
57 | ```
58 |
59 | ## License
60 |
61 | This is free and unencumbered software released into the public domain.
62 |
63 | For more info, see [LICENSE](LICENSE).
64 |
65 |
--------------------------------------------------------------------------------
/include/atomic/atomic.h:
--------------------------------------------------------------------------------
1 | //----------------------------------------------------------------------------
2 | // This is free and unencumbered software released into the public domain.
3 | //
4 | // Anyone is free to copy, modify, publish, use, compile, sell, or distribute
5 | // this software, either in source code form or as a compiled binary, for any
6 | // purpose, commercial or non-commercial, and by any means.
7 | //
8 | // In jurisdictions that recognize copyright laws, the author or authors of
9 | // this software dedicate any and all copyright interest in the software to the
10 | // public domain. We make this dedication for the benefit of the public at
11 | // large and to the detriment of our heirs and successors. We intend this
12 | // dedication to be an overt act of relinquishment in perpetuity of all present
13 | // and future rights to this software under copyright law.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19 | // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 | //
22 | // For more information, please refer to
23 | //-----------------------------------------------------------------------------
24 |
25 | #ifndef ATOMIC_ATOMIC_H_
26 | #define ATOMIC_ATOMIC_H_
27 |
28 | // Macro for disallowing copying of an object.
29 | #if __cplusplus >= 201103L
30 | #define ATOMIC_DISALLOW_COPY(T) \
31 | T(const T&) = delete; \
32 | T& operator=(const T&) = delete;
33 | #else
34 | #define ATOMIC_DISALLOW_COPY(T) \
35 | T(const T&); \
36 | T& operator=(const T&);
37 | #endif
38 |
39 | // A portable static assert.
40 | #if __cplusplus >= 201103L
41 | #define ATOMIC_STATIC_ASSERT(condition, message) \
42 | static_assert((condition), message)
43 | #else
44 | // Based on: http://stackoverflow.com/a/809465/5778708
45 | #define ATOMIC_STATIC_ASSERT(condition, message) \
46 | _impl_STATIC_ASSERT_LINE(condition, __LINE__)
47 | #define _impl_PASTE(a, b) a##b
48 | #ifdef __GNUC__
49 | #define _impl_UNUSED __attribute__((__unused__))
50 | #else
51 | #define _impl_UNUSED
52 | #endif
53 | #define _impl_STATIC_ASSERT_LINE(condition, line) \
54 | typedef char _impl_PASTE( \
55 | STATIC_ASSERT_failed_, \
56 | line)[(2 * static_cast(!!(condition))) - 1] _impl_UNUSED
57 | #endif
58 |
59 | #if defined(__GNUC__) || defined(__clang__) || defined(__xlc__)
60 | #define ATOMIC_USE_GCC_INTRINSICS
61 | #elif defined(_MSC_VER)
62 | #define ATOMIC_USE_MSVC_INTRINSICS
63 | #include "atomic_msvc.h"
64 | #elif __cplusplus >= 201103L
65 | #define ATOMIC_USE_CPP11_ATOMIC
66 | #include
67 | #else
68 | #error Unsupported compiler / system.
69 | #endif
70 |
71 | namespace atomic {
72 | template
73 | class atomic {
74 | public:
75 | ATOMIC_STATIC_ASSERT(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 ||
76 | sizeof(T) == 8,
77 | "Only types of size 1, 2, 4 or 8 are supported");
78 |
79 | atomic() : value_(static_cast(0)) {}
80 |
81 | explicit atomic(const T value) : value_(value) {}
82 |
83 | /// @brief Performs an atomic increment operation (value + 1).
84 | /// @returns The new value of the atomic object.
85 | T operator++() {
86 | #if defined(ATOMIC_USE_GCC_INTRINSICS)
87 | return __atomic_add_fetch(&value_, 1, __ATOMIC_SEQ_CST);
88 | #elif defined(ATOMIC_USE_MSVC_INTRINSICS)
89 | return msvc::interlocked::increment(&value_);
90 | #else
91 | return ++value_;
92 | #endif
93 | }
94 |
95 | /// @brief Performs an atomic decrement operation (value - 1).
96 | /// @returns The new value of the atomic object.
97 | T operator--() {
98 | #if defined(ATOMIC_USE_GCC_INTRINSICS)
99 | return __atomic_sub_fetch(&value_, 1, __ATOMIC_SEQ_CST);
100 | #elif defined(ATOMIC_USE_MSVC_INTRINSICS)
101 | return msvc::interlocked::decrement(&value_);
102 | #else
103 | return --value_;
104 | #endif
105 | }
106 |
107 | /// @brief Performs an atomic compare-and-swap (CAS) operation.
108 | ///
109 | /// The value of the atomic object is only updated to the new value if the
110 | /// old value of the atomic object matches @c expected_val.
111 | ///
112 | /// @param expected_val The expected value of the atomic object.
113 | /// @param new_val The new value to write to the atomic object.
114 | /// @returns True if new_value was written to the atomic object.
115 | bool compare_exchange(const T expected_val, const T new_val) {
116 | #if defined(ATOMIC_USE_GCC_INTRINSICS)
117 | T e = expected_val;
118 | return __atomic_compare_exchange_n(
119 | &value_, &e, new_val, true, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
120 | #elif defined(ATOMIC_USE_MSVC_INTRINSICS)
121 | const T old_val =
122 | msvc::interlocked::compare_exchange(&value_, new_val, expected_val);
123 | return (old_val == expected_val);
124 | #else
125 | T e = expected_val;
126 | return value_.compare_exchange_weak(e, new_val);
127 | #endif
128 | }
129 |
130 | /// @brief Performs an atomic set operation.
131 | ///
132 | /// The value of the atomic object is unconditionally updated to the new
133 | /// value.
134 | ///
135 | /// @param new_val The new value to write to the atomic object.
136 | void store(const T new_val) {
137 | #if defined(ATOMIC_USE_GCC_INTRINSICS)
138 | __atomic_store_n(&value_, new_val, __ATOMIC_SEQ_CST);
139 | #elif defined(ATOMIC_USE_MSVC_INTRINSICS)
140 | (void)msvc::interlocked::exchange(&value_, new_val);
141 | #else
142 | value_.store(new_val);
143 | #endif
144 | }
145 |
146 | /// @returns the current value of the atomic object.
147 | /// @note Be careful about how this is used, since any operations on the
148 | /// returned value are inherently non-atomic.
149 | T load() const {
150 | #if defined(ATOMIC_USE_GCC_INTRINSICS)
151 | return __atomic_load_n(&value_, __ATOMIC_SEQ_CST);
152 | #elif defined(ATOMIC_USE_MSVC_INTRINSICS)
153 | // TODO(m): Is there a better solution for MSVC?
154 | return value_;
155 | #else
156 | return value_;
157 | #endif
158 | }
159 |
160 | /// @brief Performs an atomic exchange operation.
161 | ///
162 | /// The value of the atomic object is unconditionally updated to the new
163 | /// value, and the old value is returned.
164 | ///
165 | /// @param new_val The new value to write to the atomic object.
166 | /// @returns the old value.
167 | T exchange(const T new_val) {
168 | #if defined(ATOMIC_USE_GCC_INTRINSICS)
169 | return __atomic_exchange_n(&value_, new_val, __ATOMIC_SEQ_CST);
170 | #elif defined(ATOMIC_USE_MSVC_INTRINSICS)
171 | return msvc::interlocked::exchange(&value_, new_val);
172 | #else
173 | return value_.exchange(new_val);
174 | #endif
175 | }
176 |
177 | T operator=(const T new_value) {
178 | store(new_value);
179 | return new_value;
180 | }
181 |
182 | operator T() const {
183 | return load();
184 | }
185 |
186 | private:
187 | #if defined(ATOMIC_USE_GCC_INTRINSICS) || defined(ATOMIC_USE_MSVC_INTRINSICS)
188 | volatile T value_;
189 | #else
190 | std::atomic value_;
191 | #endif
192 |
193 | ATOMIC_DISALLOW_COPY(atomic)
194 | };
195 |
196 | } // namespace atomic
197 |
198 | // Undef temporary defines.
199 | #undef ATOMIC_USE_GCC_INTRINSICS
200 | #undef ATOMIC_USE_MSVC_INTRINSICS
201 | #undef ATOMIC_USE_CPP11_ATOMIC
202 |
203 | #endif // ATOMIC_ATOMIC_H_
204 |
--------------------------------------------------------------------------------
/include/atomic/atomic_msvc.h:
--------------------------------------------------------------------------------
1 | //-----------------------------------------------------------------------------
2 | // This is free and unencumbered software released into the public domain.
3 | //
4 | // Anyone is free to copy, modify, publish, use, compile, sell, or distribute
5 | // this software, either in source code form or as a compiled binary, for any
6 | // purpose, commercial or non-commercial, and by any means.
7 | //
8 | // In jurisdictions that recognize copyright laws, the author or authors of
9 | // this software dedicate any and all copyright interest in the software to the
10 | // public domain. We make this dedication for the benefit of the public at
11 | // large and to the detriment of our heirs and successors. We intend this
12 | // dedication to be an overt act of relinquishment in perpetuity of all present
13 | // and future rights to this software under copyright law.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19 | // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 | //
22 | // For more information, please refer to
23 | //-----------------------------------------------------------------------------
24 |
25 | #ifndef ATOMIC_ATOMIC_MSVC_H_
26 | #define ATOMIC_ATOMIC_MSVC_H_
27 |
28 | // Define which functions we need (don't include ).
29 | extern "C" {
30 | short _InterlockedIncrement16(short volatile*);
31 | long _InterlockedIncrement(long volatile*);
32 | __int64 _InterlockedIncrement64(__int64 volatile*);
33 |
34 | short _InterlockedDecrement16(short volatile*);
35 | long _InterlockedDecrement(long volatile*);
36 | __int64 _InterlockedDecrement64(__int64 volatile*);
37 |
38 | char _InterlockedExchange8(char volatile*, char);
39 | short _InterlockedExchange16(short volatile*, short);
40 | long __cdecl _InterlockedExchange(long volatile*, long);
41 | __int64 _InterlockedExchange64(__int64 volatile*, __int64);
42 |
43 | char _InterlockedCompareExchange8(char volatile*, char, char);
44 | short _InterlockedCompareExchange16(short volatile*, short, short);
45 | long __cdecl _InterlockedCompareExchange(long volatile*, long, long);
46 | __int64 _InterlockedCompareExchange64(__int64 volatile*, __int64, __int64);
47 | };
48 |
49 | // Define which functions we want to use as inline intriniscs.
50 | #pragma intrinsic(_InterlockedIncrement)
51 | #pragma intrinsic(_InterlockedIncrement16)
52 |
53 | #pragma intrinsic(_InterlockedDecrement)
54 | #pragma intrinsic(_InterlockedDecrement16)
55 |
56 | #pragma intrinsic(_InterlockedCompareExchange)
57 | #pragma intrinsic(_InterlockedCompareExchange8)
58 | #pragma intrinsic(_InterlockedCompareExchange16)
59 |
60 | #pragma intrinsic(_InterlockedExchange)
61 | #pragma intrinsic(_InterlockedExchange8)
62 | #pragma intrinsic(_InterlockedExchange16)
63 |
64 | #if defined(_M_X64)
65 | #pragma intrinsic(_InterlockedIncrement64)
66 | #pragma intrinsic(_InterlockedDecrement64)
67 | #pragma intrinsic(_InterlockedCompareExchange64)
68 | #pragma intrinsic(_InterlockedExchange64)
69 | #endif // _M_X64
70 |
71 | namespace atomic {
72 | namespace msvc {
73 | template
74 | struct interlocked {
75 | };
76 |
77 | template
78 | struct interlocked {
79 | static inline T increment(T volatile* x) {
80 | // There's no _InterlockedIncrement8().
81 | char old_val, new_val;
82 | do {
83 | old_val = static_cast(*x);
84 | new_val = old_val + static_cast(1);
85 | } while (_InterlockedCompareExchange8(reinterpret_cast(x),
86 | new_val,
87 | old_val) != old_val);
88 | return static_cast(new_val);
89 | }
90 |
91 | static inline T decrement(T volatile* x) {
92 | // There's no _InterlockedDecrement8().
93 | char old_val, new_val;
94 | do {
95 | old_val = static_cast(*x);
96 | new_val = old_val - static_cast(1);
97 | } while (_InterlockedCompareExchange8(reinterpret_cast(x),
98 | new_val,
99 | old_val) != old_val);
100 | return static_cast(new_val);
101 | }
102 |
103 | static inline T compare_exchange(T volatile* x,
104 | const T new_val,
105 | const T expected_val) {
106 | return static_cast(
107 | _InterlockedCompareExchange8(reinterpret_cast(x),
108 | static_cast(new_val),
109 | static_cast(expected_val)));
110 | }
111 |
112 | static inline T exchange(T volatile* x, const T new_val) {
113 | return static_cast(_InterlockedExchange8(
114 | reinterpret_cast(x), static_cast(new_val)));
115 | }
116 | };
117 |
118 | template
119 | struct interlocked {
120 | static inline T increment(T volatile* x) {
121 | return static_cast(
122 | _InterlockedIncrement16(reinterpret_cast(x)));
123 | }
124 |
125 | static inline T decrement(T volatile* x) {
126 | return static_cast(
127 | _InterlockedDecrement16(reinterpret_cast(x)));
128 | }
129 |
130 | static inline T compare_exchange(T volatile* x,
131 | const T new_val,
132 | const T expected_val) {
133 | return static_cast(
134 | _InterlockedCompareExchange16(reinterpret_cast(x),
135 | static_cast(new_val),
136 | static_cast(expected_val)));
137 | }
138 |
139 | static inline T exchange(T volatile* x, const T new_val) {
140 | return static_cast(
141 | _InterlockedExchange16(reinterpret_cast(x),
142 | static_cast(new_val)));
143 | }
144 | };
145 |
146 | template
147 | struct interlocked {
148 | static inline T increment(T volatile* x) {
149 | return static_cast(
150 | _InterlockedIncrement(reinterpret_cast(x)));
151 | }
152 |
153 | static inline T decrement(T volatile* x) {
154 | return static_cast(
155 | _InterlockedDecrement(reinterpret_cast(x)));
156 | }
157 |
158 | static inline T compare_exchange(T volatile* x,
159 | const T new_val,
160 | const T expected_val) {
161 | return static_cast(
162 | _InterlockedCompareExchange(reinterpret_cast(x),
163 | static_cast(new_val),
164 | static_cast(expected_val)));
165 | }
166 |
167 | static inline T exchange(T volatile* x, const T new_val) {
168 | return static_cast(_InterlockedExchange(
169 | reinterpret_cast(x), static_cast(new_val)));
170 | }
171 | };
172 |
173 | template
174 | struct interlocked {
175 | static inline T increment(T volatile* x) {
176 | #if defined(_M_X64)
177 | return static_cast(
178 | _InterlockedIncrement64(reinterpret_cast(x)));
179 | #else
180 | // There's no _InterlockedIncrement64() for 32-bit x86.
181 | __int64 old_val, new_val;
182 | do {
183 | old_val = static_cast<__int64>(*x);
184 | new_val = old_val + static_cast<__int64>(1);
185 | } while (_InterlockedCompareExchange64(
186 | reinterpret_cast(x), new_val, old_val) !=
187 | old_val);
188 | return static_cast(new_val);
189 | #endif // _M_X64
190 | }
191 |
192 | static inline T decrement(T volatile* x) {
193 | #if defined(_M_X64)
194 | return static_cast(
195 | _InterlockedDecrement64(reinterpret_cast(x)));
196 | #else
197 | // There's no _InterlockedDecrement64() for 32-bit x86.
198 | __int64 old_val, new_val;
199 | do {
200 | old_val = static_cast<__int64>(*x);
201 | new_val = old_val - static_cast<__int64>(1);
202 | } while (_InterlockedCompareExchange64(
203 | reinterpret_cast(x), new_val, old_val) !=
204 | old_val);
205 | return static_cast(new_val);
206 | #endif // _M_X64
207 | }
208 |
209 | static inline T compare_exchange(T volatile* x,
210 | const T new_val,
211 | const T expected_val) {
212 | return static_cast(_InterlockedCompareExchange64(
213 | reinterpret_cast(x),
214 | static_cast(new_val),
215 | static_cast(expected_val)));
216 | }
217 |
218 | static inline T exchange(T volatile* x, const T new_val) {
219 | #if defined(_M_X64)
220 | return static_cast(
221 | _InterlockedExchange64(reinterpret_cast(x),
222 | static_cast(new_val)));
223 | #else
224 | // There's no _InterlockedExchange64 for 32-bit x86.
225 | __int64 old_val;
226 | do {
227 | old_val = static_cast<__int64>(*x);
228 | } while (_InterlockedCompareExchange64(
229 | reinterpret_cast(x), new_val, old_val) !=
230 | old_val);
231 | return static_cast(old_val);
232 | #endif // _M_X64
233 | }
234 | };
235 | } // namespace msvc
236 | } // namespace atomic
237 |
238 | #endif // ATOMIC_ATOMIC_MSVC_H_
239 |
--------------------------------------------------------------------------------
/include/atomic/spinlock.h:
--------------------------------------------------------------------------------
1 | //-----------------------------------------------------------------------------
2 | // This is free and unencumbered software released into the public domain.
3 | //
4 | // Anyone is free to copy, modify, publish, use, compile, sell, or distribute
5 | // this software, either in source code form or as a compiled binary, for any
6 | // purpose, commercial or non-commercial, and by any means.
7 | //
8 | // In jurisdictions that recognize copyright laws, the author or authors of
9 | // this software dedicate any and all copyright interest in the software to the
10 | // public domain. We make this dedication for the benefit of the public at
11 | // large and to the detriment of our heirs and successors. We intend this
12 | // dedication to be an overt act of relinquishment in perpetuity of all present
13 | // and future rights to this software under copyright law.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19 | // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 | //
22 | // For more information, please refer to
23 | //-----------------------------------------------------------------------------
24 |
25 | #ifndef ATOMIC_SPINLOCK_H_
26 | #define ATOMIC_SPINLOCK_H_
27 |
28 | #include "atomic/atomic.h"
29 |
30 | namespace atomic {
31 | class spinlock {
32 | public:
33 | spinlock() : value_(0) {}
34 |
35 | /// @brief Acquire the lock (blocking).
36 | /// @note Trying to acquire a lock that is already held by the calling thread
37 | /// will dead-lock (block indefinitely).
38 | void lock() {
39 | while (!value_.compare_exchange(UNLOCKED, LOCKED))
40 | ;
41 | }
42 |
43 | /// @brief Release the lock.
44 | /// @note It is an error to release a lock that has not been previously
45 | /// acquired.
46 | void unlock() { value_.store(UNLOCKED); }
47 |
48 | private:
49 | static const int UNLOCKED = 0;
50 | static const int LOCKED = 1;
51 |
52 | atomic value_;
53 |
54 | ATOMIC_DISALLOW_COPY(spinlock)
55 | };
56 |
57 | class lock_guard {
58 | public:
59 | /// @brief The constructor acquires the lock.
60 | /// @param lock The spinlock that will be locked.
61 | explicit lock_guard(spinlock& lock) : lock_(lock) {
62 | lock_.lock();
63 | }
64 |
65 | /// @brief The destructor releases the lock.
66 | ~lock_guard() {
67 | lock_.unlock();
68 | }
69 |
70 | private:
71 | spinlock& lock_;
72 |
73 | ATOMIC_DISALLOW_COPY(lock_guard)
74 | };
75 |
76 | } // namespace atomic
77 |
78 | #endif // ATOMIC_SPINLOCK_H_
79 |
--------------------------------------------------------------------------------
/test/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # We use doctest.
2 | add_subdirectory(doctest)
3 |
4 | # We require C++11 for the tests (to be able to create threads etc).
5 | set(CMAKE_CXX_STANDARD 11)
6 | set(CMAKE_CXX_STANDARD_REQUIRED ON)
7 | set(CMAKE_CXX_EXTENSIONS OFF)
8 |
9 | # We also need the platform threads package.
10 | find_package(Threads REQUIRED)
11 |
12 | # Add the unit test executable.
13 | add_executable(atomic_test atomic_test.cpp)
14 | target_link_libraries(atomic_test atomic doctest ${CMAKE_THREAD_LIBS_INIT})
15 | add_test(atomic_test atomic_test)
16 |
--------------------------------------------------------------------------------
/test/atomic_test.cpp:
--------------------------------------------------------------------------------
1 | #include "atomic/atomic.h"
2 | #include "atomic/spinlock.h"
3 |
4 | #include "doctest.h"
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | typedef atomic::atomic atomic_int;
11 |
12 | typedef doctest::Types atomic_test_types;
13 |
14 | TEST_CASE_TEMPLATE("Basic atomic single threaded operation",
15 | T,
16 | atomic_test_types) {
17 | SUBCASE("atomic<> initializes to zero") {
18 | atomic::atomic a;
19 | CHECK(a.load() == static_cast(0));
20 | }
21 |
22 | SUBCASE("atomic<> initializes to custom value") {
23 | atomic::atomic a(static_cast(42));
24 | CHECK(a.load() == static_cast(42));
25 | }
26 |
27 | SUBCASE("Incrementing atomic<> works as expected") {
28 | atomic::atomic a(static_cast(0));
29 | ++a;
30 | ++a;
31 | ++a;
32 | CHECK(a.load() == static_cast(3));
33 | }
34 |
35 | SUBCASE("Decrementing atomic<> works as expected") {
36 | atomic::atomic a(5);
37 | --a;
38 | --a;
39 | --a;
40 | CHECK(a.load() == static_cast(2));
41 | }
42 |
43 | SUBCASE("compare_exchange with non-expected value is a no-op") {
44 | atomic::atomic a(static_cast(5));
45 | const bool did_swap =
46 | a.compare_exchange(static_cast(4), static_cast(9));
47 | CHECK(did_swap == false);
48 | CHECK(a.load() == static_cast(5));
49 | }
50 |
51 | SUBCASE("compare_exchange with expected value performs swap") {
52 | atomic::atomic a(static_cast(5));
53 | const bool did_swap =
54 | a.compare_exchange(static_cast(5), static_cast(9));
55 | CHECK(did_swap == true);
56 | CHECK(a.load() == static_cast(9));
57 | }
58 |
59 | SUBCASE("exchange updates and returns the old value") {
60 | atomic::atomic a(static_cast(5));
61 | const T old_value = a.exchange(static_cast(9));
62 | CHECK(old_value == static_cast(5));
63 | CHECK(a.load() == static_cast(9));
64 | }
65 | }
66 |
67 | TEST_CASE("atomic multi threaded operation") {
68 | SUBCASE("atomic increments correctly with 100 threads") {
69 | atomic_int a;
70 |
71 | const int NUM_THREADS = 100;
72 | const int NUM_ITERATIONS = 1000;
73 | std::vector threads;
74 | for (int i = 0; i < NUM_THREADS; i++) {
75 | threads.push_back(std::thread([&a, &NUM_ITERATIONS]() {
76 | for (int k = 0; k < NUM_ITERATIONS; ++k) {
77 | ++a;
78 | }
79 | }));
80 | }
81 | for (int i = 0; i < NUM_THREADS; i++) {
82 | threads[i].join();
83 | }
84 |
85 | CHECK(a.load() == (NUM_THREADS * NUM_ITERATIONS));
86 | }
87 |
88 | SUBCASE("atomic decrements correctly with 100 threads") {
89 | atomic_int a;
90 |
91 | const int NUM_THREADS = 100;
92 | const int NUM_ITERATIONS = 1000;
93 | std::vector threads;
94 | for (int i = 0; i < NUM_THREADS; i++) {
95 | threads.push_back(std::thread([&a, &NUM_ITERATIONS]() {
96 | for (int k = 0; k < NUM_ITERATIONS; ++k) {
97 | --a;
98 | }
99 | }));
100 | }
101 | for (int i = 0; i < NUM_THREADS; i++) {
102 | threads[i].join();
103 | }
104 |
105 | CHECK(a.load() == -(NUM_THREADS * NUM_ITERATIONS));
106 | }
107 |
108 | SUBCASE("spinlock with 100 threads") {
109 | atomic::spinlock lock;
110 | int unsafe_value = 0;
111 |
112 | const int NUM_THREADS = 100;
113 | const int NUM_ITERATIONS = 1000;
114 | std::vector threads;
115 | for (int i = 0; i < NUM_THREADS; i++) {
116 | threads.push_back(std::thread([&lock, &unsafe_value, &NUM_ITERATIONS]() {
117 | for (int k = 0; k < NUM_ITERATIONS; ++k) {
118 | // Acquire (blocking).
119 | lock.lock();
120 |
121 | // Update the unsafe value (now protected by our acquired lock).
122 | ++unsafe_value;
123 |
124 | // Release.
125 | lock.unlock();
126 | }
127 | }));
128 | }
129 | for (int i = 0; i < NUM_THREADS; i++) {
130 | threads[i].join();
131 | }
132 |
133 | CHECK(unsafe_value == (NUM_THREADS * NUM_ITERATIONS));
134 | }
135 |
136 | SUBCASE("lock_guard with 100 threads") {
137 | atomic::spinlock lock;
138 | int unsafe_value = 0;
139 |
140 | const int NUM_THREADS = 100;
141 | const int NUM_ITERATIONS = 1000;
142 | std::vector threads;
143 | for (int i = 0; i < NUM_THREADS; i++) {
144 | threads.push_back(std::thread([&lock, &unsafe_value, &NUM_ITERATIONS]() {
145 | for (int k = 0; k < NUM_ITERATIONS; ++k) {
146 | atomic::lock_guard guard(lock);
147 |
148 | // Update the unsafe value (now protected by our acquired lock).
149 | ++unsafe_value;
150 | }
151 | }));
152 | }
153 | for (int i = 0; i < NUM_THREADS; i++) {
154 | threads[i].join();
155 | }
156 |
157 | CHECK(unsafe_value == (NUM_THREADS * NUM_ITERATIONS));
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/test/doctest/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | add_library(doctest
2 | include/doctest.h
3 | doctest_main.cpp
4 | )
5 | target_include_directories(doctest PUBLIC include)
6 |
7 |
--------------------------------------------------------------------------------
/test/doctest/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016-2017 Viktor Kirilov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/test/doctest/doctest_main.cpp:
--------------------------------------------------------------------------------
1 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
2 | #include "doctest.h"
3 |
--------------------------------------------------------------------------------