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

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

本文关键字:默认 复制 构造函数 安全 c++ 线程      更新时间:2023-10-16
class CSample{
     int a;
     // ..... lots of fields
}
Csample c;

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

Csample d = c

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

c++中没有是线程安全的¹,除非明确指出

如果你需要读取对象c,而它可能在另一个线程中被修改,你负责锁定它。这是一个一般规则,没有理由认为为了创建副本而读取它应该是一个例外。

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

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

你也可以简单地把对象设为常量,这样你就可以在不加锁的情况下读取它们,因为在创建之后没有任何东西在写它们。通常,使用常量对象的代码更容易并行化,也更容易理解,因为需要跟踪的状态较少。

请注意,在最常见的体系结构中,mov指令与int操作数恰好是线程安全的。在其他CPU类型上,甚至可能不是这样。因为编译器允许预加载值,所以c++中的整型赋值是而不是

¹如果在相同的对象上并发调用一组操作,则认为是线程安全的²。在c++中,对同一对象并发调用任何修改操作和任何其他操作都是数据争用,这是UndefinedBehaviour™。

²需要注意的是,如果一个对象是"线程安全的",那么它在大多数情况下并不能真正帮助你。因为如果一个对象保证在并发写入时总是读取新的或旧的值(c++允许当一个线程将int c0更改为1000时,另一个线程可能会读取232),那么大多数情况下这对您没有帮助,因为您需要在一致状态下读取多个值,为此您必须自己锁定它们。

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

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

和关于"线程安全":通常的意思有经验的从业者,它的对象/类/什么精确指定它保证了多少保护。精确的因为这样的低级定义,如您(和许多,许多)其他)似乎使用是无用的。中的各功能同步类通常是无用的。(Java做了这个实验,并且然后放弃了,因为他们在最初的保证他们的容器版本原来是昂贵的和一文不值。)

假设dc在多个线程上被并发访问,这不是线程安全的。这将导致数据竞争,这是一种未定义的行为。

Csample d = c;

一样不安全
int d = c;

.