是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>
)。
你也可以简单地把对象设为常量,这样你就可以在不加锁的情况下读取它们,因为在创建之后没有任何东西在写它们。通常,使用常量对象的代码更容易并行化,也更容易理解,因为需要跟踪的状态较少。
请注意,在最常见的体系结构中,mov
指令与int
操作数恰好是线程安全的。在其他CPU类型上,甚至可能不是这样。因为编译器允许预加载值,所以c++中的整型赋值是而不是。
¹如果在相同的对象上并发调用一组操作,则认为是线程安全的²。在c++中,对同一对象并发调用任何修改操作和任何其他操作都是数据争用,这是UndefinedBehaviour™。
²需要注意的是,如果一个对象是"线程安全的",那么它在大多数情况下并不能真正帮助你。因为如果一个对象保证在并发写入时总是读取新的或旧的值(c++允许当一个线程将int c
从0
更改为1000
时,另一个线程可能会读取232
),那么大多数情况下这对您没有帮助,因为您需要在一致状态下读取多个值,为此您必须自己锁定它们。
³可重入意味着可以同时对不同的对象调用相同的操作。标准C库中有一些函数不是可重入的,因为它们使用全局(静态)缓冲区或其他状态。大多数都有可重入的变体(通常带有_r
后缀),标准c++库使用这些变体,因此c++部分通常是可重入的。
标准中的一般规则很简单:如果一个对象(和子对象(对象)被多个线程访问,和可以被任何线程修改,那么所有的访问都必须被修改同步。这有很多原因,但最重要的是最基本的一点是,最低级别的保护通常是最安全的粒度级别错误;添加同步原语只会使代码运行速度明显变慢,没有任何对用户来说真正的优势,即使是在多线程中环境。即使复制构造函数是"线程安全的",除非这个物体是完全独立于其他物体的上下文中,您可能需要某种同步更高级别的原语。
和关于"线程安全":通常的意思有经验的从业者,它的对象/类/什么精确指定它保证了多少保护。精确的因为这样的低级定义,如您(和许多,许多)其他)似乎使用是无用的。中的各功能同步类通常是无用的。(Java做了这个实验,并且然后放弃了,因为他们在最初的保证他们的容器版本原来是昂贵的和一文不值。)
假设d
或c
在多个线程上被并发访问,这不是线程安全的。这将导致数据竞争,这是一种未定义的行为。
Csample d = c;
和
一样不安全int d = c;
.
- 为什么默认复制函数在按值发送参数时不调用?
- 如何在QTreeView中禁用默认复制行为?
- 通过默认复制构造函数比较 C++ 字符串是否会影响性能,原因为何?
- 默认复制/移动构造函数时 GDB 中的奇怪行为
- C++默认复制构造函数不可行
- 没有数据成员和大括号语法的类的默认复制构造函数
- C++中默认复制构造函数的奇怪行为
- 将功能添加到默认复制构造函数中
- C 带有什么默认复制构造函数使用什么初始化基本复制构造函数
- 如果我默认复制构造函数,将生成构造函数和移动分配
- 为什么要删除基类的默认复制并移动ctor和赋值
- 对象数组的默认复制行为
- C++11:是用户声明的默认复制构造函数
- 外部C结构的C++默认复制/移动赋值运算符不是常量
- 删除默认C++复制和移动构造函数和赋值运算符的缺点
- 非默认复制构造函数会减慢程序的速度吗?
- 错误:没有与默认复制构造函数调用匹配的函数
- 使用默认复制构造函数时出错:"deleted function"
- 是c++中线程安全的默认复制构造函数
- 使用模板化构造函数时禁用默认复制构造和赋值构造函数