Home > Blockchain >  Can atomic read operations lead to deadlocks?
Can atomic read operations lead to deadlocks?

Time:12-08

I am watching this Herb Sutter's talk about atomics, mutexes and memory barriers, and I have a question regarding it. Since 47:33 Herb explains how mutexes and atomics are related to memory ordering. On 49:12 he says, that with the default memory order, which is memory_order_seq_cst, atomic load() operation is equivalent of locking a mutex, and atomic store operation is equivalent of unlocking a mutex. On 53:15 Herb was asked what would happen if the atomic load() operation in his example was not followed by subsequent store() operation, and his answer really confused me:

54:00 - If you don't write this release(mutex.unlock()) or this one(atomic.store()), that person will never get a chance to run...

Maybe I completely misunderstood him due to my not very good English skills, but this is how I interpret his words:

If a thread A reads an atomic variable without subsequent write into it, no other thread will be able to work with this variable due to it being deadlocked by the thread A like as if a mutex was locked by this thread without further unlocking.

But is it really what he meant? It doesn't seem to be true. In my understanding release happens automatically right after an atomic variable was either loaded or stored. Just an example:

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

std::atomic<int> number{0};

void foo()
{
    while (number != 104) {}
    std::cout << "Number:\t" << number << '\n';
}

int main()
{
    std::thread thr1(foo);
    std::thread thr2(foo);
    std::thread thr3(foo);
    std::thread thr4(foo);
    number = 104;
    thr1.join();
    thr2.join();
    thr3.join();
    thr4.join();
}

In the above example there are 4 threads successfully reading the same atomic variable, and no any writes are needed in these threads to release the variable for other threads. Apparently, atomic.load() != mutex.lock() as well as atomic.store() != mutex.unlock(). They may behave the same way in terms of memory barriers, but they are not the same, aren't they?

Could you please explain to me, what did Herb actually mean by saying they are equal?

CodePudding user response:

There's a misunderstanding here. An atomic read, regardless of memory order, is not "equivalent to locking a mutex". In terms of visibility it might have the same effects, but a mutex is much heavier.

Here's a typical problem with a mutex:

std::mutex mtx1;
std::mutex mtx2;

void thr1() {
    mtx1.lock();
    mtx2.lock();
    mtx2.unlock();
    mtx1.unlock();
}

void thr2() {
    mtx2.lock();
    mtx1.lock();
    mtx1.unlock();
    mtx2.unlock();
}

Note that the two function lock the two mutexes in the opposite order. So it's possible that thr1 locks mtx1, then thr2 locks mtx2, then thr1 tries to lock mtx2 and thr2 tries to lock mtx1. That's a deadlock; neither thread can make progress, because the resource it needs is held by the other thread.

Atomics don't have that problem, because you can't run code "inside" an atomic access. You can't get that sort of resource conflict.

The issue that seems to underly that discussion is the possibility that the thread running while (number != 104) {} won't see the updated value of number, and so the code will be an infinite loop. That's not a deadlock. Which isn't to say that it's not a problem, but the problem is with visibility.

  • Related