Const 在 C + + 11中是否意味着线程安全?

我听说 constC + + 11中的意思是 线程安全,是真的吗?

这是否意味着 const现在相当于 爪哇咖啡synchronized

他们没有 关键词了吗?

18567 次浏览

我听说 constC + + 11中的意思是 线程安全,是真的吗?

有点是真的。

以下是 标准语言在线程安全问题上的说法:

Two expression evaluations 冲突 if one of them modifies a memory location (1.7) and the other one accesses or modifies the same memory location.

[1.10/21] 如果程序在不同的线程中包含两个相互冲突的操作,那么程序的执行包含 数据竞赛,至少其中一个不是原子操作,而且两个操作都没有先于另一个发生。任何这样的数据竞赛都会导致未定义行为。

这就是 数据竞赛发生的充分条件:

  1. 在给定的事物上同时执行两个或两个以上的操作;
  2. 至少其中一个是作家。

标准图书馆以此为基础,更进一步:

[17.6.5.9/1] 本节指定了实现应满足的要求,以防止数据竞争(1.10)。除非另有说明,每个标准库功能应符合每项要求。在下面指定的情况以外的情况下,实现可以防止数据竞争。

[17.6.5.9/3] C++标准程式库函数不得直接或间接修改当前线程以外的线程可访问的对象(1.10) ,除非这些对象是通过函数的非 康斯特参数(包括 this)直接或间接访问的。

简单地说,它期望对 const对象的操作是 线程安全。这意味着,只要对自己类型的 const对象进行操作,标准图书馆就不会引入数据竞争

  1. Consist entirely of reads --that is, there are no writes--; or
  2. 内部同步写入。

如果这个期望不适用于您的类型之一,那么直接或间接地与 标准图书馆的任何组件一起使用它可能会导致 数据竞赛。总之,从 标准图书馆的角度来看,const确实意味着 线程安全。值得注意的是,这仅仅是一个 合约,它不会被编译器强制执行,如果你打破了它,你就得到了 未定义行为,你就得靠自己了。const是否存在将不会影响代码生成——至少对于 数据竞赛不会——。

这是否意味着 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;
}
};

成员函数成员函数 areathread-safe; 不是因为它的 const,而是因为它完全由读操作组成。没有涉及到写操作,而且至少有一个涉及到的写操作对于 data race的发生是必要的。这意味着您可以从任意多个线程调用 area,并且始终可以得到正确的结果。

注意,这并不意味着 rect就是 线程安全。事实上,很容易看出,如果对 area的调用与对给定 rect上的 set_size的调用同时发生,那么 area最终可能根据旧的宽度和新的高度(甚至根据混乱的值)计算结果。

But that is alright, rect isn't const so its not even expected to be 线程安全 after all. An object declared const rect, on the other hand, would be 线程安全 since no writes are possible (and if you are considering const_cast-ing something originally declared const then you get 未定义的行为 and that's it).

那是什么意思?

为了便于讨论,让我们假设乘法运算的代价非常高昂,最好在可能的情况下避免它们。我们可以计算面积,只有当它被请求,然后缓存它的情况下,它被请求再次在未来:

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,这个 非常大的动态分配的整数本质上是非 线程安全的,而且对于它来说增殖是非常昂贵的。]

The 成员函数成员函数 area is no longer thread-safe, it is doing writes now and is not internally synchronized. Is it a problem? The call to area may happen as part of a 拷贝构造函数拷贝构造函数 of another object, such 构造函数 could have been called by some operation on a 标准集装箱, and at that point the 标准图书馆 expects this operation to behave as a in regard to 数据竞赛. But we are doing writes!

一旦我们将 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;
}
};

注意,我们使用了 area函数 线程安全,但是 rect仍然不是 线程安全。对 area的调用发生在对 set_size的调用仍然可能计算出错误的值的同时,因为对 widthheight的赋值不受互斥锁的保护。

If we really wanted a 线程安全 rect, we would use a synchronization primitive to protect the 非线程安全 rect.

他们没有 关键词了吗?

是的,他们从第一天起就没有 关键词了。


Source : 你不知道 ABC0和 mutable-< em > Herb Sutter

这是对 K-ballo 的回答的补充。

术语 线程安全在这种情况下被滥用。正确的措辞是: a const function implies thread-safe bitwise const or internally synchronised,正如 Herb Sutter(29:43)本人所说

同时从多个线程调用 const 函数应该是线程安全的,没有在另一个线程中同时调用 非常规函数。

因此,const 函数不应该(并且在大多数情况下不会)是真正的线程安全的,因为它可能读取内存(没有内部同步) ,而这些内存可能被另一个 非常规函数更改。通常,这不是线程安全的,因为即使只有一个线程正在写入(另一个线程正在读取数据) ,也会发生数据竞争。

也请看我对相关问题 根据 C + + 11(语言/库)标准,线程安全函数的定义是什么?的回答。

不! 反例:

#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的拷贝构造函数是完全合法的,但是它不是线程安全的,尽管 Cconst