// This program creates a number of threads and each thread adds numbers from 0 to nr_iterations-1 to a shared sum variable. // The main thread creates the worker threads, waits for them to finish, and then checks the result (should be nr_threads*(nr_iterations-1)*nr_iterations/2 ) // There are several classes derived from TestClass to demonstrate the available synchronization mechanisms and to allow comparing times: // TestWrong - no synchronization at all // TestMutex - uses a mutex protecting the shared sum // TestAtomic - uses an atomic integer for the sum and fetch_add() to add the number i // TestAtomicCompareExchange - also uses an atomic integer for the sum, but uses compare_exchange_weak() #include #include #include #include #include #include #include class TestClass { public: virtual ~TestClass() {} virtual void add(uint64_t iter) = 0; virtual uint64_t sum() const = 0; }; class TestWrong : public TestClass { public: void add(uint64_t iter) override { for (uint64_t i = 0; i < iter; ++i) { m_sum += i; } } uint64_t sum() const override { return m_sum; } private: uint64_t m_sum = 0; }; class TestAtomic : public TestClass { public: TestAtomic() :m_sum(0) { } void add(uint64_t iter) override { for (uint64_t i = 0; i < iter; ++i) { m_sum.fetch_add(i, std::memory_order::memory_order_relaxed); //m_sum.fetch_add(i, std::memory_order::memory_order_seq_cst); } } uint64_t sum() const override { return m_sum; } private: std::atomic m_sum; }; class TestAtomicCompareExchange : public TestClass { public: TestAtomicCompareExchange() :m_sum(0) { } void add(uint64_t iter) override { for (uint64_t i = 0; i < iter; ++i) { uint64_t val = m_sum.load(); while (true) { uint64_t newVal = val + i; if (m_sum.compare_exchange_weak(val, newVal)) break; } } } uint64_t sum() const override { return m_sum; } private: std::atomic m_sum; }; class TestMutex : public TestClass { public: void add(uint64_t iter) override { for (uint64_t i = 0; i < iter; ++i) { std::unique_lock lck(m_mutex); m_sum += i; } } uint64_t sum() const override { std::unique_lock lck(m_mutex); return m_sum; } private: mutable std::mutex m_mutex; uint64_t m_sum = 0; }; void executeTest(TestClass& testObj, uint64_t iter, uint32_t nrThreads) { std::vector threads; threads.reserve(nrThreads); std::chrono::system_clock::time_point startTime = std::chrono::system_clock::now(); for (uint32_t i = 0; i < nrThreads; ++i) { threads.emplace_back([&testObj, iter]() {testObj.add(iter); }); } for (uint32_t i = 0; i < nrThreads; ++i) { threads[i].join(); } std::chrono::system_clock::time_point stopTime = std::chrono::system_clock::now(); uint64_t expectedSum = (iter * (iter - 1) / 2) * nrThreads; uint64_t actualSum = testObj.sum(); std::cout << "Sum: expected=" << expectedSum << ", actual=" << actualSum << ((expectedSum == actualSum) ? " OK" : " wrong") << "\n"; std::cout << "Real time=" << std::chrono::duration_cast(stopTime - startTime).count() << "us\n"; } int main() { size_t total_ops = 10000000; unsigned nr_threads = 16; TestAtomicCompareExchange test; executeTest(test, total_ops/nr_threads, nr_threads); }