为什么 C++ std 库本质上不是线程安全的?

Why is the C++ std library not inherently thread safe?

提问人:zalanshah64 提问时间:6/10/2023 更新时间:6/10/2023 访问量:165

问:

我知道,从根本上说,在 C++ 中使用类不是线程安全的,因为它们的函数不是原子的,这可能会导致竞争条件。

在学习在类中编写多线程程序时,我们必须将对象读取/写入的关键部分包装在互斥锁中,以确保原子性。在花时间阅读了一些标准库之后,我很好奇为什么 C++ 不会自动为用户执行此操作。

我的理解是,互斥锁只包含一个表示锁状态的布尔值或整数,以及某种形式的队列,表示锁上等待的所有线程。既然这不是一个很大的开销,为什么标准库不自动提供这种抽象,并用一个仅对该类本地的互斥锁(即不是全局互斥锁)包装它的所有函数?

C++ 多线程标准

评论

9赞 user4581301 6/10/2023
为什么所有不使用线程的人都愿意支付线程安全的开销?
5赞 user4581301 6/10/2023
在存储方面,成本可能很低。性能方面,它通常是致命的。一切都必须停止并获取互斥锁,然后释放它。这可能很快,但如果一切都必须这样做,它会很快加起来。对于像标准库这样的东西,你必须为最低的公分母提供服务,每个人都需要的东西,因为如果你建立的成本只为一些人服务,那么只有这些人会使用库。
7赞 Pete Becker 6/10/2023
螺纹安全体现在应用层面。你不能只是把一堆线程安全对象混为一谈,然后得到一个线程安全的应用程序。 如果从多个线程访问容器,即使容器上的每个操作都是线程安全的,也可能崩溃和烧毁。其他一些线程可以删除此代码的测试和尝试使用它之间的最后一个元素。if (!container.is_empty()) container.do_something_with_first_element();
3赞 user4581301 6/10/2023
这种方法到处都在失去性能,甚至可能无法提供所需的结果,因为在收集数据时更改了值。
4赞 PaulMcKenzie 6/10/2023
@zalanshah64我很好奇为什么C++不自动为用户执行此操作 - 这违背了C++的零开销原则。为什么不问为什么C++不引入自动数组边界检查?出于同样的原因,线程安全不是自动的。

答:

9赞 Nicol Bolas 6/10/2023 #1

即使忽略性能问题(这是不容忽视的),线程安全也绝不会像“所有对象都有一个互斥锁”那么简单。

假设您有一个线程安全容器。有一个容器对象被共享。那么,这个代码是“线程安全的”吗?

shared_container.push_back(value1);
shared_container.push_back(value2);

嗯,这取决于:如果此代码的意图是并且彼此相邻,则不能保证该行为。文本文本不会导致数据争用,但也不会具有所需的行为。value1value2

代码的目的是按照它所说的去做。如果不这样做,无论是由于数据争用还是违反代码的预期行为,都是一个错误。

那这个呢:

shared_container.emplace_back(param1, param2);

切记:在函数调用中调用构造函数(容器的元素类型)。如果该构造函数尝试访问 ,会发生什么情况?不能将对象锁定在其他线程上。emplace_backshared_container

或者更好的是,如果该构造函数需要访问其他一些共享对象,但保持该对象上的锁的线程在等待 的锁时被阻塞,会发生什么情况?您的线程拥有哪个并且无法释放,因为您处于 ?shared_containeremplace_back

您不能在不考虑线程安全性的情况下运行多线程。你可以划分线程间的通信,但你永远不能不去想它。默认情况下,将所有对象设置为“线程安全”只会提供安全的错觉。更糟糕的是,它使追踪错误变得更加困难,因为它们更加微妙。

我们甚至可以从标准库中看到一个更合理的线程安全思维的例子。 是一种将单个对象从一个线程转发到另一个线程的方法。但剥离到它的基本概念,你可以把它看作是你在线程之间共享的“线程安全”。promise/futureoptional<T>

但它们不是一回事。

promise/future具有非对称接口。一旦保存 的代码设置了该值,它就无法访问或修改该值。保存 的代码不能操作该值;他们只能查看是否已设置或访问它。接口说有一个值的提供者和一个值的接收者,并且该值只能单向。promisefuture

共享没有对这种值转发的接口级支持。如果您有权访问该对象,则可以根据需要设置或获取该值。但这通常不是数据管理跨线程的工作方式,而且与 相比,它肯定不那么“安全”。optional<T>promise/future

安全的线程间通信最好通过使用专门为此而设计的对象来完成。其接口支持特定线程间通信模式的对象,这些对象具有非常严格的线程间交互。