C++ 标准容器的线程安全
Posted
技术标签:
【中文标题】C++ 标准容器的线程安全【英文标题】:Thread safety of C++ std Containers 【发布时间】:2013-09-06 11:53:47 【问题描述】:我在这里阅读了很多帖子,询问 C++ 的标准容器(如“list”或“map”是否是线程安全的,并且所有人都说它不是一般情况。并行读取应该没问题,但是并行写入或并行读写可能会导致问题。
现在我在www.cplusplus.com 发现,在大多数操作期间访问或修改列表是安全的。
一些例子:
map::find
容器被访问(const 和 non-const 版本都不会修改容器)。 不访问映射值:同时访问或修改元素是安全的。
map::insert
容器已修改。 并发访问现有元素是安全的,尽管容器中的迭代范围不是。
我是否对 cplusplus.com 有误解,或者关于 std 容器中的线程安全,我还有什么需要了解的吗?
提前致谢!
PS:我要的是 C++03 而不是 C++11
【问题讨论】:
在资源方面,貌似cppreference比较靠谱;您可能想开始使用它而不是 cplusplus.com。 @MatthieuM。 cppreference is still discussing如何正确表达23.2.2[container.requirements.dataraces]/1
和17.6.5.9[res.on.data.races]
@Cubbi:这证明他们确实关心它:)
请注意一个初学者的错误:为std::map<int,int>
容器调用if( mymap[1] ) ...
也会创建 映射条目。因此,改为读取您也将有写入。如果一切看起来都正确,这种错误真的很难被发现。
【参考方案1】:
并行读取应该没问题,但是并行写入或者并行读写可能会出问题。
没错。这是对 C++ 中对象的非同步访问通常提供的保证。此类“问题”正式称为数据竞赛。
现在我在 www.cplusplus.com 发现在大多数操作期间访问或修改列表是安全的。
不,容器只提供并发读取的基本保证。如果一个线程访问它而另一个线程修改它,则会出现数据竞争。但是,对于某些容器,在修改容器本身时访问容器的 元素 有时是安全的。
第一个例子是说find
确实不修改容器或访问元素值(只有键),所以如果其他线程正在访问它是安全的,或修改(不同的)值而不修改容器本身。
第二个例子是说您可以安全地访问现有元素(使用对该元素的引用或迭代器),因为插入元素不会干扰现有元素。
我要的是 C++ 而不是 C++11
如今,C++ 是 C++11。如果您要询问该语言的历史版本,他们对线程没有什么可说的,所以这个问题一般无法回答,仅针对特定的实现和线程框架。
【讨论】:
【参考方案2】:听起来差不多。
请注意,从多个线程访问map
中的值,如果您修改实际值,也需要受到保护。如果您知道两个线程更新不同的条目(我不是指插入/删除),那么它是安全的。
【讨论】:
但是容器本身会发生什么(不仅仅是存储在其中的数据)?如果两个线程同时尝试插入不同的新项目会发生什么? @mrwerner 这将是一场数据竞争(即不好),因为两者都试图修改同一个对象(即容器)。 @merwerner:地图是一种二叉树。如果两个线程试图修改树(例如重新平衡它),你最终会得到一个混乱! 所以 www.cplusplus.com 上的信息是关于访问存储在容器中的元素而不是容器本身,对吧? “读取”容器本身是安全的。从多个线程修改容器本身是不安全的。对当前正在修改的容器进行迭代也是不安全的,因为迭代器可能指向“错误的东西”。只要您没有一个线程修改特定元素并且其他线程也对同一元素进行操作,这些元素就可以安全访问。【参考方案3】:在 C++11 之前,标准中没有“线程”的概念。所以,容器是否是线程安全的问题在 C++03 的上下文中是没有意义的。
【讨论】:
这是评论,不是答案。【参考方案4】:正如 Marcin 所指出的,C++03 没有线程的概念。因此,即使在写入完全完成后在两个线程中并发读取,您也不能假设任何线程安全操作。
想想这个案例: 在 t=0 时,你创建一个线程,让我们调用 A 在 t=10 秒,线程 B(在线程 A 被创建之前存在)写入容器。 在 t=1 小时,线程 A 和 B 都尝试通过 3rd 方库(例如 pthread)在没有任何同步的情况下读取容器。
C++03 只保证线程 B 会看到正确的值。但是并不能保证线程 A 会看到正确的值,因为 C++03 期望每个程序都是单线程的,因此 C++03 规范只能保证事件序列在编程顺序中可见(就像在 1线程)。
【讨论】:
以上是关于C++ 标准容器的线程安全的主要内容,如果未能解决你的问题,请参考以下文章