Lock-free swap of two unique_ptr

后端 未结 3 1067
梦毁少年i
梦毁少年i 2021-01-30 16:20

Swapping two unique_ptrs is not guaranteed to be threadsafe.

std::unique_ptr a, b;
std::swap(a, b); // not threadsafe

Sin

3条回答
  •  半阙折子戏
    2021-01-30 17:20

    The idiomatic way to modify two variables atomically is to use a lock.

    You can't do it for std::unique_ptr without a lock. Even std::atomic doesn't provide a way to swap two values atomically. You can update one atomically and get its previous value back, but a swap is conceptually three steps, in terms of the std::atomic API they are:

    auto tmp = a.load();
    tmp = b.exchange(tmp);
    a.store(tmp);
    

    This is an atomic read followed by an atomic read-modify-write followed by an atomic write. Each step can be done atomically, but you can't do all three atomically without a lock.

    For a non-copyable value such as std::unique_ptr you can't even use the load and store operations above, but must do:

    auto tmp = a.exchange(nullptr);
    tmp = b.exchange(tmp);
    a.exchange(tmp);
    

    This is three read-modify-write operations. (You can't really use std::atomic> to do that, because it requires a trivially-copyable argument type, and std::unique_ptr isn't any kind of copyable.)

    To do it with fewer operations would need a different API that isn't supported by std::atomic because it can't be implemented because as Stas's answer says, it isn't possible with most processors. The C++ standard is not in the habit of standardising functionality that is impossible on all contemporary architectures. (Not intentionally anyway!)

    Edit: Your updated question asks about a very different problem, in the second example you don't need an atomic swap that affects two objects. Only global is shared between threads, so you don't care if updates to local are atomic, you just need to atomically update global and retrieve the old value. The canonical C++11 way to do that is with std:atomic and you don't even need a second variable:

    atomic global;
    
    void f() {
       delete global.exchange(new T(...));
    }
    

    This is a single read-modify-write operation.

提交回复
热议问题