├── .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) | [![Build status](https://travis-ci.org/mbitsnbites/atomic.svg?branch=master)](https://travis-ci.org/mbitsnbites/atomic) 13 | Windows (MSVC) | [![Build status](https://ci.appveyor.com/api/projects/status/w502ka3k674wn25f?svg=true)](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 | --------------------------------------------------------------------------------