unique_ptr是线程安全的吗?

41

unique_ptr是否线程安全?下面的代码会不会打印两次相同的数字?

#include <memory>
#include <string>
#include <thread>
#include <cstdio>

using namespace std;

int main()
{
    unique_ptr<int> work;

    thread t1([&] {
        while (true) {
            const unique_ptr<int> localWork = move(work);
            if (localWork)
                printf("thread1: %d\n", *localWork);
            this_thread::yield();
        }
    });

    thread t2([&] {
        while (true) {
            const unique_ptr<int> localWork = move(work);
            if (localWork)
                printf("thread2: %d\n", *localWork);
            this_thread::yield();
        }
    });

    for (int i = 0; ; i++) {
        work.reset(new int(i));

        while (work)
            this_thread::yield();
    }

    return 0;
}
3个回答

47

在正确使用时,unique_ptr 是线程安全的。你违反了一个不成文的规则:永远不要通过引用在线程之间传递 unique_ptr。

unique_ptr 的设计理念是它始终只有一个(唯一)所有者。因此,您始终可以安全地在线程之间传递它而无需同步 —— 但必须按值传递,而非按引用传递。一旦您为 unique_ptr 创建别名,就失去了唯一性属性,所有赌注都作废了。不幸的是,C++ 无法保证唯一性,因此您必须严格遵循约定。不要创建 unique_ptr 的别名!


1
如果“在线程之间传递”意味着使用unique_ptr参数启动新线程,那么您可以将unique_ptr替换为几乎任何类型,并仍然称其为线程安全。只要正确使用,任何东西都是线程安全的 :) - Valentin Milea
3
你确定这个吗?我内心的多疑程序员告诉我,如果你在线程之间传递 "up",那么它是未定义行为,因为你没有确保 seq_cst,也就是说,如果线程1修改了 "up" 指向的内容,然后将 "up" 传递给线程2,则线程2可能会看到指向内容的旧值。 - NoSenseEtAl
@ValentinMilea:将任意指针传递给线程是不安全的。你可能在考虑传递具有复制语义的值--这些确实是安全的。 - Bartosz Milewski
6
当人们询问线程安全性时,实际上是在询问同时从多个线程访问一个对象实例是否安全。从这个角度来看,unique_ptr并不比T*更安全。 - SnakE
2
这个回答没什么意义。你 不能 按值传递 unique_ptr - Paul Sanders
显示剩余3条评论

31

不,它不是线程安全的。

两个线程都可以在没有显式同步的情况下潜在地移动工作指针,因此两个线程可能会获得相同的值或者两者都获得无效的指针...这是未定义行为。

如果您想要像这样做正确的事情,您可能需要使用像std::atomic_exchange这样的东西,以便两个线程可以使用正确的语义读取/修改共享的工作指针。


令人兴奋的是,如果发生这种情况,它也将导致整数被双重释放。但这只是可能发生的情况,也有可能根本不会释放整数,或者移动版本都只有指针值的一半。它可能会做出各种各样的事情。 - Nicol Bolas
没错,我对架构做了一些假设,这些假设实际上并不成立。 - Useless

12
根据Msdn的说明:

以下线程安全规则适用于标准C++库中的所有类(除了shared_ptr和iostream类,如下所述)。

一个对象可以被多个线程同时读取而不会出现问题。例如,给定一个对象A,从线程1和线程2同时读取A是安全的。

如果单个对象正在被一个线程写入,则对该对象的所有读取和写入(在相同或其他线程中)都必须受到保护。例如,给定一个对象A,如果线程1正在向A写入数据,则必须防止线程2从A读取或写入A。

即使另一个线程正在读取或写入同一类型的不同实例,也可以安全地读取和写入某一类型的实例。例如,给定同一类型的对象A和B,如果在线程1中写入A并在线程2中读取B是安全的。


最后,一个明智的答案。 - Paul Sanders

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接