线程安全的数据结构类的目的是什么?

时间:2013-08-30 04:05:41

标签: c++ multithreading thread-safety

AFAIK是多线程编程的主要目标,它通过利用多个处理内核来提高性能。关键是最大化并行执行。

当我看到线程安全的通用数据结构类时,我觉得有些讽刺。因为线程安全意味着强制执行串行执行(锁定,原子操作或其他),所以它是反并行的。线程安全的意味着序列化被封装并隐藏在类中,因此我们将有更多机会强制执行串行 - 失去性能。最好在更大(或最大)的单元应用逻辑中管理那些关键部分。

那么为什么人们想要线程安全的类呢?他们的真正好处是什么?


P.S。 我的意思是线程安全类是一个只有线程安全方法的类,可以安全地同时从多个线程调用。 安全意味着它保证了正确的读/写结果。 正确表示其结果等于单线程执行时的结果。 (例如,避免 ABA问题

所以我认为我的问题中的术语thread-safety 包含串行执行。这就是为什么我对它的目的感到困惑并问了这个问题。

5 个答案:

答案 0 :(得分:6)

我认为你的问题有一个错误的假设:同步操作根本就不是反并行的。没有某种形式的同步,根本无法构建并行可变数据结构。是的,这些同步机制的大量使用会降低代码并行运行的能力。但是如果没有这些机制,就不可能首先编写代码。

不需要同步的一种形式的线程安全数据结构是不可变值。但是它们仅适用于一部分场景(并行读取,数据传递等)

答案 1 :(得分:3)

无需序列化即可实现线程安全数据结构。这是正确的,但它是可行的并且完成了。然后你就可以获得并发的好处而没有任何瓶颈。

答案 2 :(得分:3)

这通常是性能关键的多线程代码避免使用“线程安全”容器的原因。像std :: vector等容器不是线程安全的。如果应用程序需要在不同线程之间对这些容器进行共享访问,则应用程序负责管理该访问。

另一方面,有时性能不是多线程的驱动因素。 GUI程序受益于将UI线程与正在执行工作的线程分开。由于各种原因,其他线程可能会被分离。通常,这可以在代码中实现良好的职责分离,并为应用程序提供更好的整体生动性。在这些情况下,目标通常不是高性能本身。对于这些应用程序,使用线程安全容器可能是非常自然的选择。

当然,最好的选择是让你的蛋糕也吃掉它,比如一些无锁队列实现,它允许一个线程提供队列,另一个线程消耗,没有锁定(仅依赖于原子性质)某些基本操作)。

答案 3 :(得分:3)

  

线程安全类意味着序列化被封装并隐藏在类中,因此我们将有更多机会强制执行串行 - 失去性能。

使线程安全客户端的责任会破坏封装(并非总是如此)。根据上下文/设计,线程安全性可能非常复杂,或者随着时间的推移容易发生变化(在API更改时中断程序),或者它们根本不统一。抽象同步不一定等于损失;它也有可能带来很大的好处 - 特别是因为它不是新手的主题。

  

最好在较大(或最大)的单元应用逻辑中管理这些关键部分。

我不确定是谁告诉你的,但这并不一定适用于所有场景。一旦开始实施并发系统,您就会意识到在设计中选择最佳的同步粒度会对其运行方式产生巨大影响。请注意,“最佳”通用设计并不总是最适合给定用途。

这里没有硬性规则 - 小而短(可能使用并获得更多数量的锁)对于许多设计来说更好,而最大单位可能会增加争用并导致严重阻塞。开始更新非常容易,然后花费大量时间在更新中执行操作,这些操作不需要在更新期间持续同步整个结构。在每次访问时锁定整个图并不总是更好,并且结构的某些组件可以是独立于其他组件的线程安全的。因此,最大的单元方法通常可能会强制执行影响性能的序列化,尤其是随着大小和复杂性的增加。

  

那么为什么人们想要线程安全的类呢?他们的真正好处是什么?

有几个很好的理由浮现在脑海中:

  1. 它们很难正确实施,诊断和测试。高性能并发设计不是通过参加演讲或通过一些在线教程学习的概念。要理解什么是好的设计需要花费大量的错误和时间。

  2. 有些结构非常专业。这些可能是非阻塞的,依赖于原子,或使用不太典型的并发模式或同步形式。示例:默认情况下,您可能只需要在需要锁定时获取互斥锁,但有时rwlock或spinlock会更好。有时候不变性可能会更好。

  3. 某些上下文或域非常专业。设计单个组件通常是一项简单的任务,但设计整个系统以及组件如何交互是一个更大的挑战,系统可能需要在特殊约束下运行 - 依赖于该设计的同步可以为您省去很多头痛。您可能没有花时间在许多不同的工作负载下进行基准测试,而编写它的人花了很多时间来理解实现及其执行。

  4. 它只是有效。有些人不想在兼并性问题上花费精力。他们宁愿使用经过验证的可靠实施,并专注于其计划的其他方面。在某些情况下,使用其软件的人可能不会很好地理解其中的一些概念,当他们选择使用经过验证的(甚至是熟悉的)设计时,您将会感激不尽。

  5. 封装。有时,封装可以在并发系统中带来巨大的性能提升。示例:成员或参数可以是有条件不可变的,并且可以利用该特征。在其他情况下,封装可能导致较低的采集或减少阻塞。另一种情况是封装可以降低使用界面的复杂性 - 可能会删除整个类别的潜在线程问题(尽管您可能会留下一小部分约束)。

  6. 少了解。重新使用一个众所周知的实现并理解它的运作方式,与审阅手写的实现(例如,去年离职的同事)相比,您学习的内容较少。

  7. 当然有缺点,但这不是你的问题;)

答案 4 :(得分:1)

这完全取决于班级是什么。

考虑一个队列。并非每个队列都需要线程安全。但是在某些情况下肯定需要一种数据结构,你可以从一个线程中推送“stuff”,并让另一个线程从中拉出“东西”。这个提高了线程的并行性,因为它将线程间通信集中在一个位置:线程间队列。一方填充一系列命令,另一方读取它们并在可能的情况下执行它们。如果没有可用的命令,它会阻止或做任何事情。

在某种程度上,这需要一个线程安全的类。由于用户可能希望使用不同类型的“东西”来定制它,因此标准库提供的通用实现并非不合理。当然,今天的C ++标准中并没有这样的东西,但它几乎肯定会到来。

这不是“反平行”;它提高并行性。没有它,你必须找到两个线程进行通信的其他方式。一个很可能会迫使其中一个人更频繁地阻止。

考虑shared_ptr。使shared_ptr的引用计数器线程安全的成本是微不足道的,因为很可能有人将其搞砸了。它当然不是免费的;原子增量/减量不是免费的。但它“来自”强制执行串行执行“,因为”串行执行“的任何时刻都是如此之短,以至于在任何真正的程序中都无关紧要。

所以不,这些东西不是“反平行”。