提问人:K-ballo 提问时间:1/3/2013 最后编辑:Johannes Schaub - litbK-ballo 更新时间:10/29/2022 访问量:19682
const 在 C++11 中是否意味着线程安全?
Does const mean thread-safe in C++11?
问:
我听说这意味着 C++11 中的线程安全。这是真的吗?const
这是否意味着现在相当于 Java 的?const
synchronized
他们的关键词用完了吗?
答:
我听说这意味着 C++11 中的线程安全。这是真的吗?
const
这在某种程度上是真的......
这就是标准语言对线程安全的看法:
[1.10/4] 如果其中一个表达式计算修改了内存位置 (1.7),而另一个表达式访问或修改了相同的内存位置,则两个表达式计算会发生冲突。
[1.10/21] 如果一个程序在不同的线程中包含两个冲突的动作,其中至少一个不是原子的,并且两者都不先于另一个发生,则程序的执行包含数据争用。任何此类数据争用都会导致未定义的行为。
这只不过是发生数据争用的充分条件:
- 对给定的事物同时执行两个或多个操作;和
- 其中至少有一个是写入。
标准库在此基础上,更进一步:
[17.6.5.9/1] 本节指定了实现为防止数据争用而应满足的要求 (1.10)。除非另有说明,否则每个标准库函数都应满足每个要求。在以下指定的情况以外的情况下,实现可以防止数据争用。
[17.6.5.9/3] C++ 标准库函数不得直接或间接修改由当前线程以外的线程访问的对象 (1.10),除非通过函数的非常量参数直接或间接访问对象,包括 .
this
简单来说,它期望对对象的操作是线程安全的。这意味着,只要对你自己类型的对象进行操作,标准库就不会引入数据争用const
const
- 完全由读取组成 - 也就是说,没有写入 - ;或
- 在内部同步写入。
如果此预期不适用于您的某个类型,则直接或间接地将其与标准库的任何组件一起使用可能会导致数据争用。总之,从标准库的角度来看,确实意味着线程安全。需要注意的是,这只是一个合约,编译器不会强制执行,如果你违反它,你会得到未定义的行为,你要靠自己。是否存在不会影响代码生成 - 至少在数据竞争方面不会--.const
const
这是否意味着现在相当于 Java 的?
const
synchronized
不可以。一点也不。。。
请考虑以下表示矩形的过于简化的类:
class rect {
int width = 0, height = 0;
public:
/*...*/
void set_size( int new_width, int new_height ) {
width = new_width;
height = new_height;
}
int area() const {
return width * height;
}
};
member-function 是线程安全的;不是因为它 ,而是因为它完全由读取操作组成。不涉及写入,并且至少涉及一次写入是发生数据争用所必需的。这意味着您可以从任意数量的线程中调用,并且您将始终获得正确的结果。area
const
area
请注意,这并不意味着它是线程安全的。事实上,很容易看出,如果对给定的调用同时发生对 的调用,那么最终可能会根据旧宽度和新高度(甚至乱码值)计算其结果。rect
area
set_size
rect
area
但这没关系,不是这样,它甚至不期望线程安全。另一方面,声明的对象将是线程安全的,因为不可能写入(如果您正在考虑 -ing 最初声明的东西,那么您将获得未定义的行为,仅此而已)。rect
const
const rect
const_cast
const
那么这意味着什么呢?
为了论证起见,让我们假设乘法运算的成本非常高,我们最好尽可能避免使用它们。我们只能在请求时计算面积,然后缓存它以防将来再次请求:
class rect {
int width = 0, height = 0;
mutable int cached_area = 0;
mutable bool cached_area_valid = true;
public:
/*...*/
void set_size( int new_width, int new_height ) {
cached_area_valid = ( width == new_width && height == new_height );
width = new_width;
height = new_height;
}
int area() const {
if( !cached_area_valid ) {
cached_area = width;
cached_area *= height;
cached_area_valid = true;
}
return cached_area;
}
};
[如果这个例子看起来太人为了,你可以在心里用一个非常大的动态分配整数来替换,这个整数本质上是非线程安全的,而且乘法的成本非常高。int
member-function 不再是线程安全的,它现在正在执行写入,并且不会在内部同步。有问题吗?对 的调用可能作为另一个对象的复制构造函数的一部分发生,此类构造函数可能已由标准容器上的某些操作调用,此时标准库期望此操作在数据争用方面表现为读取。但我们正在写!area
area
一旦我们直接或间接地将一个放入标准容器中,我们就与标准库签订了合同。为了继续在函数中执行写入,同时仍然遵守该协定,我们需要在内部同步这些写入:rect
const
class rect {
int width = 0, height = 0;
mutable std::mutex cache_mutex;
mutable int cached_area = 0;
mutable bool cached_area_valid = true;
public:
/*...*/
void set_size( int new_width, int new_height ) {
if( new_width != width || new_height != height )
{
std::lock_guard< std::mutex > guard( cache_mutex );
cached_area_valid = false;
}
width = new_width;
height = new_height;
}
int area() const {
std::lock_guard< std::mutex > guard( cache_mutex );
if( !cached_area_valid ) {
cached_area = width;
cached_area *= height;
cached_area_valid = true;
}
return cached_area;
}
};
请注意,我们使函数线程安全,但 still 不是线程安全的。在调用的同时发生的对的调用可能最终仍会计算错误的值,因为对互斥锁的赋值不受互斥锁保护。area
rect
area
set_size
width
height
如果我们真的想要一个线程安全的,我们将使用同步原语来保护非线程安全的。rect
rect
他们的关键词用完了吗?
是的,他们是。从第一天起,他们就已经用完了关键词。
来源: You don't know const
and mutable
- Herb Sutter
评论
std::string
const
mutable
const
const
mutable
mutable
这是对 K-ballo 答案的补充。
在这种情况下,术语线程安全被滥用。正确的措辞是:const 函数意味着线程安全的按位 const 或内部同步,正如 Herb Sutter (29:43) 自己所说
同时从多个线程调用 const 函数应该是线程安全的,而无需在另一个线程中同时调用非常量函数。
因此,const 函数不应该(而且大多数时候也不会)真正是线程安全的,因为它可能会读取可能被另一个非常量函数更改的内存(没有内部同步)。通常,这不是线程安全的,因为即使只有一个线程在写入(另一个线程读取数据),也会发生数据争用。
另请参阅我对相关问题的回答:根据 C++11(语言/库)标准,线程安全函数的定义是什么?。
评论
const
函数更改的内存。(想象一下一个函数。const
const
++this->p->value
++this->p->value
bitwise const
++this->p->value
this->p->value
this->p->value
不!反例:
#include <memory>
#include <thread>
class C
{
std::shared_ptr<int> refs = std::make_shared<int>();
public:
C() = default;
C(C const &other) : refs(other.refs)
{ ++*this->refs; }
};
int main()
{
C const c;
std::thread t1([&]() { C const dummy(c); });
std::thread t2([&]() { C const dummy(c); });
}
的复制构造函数是完全合法的,但它不是线程安全的。C
C
const
评论
K-Ballo
const
评论
const
是否意味着线程安全。这将是无稽之谈,否则这意味着您应该能够继续将每个线程安全方法标记为 .相反,我们真正要问的问题是暗示线程安全的,这就是本次讨论的内容。const
const