0

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

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

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


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

所以我认为我的问题中的术语线程安全包含定义的串行执行。这就是为什么我对其目的感到困惑并提出这个问题的原因。

4

5 回答 5

6

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

不需要同步的线程安全数据结构的一种形式是不可变值。然而,它们只适用于场景的一个子集(并行读取、数据传递等......)

于 2013-08-30T04:10:08.647 回答
3

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

让线程安全成为客户端的责任会破坏封装(并非总是如此)。根据上下文/设计,线程安全可能非常复杂,或者容易随时间变化(当 API 更改时会破坏您的程序),或者它们根本不统一。抽象同步不一定等同于丢失;它也有可能带来巨大的好处——尤其是因为它不是新手的主题。

最好在更大(或最大)单元 - 应用程序逻辑中管理这些关键部分。

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

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

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

我想到了几个很好的理由:

  1. 它们可能难以正确实施、诊断和测试。高性能并发设计不是通过参加讲座或通过一些在线教程学到的概念。理解一个好的设计需要很多错误和时间投入。

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

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

  4. 它只是工作。有些人不想把精力花在并发问题上。他们宁愿使用经过验证的、可靠的实施,并专注于他们计划的其他方面。在某些情况下,您最终使用其软件的人可能对其中一些概念不够了解,当他们选择使用经过验证的(甚至熟悉的)设计时,您会感激不尽。

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

  6. 比较少理解。重用一个众所周知的实现并理解它是如何运作的,与审查一个手写的实现(例如,你去年离职的同事)相比,你需要学习的东西更少。

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

于 2013-08-30T05:40:13.000 回答
3

无需序列化即可实现线程安全数据结构。做对很棘手,但它是可行的并且已经完成。然后,您将获得并发的好处,没有任何瓶颈。

于 2013-08-30T04:07:54.897 回答
3

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

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

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

于 2013-08-30T04:13:33.233 回答
1

这一切都取决于类是什么。

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

这在某种程度上要求有一个线程安全的类。而且由于用户可能希望使用不同种类的“东西”对其进行自定义,因此标准库提供的通用实现并非不合理。诚然,今天的 C++ 标准中不存在这样的东西,但它几乎肯定会出现。

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

考虑一个shared_ptr. 使shared_ptr' 的参考计数器线程安全的成本是微不足道的,旁边很可能有人把它搞砸了。它当然不是免费的。原子增量/减量不是免费的。但这远非“强制串行执行”,因为“串行执行”的任何时刻都很短,以至于在任何实际程序中都无关紧要。

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

于 2013-08-30T07:21:45.517 回答