std::atomic to what extent?

Ali Tavakol

In the C++ Seasoning video by Sean Parent https://youtu.be/W2tWOdzgXHA at 33:41 when starting to talk about “no raw synchronization primitives”, he brings an example to show that with raw synchronization primitives we will get it wrong. The example is a bad copy on write class:

template <typename T>
class bad_cow {
    struct object_t {
        explicit object_t(const T& x) : data_m(x) { ++count_m; }
        atomic<int> count_m;
        T data_m;
    };
    object_t* object_m;

public:
    explicit bad_cow(const T& x) : object_m(new object_t(x)) { }
    ~bad_cow() { if (0 == --object_m->count_m) delete object_m; }
    bad_cow(const bad_cow& x) : object_m(x.object_m) { ++object_m->count_m; }

    bad_cow& operator=(const T& x) {
        if (object_m->count_m == 1) {
            // label #2
            object_m->data_m = x; 
        } else {
            object_t* tmp = new object_t(x);
            --object_m->count_m; // bug #1
            // this solves bug #1:
            // if (0 == --object_m->count_m) delete object_m;
            object_m = tmp;
        }
        return *this;
    }
};

He then asks the audience to find the bug, which is the bug #1 as he confirms.

But a more obvious bug I guess, is when some thread is about to proceed to execute a line of code that I have denoted with label #2, while all of a sudden, some other thread just destroys the object and the destructor is called, which deletes object_m. So, the first thread will encounter a deleted memory location.

Am I right? I don’t seem so!

6502

Your objection doesn't hold because *this at that moment is pointing to the object and the count is 1. The counter cannot get to 0 unless someone is not playing this game correctly (but in that case anything can happen anyway).

Another similar objection could be that while you're assigning to *this and the code being executed is inside the #2 branch another thread makes a copy of *this; even if this second thread is just reading the pointed object may see it mutating suddenly because of the assignment. The problem in this case is that count was 1 when entering the if in the thread doing the mutation but increased immediately after.

This is also however a bad objection because this code handles concurrency to the pointed-to object (like for example std::shared_ptr does) but you are not allowed to mutate and read a single instance of bad_cow class from different threads. In other words a single instance of bad_cow cannot be used from multiple threads if some of them are writers without adding synchronization. Distinct instances of bad_cow pointing to the same storage are instead safe to be used from different threads (after the fix #1, of course).

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

What exactly is std::atomic?

What's the purpose of std::dynamic_extent in std::span

What's the default value for a std::atomic?

What's the closest thing to `std::atomic<std::vector>`?

To what extent is Haskell lazy?

What is the memory footprint of std::atomic in C++11, in practice?

What happened to std::atomic<X>::value_type?

What is the right std::atomic memory order for dynamic scheduling?

What are the exact requirements for MyType to allow std::atomic<MyType>?

What's the difference between T, volatile T, and std::atomic<T>?

Using std extent with a template array

what is the difference between using ATOMIC_FLAG_INIT and std::atomic_flag::clear

atomic read then write with std::atomic

What is the extent of the import statement in Python

What is the difference between std::shared_ptr and std::experimental::atomic_shared_ptr?

How is std::atomic implemented

Custom type in std::atomic

Where is the lock for a std::atomic?

Segfault in std::atomic load?

std::atomic as a value of std::map

get second dimension of the array using std::extent

Atomic operations, std::atomic<> and ordering of writes

Is assignment to a std::atomic<double> variable guaranteed to be atomic?

Indirect & Direct initialization of std::atomic in C++11/17. What are the differences?

What does std::atomic::is_always_lock_free = true really mean?

Deducing extent when passing std::array to function expecting std::span

What is this non numeric matrix extent error in R?

To what extent can a computer be reset to factory settings?

To what extent are Applicative/Monad instances uniquely determined?