c ++中的默认复制构造函数是否是线程安全的?

fra*_*lin 7 c++ multithreading copy-constructor

class CSample{
     int a;
     // ..... lots of fields
}
Csample c;
Run Code Online (Sandbox Code Playgroud)

我们知道,Csample有一个默认的复制构造函数.当我这样做:

Csample d = c
Run Code Online (Sandbox Code Playgroud)

将发生默认的复制构造函数.我的问题是:它是线程安全的吗?因为c在执行复制构造函数时,可能有人在另一个线程中进行了修改.如果是这样,编译器如何做到这一点?如果没有,我认为编译器无法保证复制构造函数是线程安全的,这太可怕了.

Jan*_*dec 13

除非明确指出,否则 C++中的任何内容都不是线程安全的.

如果您需要读取对象,c而在另一个线程中可能会对其进行修改,则您有责任将其锁定.这是一般规则,没有理由为了创建副本而阅读它应该是一个例外.

请注意,正在创建的副本不需要被锁定,因为还没有其他线程知道它.只需要来源.

编译器不保证任何东西都是自己的线程安全的,因为99.9%的东西不需要是线程安全的.大多数事情只需要是可重入的.因此,在极少数情况下,您实际上需要创建一些线程安全的东西,您必须使用locks(std::mutex)或atomic types(std::atomic<int>).

你也可以简单地让你的对象保持不变,然后你就可以在没有锁定的情况下读取它们,因为在创建之后没有任 使用常量对象的代码更容易并行化,并且通常更容易理解,因为您必须跟踪的状态更少.

请注意,在最常见的体系结构中,mov带有int操作数的指令恰好是线程安全的.在其他CPU类型甚至可能不是真的.并且因为允许编译器预加载值,所以C++中的整数赋值无论如何都不是.


¹ 如果在同一个对象上同时调用它们,一组操作被认为是线程安全的.在C++中,在同一对象上同时调用任何修改操作和任何其他操作是数据竞争,即UndefinedBehaviour™.

² 重要的是要注意,如果一个对象是"线程安全的",它在大多数情况下并不能真正帮助你.因为如果一个对象保证当它同时写入时你将始终读取新值或旧值(C++允许在一个线程int c被更改01000一个线程时,另一个线程可能会读取,比如说232),大多数情况下不会帮助你,因为你需要以一致的状态读取多个值,无论如何你必须自己锁定它们.

³ 可重入意味着可以同时在不同对象上调用相同的操作.标准C库中有一些函数不可重入,因为它们使用全局(静态)缓冲区或其他状态.大多数都有可重入的变体(_r通常带后缀),而standrd C++库使用这些,因此C++部分通常是可重入的.


Jam*_*nze 5

标准中的一般规则很简单:如果一个对象(和子对象是对象)被多个线程访问, 并且被任何线程修改,则必须同步所有访问.造成这种情况的原因有很多,但最基本的原因是最低级别的保护通常是错误的粒度级别 ; 添加同步原语只会使代码运行速度明显变慢,对用户没有任何实际优势,即使在多线程环境中也是如此.即使复制构造函数是"线程安全的",除非对象在某种程度上完全独立于所有其他上下文,否则您可能需要某种更高级别的同步原语.

关于"线程安全":经验丰富的实践者通常意义上的对象/类/任何事物确切地指出它保证了多少保护.正是因为像你(以及许多其他人)这样的低级定义似乎没用.同步类中的每个函数通常是无用的.(Java进行了实验,然后退出了,因为他们在容器的初始版本中所做的保证变得昂贵且毫无价值.)


usr*_*usr 1

假设在多个线程上同时访问dc,则这不是线程安全的。这将构成数据竞争,这是未定义的行为。

Csample d = c;
Run Code Online (Sandbox Code Playgroud)

和一样不安全

int d = c;
Run Code Online (Sandbox Code Playgroud)

是。