繁体   English   中英

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

[英]Is the default copy constructor thread-safe in c++?

class CSample{
     int a;
     // ..... lots of fields
}
Csample c;

众所周知,Csample具有默认的副本构造函数。 当我这样做时:

Csample d = c

默认的复制构造函数将发生。 我的问题是:线程安全吗? 因为当您执行复制构造函数时,可能有人在另一个线程中修改了c 如果是这样,编译器怎么做? 如果没有,我认为编译器无法保证副本构造函数是线程安全的,这是很可怕的。

除非明确指出,否则 C ++中的任何事物都不是线程安全的¹。

如果需要在另一个线程中修改对象c同时读取它,则有责任对其进行锁定。 这是一条通用规则,没有理由为什么出于创建副本的目的而阅读它应该是一个例外。

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

编译器不保证任何东西都是线程安全的,因为99.9%的内容不需要是线程安全的 大多数事情只需要重入³。 因此,在极少数情况下,您实际上需要使线程安全,必须使用锁( std::mutex )或原子类型( std::atomic<int> )。

您还可以简单地使对象恒定,然后可以不加锁定地读取它们,因为创建后没有东西在写它们。 通常,使用常量对象的代码既易于并行化,又易于理解,因为您需要跟踪的状态更少。

请注意,在最常见的体系结构上,带有int操作数的mov指令恰好是线程安全的。 在其他CPU类型上,甚至可能不是这样。 而且由于允许编译器预加载值,因此在C ++中始终不能进行整数赋值。


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

² 需要注意的重要一点是,如果对象是“线程安全的”对象,则无论如何在大多数情况下并不能真正帮助您。 因为如果一个对象保证在并发写入时总是读取新值或旧值(C ++允许当一个线程将int c0更改为1000 ,另一个线程可能会读取232 ),大多数情况下这对您无济于事,因为您需要以一致的状态读取多个值,因此您必须自己锁定它们。

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

该标准中的一般规则很简单:如果一个对象(和子对象是对象)被多个线程访问, 并且被任何线程修改,则所有访问都必须同步。 造成这种情况的原因有很多,但是最基本的原因是, 最低级别的保护通常是错误的粒度级别 添加同步原语只会使代码运行速度大大降低,即使在多线程环境中,也对用户没有任何真正的好处。 即使复制构造函数是“线程安全的”,除非对象完全不依赖于所有其他上下文,否则您可能需要更高级别的某种同步原语。

关于“线程安全性”:经验丰富的从业人员通常的含义是,对象/类/任何内容都明确说明了它所保证的保护程度。 正是因为这样的低级定义(例如您(以及许多其他许多类))没有用。 同步类中的每个函数通常是无用的。 (Java进行了实验,然后放弃了,因为他们在容器的初始版本中所做的保证非常昂贵且一文不值。)

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

Csample d = c;

和不安全一样

int d = c;

是。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM