Volatile v/s Synchronization Mechanisms

Volatile v/s Synchronization Mechanisms

Premises: Volatile is not for Thread Synchronization. Contrary to some beliefs, volatile does not provide atomicity or memory ordering guarantees. It is not suitable for thread synchronization. For such purposes, you should use other mechanisms like std::atomic.

In the code below marking counter as volatile ensures that each increment operation reads and writes the value directly to and from memory, avoiding potential optimizations that could lead to incorrect results.

#include <iostream>
#include <thread>
#include <chrono>

volatile int counter = 0;

void incrementCounter()
{
    for (int i = 0; i < 1000; ++i)
    {
        ++counter; // Increment the counter
        std::this_thread::sleep_for(std::chrono::microseconds(1)); // Simulate some work
    }
}

int main()
{
    std::thread t1(incrementCounter);
    std::thread t2(incrementCounter);

    t1.join();
    t2.join();

    std::cout << "Final counter value: " << counter << std::endl;

    return 0;
}        

Output of this is not always 2000 as one thread writes back before another threads get's time write. While the volatile keyword helps in ensuring the correct reading and writing of variables that can be changed externally or asynchronously, it does not provide atomicity or synchronization between threads. In practice, for thread-safe operations, you would typically use proper synchronization mechanisms such as mutexes or atomic variables. For example, using std::atomic<int> from the <atomic> header would be more appropriate for thread-safe increment operations:

#include <iostream>
#include <thread>
#include <atomic>
#include <chrono>

std::atomic<int> counter(0);

void incrementCounter()
{
    for (int i = 0; i < 1000; ++i)
    {
        ++counter; // Atomic increment
        std::this_thread::sleep_for(std::chrono::microseconds(1)); // Simulate some work
    }
}

int main()
{
    std::thread t1(incrementCounter);
    std::thread t2(incrementCounter);

    t1.join();
    t2.join();

    std::cout << "Final counter value: " << counter.load() << std::endl;

    return 0;
}        

Tricky Question Time! ??

Now, let’s see if you can crack this one:

#include <iostream>
#include <atomic>
#include <thread>

std::atomic<int> value(0);

void increment()
{
    for (int i = 0; i < 1000; ++i)
    {
        ++value; // Increment the atomic variable
    }
}

void decrement()
{
    for (int i = 0; i < 1000; ++i)
    {
        --value; // Decrement the atomic variable
    }
}

int main()
{
    std::thread t1(increment);
    std::thread t2(decrement);

    t1.join();
    t2.join();

    std::cout << "Final value: " << value << std::endl;

    return 0;
}        

What will be the output of this program?

Options:

A. Final value: 0

B. Final value: 1000

C. Final value: -1000

D. Final value: Undefined

If you want to explore more about std::atomic here are some resource

CppCon 2017: Fedor Pikus “C++ atomics, from basic to advanced. What do they really do?”

Nikhil Kumar

Software Engineer , Deevia

2 个月

Such an Insight full article We need insight like this ????

Ajit Sakri

Embedded SWE at Deevia Software India Private Limited

2 个月

Note: Some uses of volatile have been deprecated in C++20. For example, volatile compound assignments and pre/post increment/decrement operations are deprecated. This change aims to remove ambiguities and potential issues in code.

要查看或添加评论,请登录

社区洞察

其他会员也浏览了