“weak_ptr”和“shared_ptr”访问是原子的

How `weak_ptr` and `shared_ptr` accesses are atomic

本文关键字:ptr 原子的 访问 shared weak      更新时间:2023-10-16
std::shared_ptr<int> int_ptr;
int main() {
    int_ptr = std::make_shared<int>(1);
    std::thread th{[&]() {
        std::weak_ptr int_ptr_weak = int_ptr;
        auto int_ptr_local = int_ptr_weak.lock();
        if (int_ptr_local) {
            cout << "Value in the shared_ptr is " << *int_ptr_local << endl;
        }
    });
    int_ptr.reset(nullptr);
    th.join();
    return 0;
}

上面的代码线程安全吗? 我读了这个关于weak_ptr线程安全的答案,但只是想确保上面的代码是线程安全的。

我问这个问题的原因是,如果上面的代码确实是线程安全的,我无法理解std::weak_ptrstd::shared_ptr接口如何使以下操作原子expired() ? shared_ptr<T>() : shared_ptr<T>(*this)。 在我看来,如果不使用某种互斥锁或自旋锁,就无法使上述两行逻辑代码同步。

我了解原子增量如何与共享指针的不同实例一起工作,并且我知道shared_ptr本身不是线程安全的,但如果上述确实是线程安全的,那么它非常像线程安全shared_ptr我不明白像上面的条件中的两行代码如何在没有锁的情况下成为原子。

上面的代码线程安全吗?

我相信不是,因为int_ptr.reset(nullptr);正在与std::weak_ptr int_ptr_weak = int_ptr;赛跑

我不明白 std::weak_ptr 和 std::shared_ptr 接口如何使以下操作原子expired() ? shared_ptr<T>() : shared_ptr<T>(*this)

这样的操作不是原子的,因为expired()可能会返回 false,但当您对该值进行操作时,它可能不再准确。另一方面,如果它返回 true,则保证保持准确,只要从那时起没有人修改这个特定的shared_ptr实例。也就是说,对给定shared_ptr的其他副本执行的操作不会导致其过期。

weak_ptr::lock()实现不会使用 expired() .它可能会做一些类似原子比较交换的事情,其中只有当当前强引用的数量大于零时,才会添加一个额外的强引用。

这个问题分为两部分:

线程安全

代码不是线程安全的,但这与lock()无关:
种族存在于int_ptr.reset();std::weak_ptr int_ptr_weak = int_ptr;之间。因为一个线程正在修改非原子变量int_ptr而另一个线程读取它,根据定义,这是数据竞赛。

所以这没关系:

int main() {
    auto int_ptr = std::make_shared<int>(1);
    std::weak_ptr<int> int_ptr_weak = int_ptr;  //create the weak pointer in the original thread
    std::thread th( [&]() {
        auto int_ptr_local = int_ptr_weak.lock();
        if (int_ptr_local) {
            std::cout << "Value in the shared_ptr is " << *int_ptr_local << std::endl;
        }
    });
    int_ptr.reset();
    th.join();
}

示例代码的原子版本 expired() ? shared_ptr<T>() : shared_ptr<T>(*this)

当然,整个过程不可能是原子的。真正重要的部分是,强引用计数仅在它已经大于零时才增加,并且检查和增量以原子方式发生。我不知道是否有任何特定于系统/架构的原语可用于此,但是在c ++ 11中实现它的一种方法是:

std::shared_ptr<T> lock() {
    if (!isInitialized) {
        return std::shared_ptr<T>();
    }
    std::atomic<int>& strong_ref_cnt = get_strong_ref_cnt_var_from_control_block();
    int old_cnt = strong_ref_cnt.load();
    while (old_cnt && !strong_ref_cnt.compare_exchange_weak(old_cnt, old_cnt + 1)) {
        ;
    }
    if (old_cnt > 0) {
        // create shared_ptr without touching the control block any further
    } else {
        // create empty shared_ptr
    }
}

不,您的代码不是线程安全的。 主线程中的int_ptr.reset()操作(这是写入操作)与th中从int_ptr初始化int_weak_ptr(这是读取操作)之间存在数据争用。

"std::weak_ptrstd::shared_ptr 接口如何使以下操作原子expired() ? shared_ptr<T>() : shared_ptr<T>(*this)"

接口没有。它是实现的内部。具体完成方式因实现而异。