【问题标题】:What is the purpose of thread-safe data structure classes? [closed]线程安全数据结构类的目的是什么? [关闭]
【发布时间】:2013-08-30 19:54:05
【问题描述】:

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

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

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


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

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

【问题讨论】:

  • 如果您没有线程安全的数据交换方式,您希望如何在两个不同的线程之间进行通信?
  • 问:“线程安全类”是什么意思? To quote:“人们使用这个术语就像它意味着特定的东西,而事实上,它只是意味着“在场景 X 中正常工作”。没有“正确”的规范和 X 是什么的声明,你实际上不能实施某事并知道您解决了您真正遇到的问题。”
  • @paulsm4 我在我的问题中添加了额外的文本。我希望这能让意思清楚。

标签: c++ multithreading thread-safety


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    无需序列化即可实现线程安全的数据结构。做对是很棘手的,但它是可行的并且已经完成。这样您就可以在没有任何瓶颈的情况下享受并发的好处。

    【讨论】:

      【解决方案3】:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      【讨论】:

        【解决方案4】:

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

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

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

        【讨论】:

          【解决方案5】:

          这完全取决于类是什么。

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

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

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

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-06-10
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多