20

最近学习了 Grand Central Dispatch,我发现多线程代码非常直观(使用 GCD)。我喜欢不需要锁的事实(以及它在内部使用无锁数据结构的事实),并且 API 非常简单。

现在,我开始学习 pthreads,我不禁对复杂性有点不知所措。线程连接、互斥体、条件变量——所有这些在 GCD 中都不是必需的,但在 pthread 中有很多 API 调用。

pthreads 比 GCD 有什么优势吗?是不是更有效率?是否存在 pthread 可以做 GCD 不能做的事情(不包括内核级软件)的正常用例?

在跨平台兼容性方面,我并不太在意。毕竟,libdispatch 是开源的,Apple 已将其闭包更改作为 GCC 的补丁提交,clang 支持闭包,并且已经(例如 FreeBSD),我们开始看到一些非 Apple 的 GCD 实现。我最感兴趣的是 API 的使用(具体的例子会很棒!)。

4

6 回答 6

16

我来自另一个方向:开始pthreads在我的应用程序中使用,我最近用 C++11 的std::thread. 现在,我正在使用更高级别的构造,例如伪增强线程池,甚至更抽象的英特尔的线程构建块。我认为 GCD 处于甚至高于 TBB。

几点评论:

  • 恕我直言,pthread并不比 GCD 复杂:在其基本核心上,pthread 实际上包含很少的命令(只有少数:仅使用 OP 中提到的那些将为您提供您需要的 95% 以上的功能)。像任何较低级别的库一样,您如何将它们组合在一起以及如何使用它赋予了您强大的功能。不要忘记,最终,像 GCD 和 TBB 这样的库将调用像pthreadsor一样的线程库std::thread
  • 有时,决定成败的不是你使用什么,而是你如何使用它。作为库的支持者,TBB 或 GCD 会告诉您使用他们的库的所有好处,但在您在实际应用程序环境中尝试之前,所有这些都是理论上的好处。例如,当我读到使用细粒度的 parallel_for是多么容易时,我立即将它用于我认为可以从并行性中受益的任务中。当然,我也被 TBB 将处理有关最佳负载平衡和线程分配的所有细节这一事实所吸引。结果? TBB 花费的时间是单线程版本的五倍! 但我不怪 TBB:回想起来,这显然是误用 parallel_for 的案例:当我阅读细则时,我发现了使用 parallel_for 所涉及的开销,并假设在我的案例中,上下文的成本 -切换和添加的函数调用超过了使用多个线程的好处。因此,您必须分析您的案例以查看哪个案例运行得更快。您可能必须重新组织您的算法以使用更少的线程开销。
  • 为什么会这样?pthread 或无线程如何比 GCD 或 TBB 更快?当设计师设计 GCD 或 TBB 时,他必须对任务运行的环境做出假设。事实上,该库必须足够通用,才能处理开发人员无法预料的奇怪用例。这些通用实现不会免费提供。从好的方面来说,库将查询硬件和当前运行环境,以更好地进行负载平衡。它会对您有利吗?唯一知道的方法就是尝试一下。
  • 学习低级库有什么好处,比如std::thread高级库可用时?答案是响亮的YES。使用高级库的优点是,从实现细节中抽象出来。使用高级库的缺点也是对实现细节的抽象。使用时pthreads,我非常了解对象的共享状态和生命周期,因为如果我放松警惕,尤其是在中大型项目中,我很容易遇到竞争条件内存错误. 当我使用更高级别的库时,这些问题会消失吗?并不真地。看起来我不需要考虑它们,但实际上,如果我对这些细节马虎,库实现也会崩溃。因此,您会发现,如果您了解较低级别的结构,那么所有这些库实际上都是有意义的,因为在某些时候,如果您使用较低级别的调用,您将考虑自己实现它们。当然,此时,通常最好使用经过时间测试和调试的库调用。

因此,让我们分解可能的实现:

  • TBB/GCD库调用:最大的好处是线程初学者。与学习较低级别的图书馆相比,他们的进入门槛较低。但是,它们也忽略/隐藏了使用多线程的一些陷阱。动态负载平衡将使您的应用程序更具可移植性,而无需您进行额外的编码。
  • pthreadstd::thread调用:实际上需要学习的调用很少,但正确使用它们需要注意细节并深入了解应用程序的工作原理。如果你能理解这个级别的线程,那么更高级别库的 API 肯定会更有意义。
  • 单线程算法:让我们不要忘记简单单线程段的好处。对于大多数应用程序,单线程比多线程更容易理解并且更不容易出错。事实上,在很多情况下,它可能是合适的设计选择。事实是,一个真正的应用程序会经历各种多线程阶段和单线程阶段:可能不需要一直都是多线程的。

哪个最快? 令人惊讶的事实是,它可能是上述三个中的任何一个。为了获得多线程的速度优势,您可能需要彻底重组您的算法。收益是否大于成本取决于具体情况。

哦,OP询问了thread_pool不合适的情况。简单的情况:如果你有一个紧密的循环,每个循环不需要很多循环来计算,那么使用 thread_pool 可能会花费更多而不是认真返工的好处。还要注意函数调用(如通过线程池的 lambda)与使用单个紧密循环的开销。

对于大多数应用程序来说,多线程是一种优化,所以要在正确的时间和正确的地方进行。

于 2011-12-30T16:41:20.647 回答
15

你正在经历的那种压倒性的感觉……这正是 GCD 被发明的原因。

在最基本的层面上存在线程,pthreads 是用于线程的 POSIX API,因此您可以在任何兼容的操作系统中编写代码并期望它能够工作。GCD 建立在线程之上(尽管我不确定它们是否真的使用 pthreads 作为 API)。我相信 GCD 只能在 OS X 和 iOS 上工作——简而言之,这是它的主要缺点。

请注意,大量使用线程并需要高性能的项目会实现自己的线程池版本。GCD 允许您无数次地避免(重新)发明轮子。

于 2010-01-27T04:57:51.063 回答
4

GCD 是 Apple 技术,不是最兼容跨平台的;pthread 几乎可以在 OSX、Linux、Unix、Windows 的所有设备上使用。包括这个烤面包机

GCD 针对线程池并行性进行了优化。Pthreads(如您所说)是非常复杂的并行构建块,您可以开发自己的模型。如果您有兴趣了解有关 pthread 和不同并行模型的更多信息,我强烈建议您阅读有关该主题的书籍。

于 2010-01-27T04:55:45.453 回答
4

openmpIntel TBB GCD 这样的任何声明/辅助方法都应该非常擅长处理令人尴尬的并行问题,并且很可能会轻松击败幼稚的手动 pthread 并行排序。我建议你仍然学习 pthreads。您将更好地理解并发性,您将能够在每种特定情况下应用正确的工具,并且如果没有别的 - 那里有大量基于 pthread 的代码 - 您将能够阅读“遗留”代码。

于 2010-01-27T05:28:29.453 回答
0

GCD 抽象线程并为您提供调度队列。考虑到可用处理器内核的数量,它会根据需要创建线程。GCD 是开源的,可通过 libdispatch 库获得。FreeBSD 从 8.1 开始包含 libdispatch。GCD 和 C Blocks 是 Apple 对 C 编程社区的主要贡献。我永远不会使用任何不支持 GCD 的操作系统。

于 2013-07-15T18:42:32.443 回答
0

通常:每个 Pthread 实现 1 个任务使用互斥锁(操作系统功能)。
GCD:每个块 1 个任务,分组到队列中。每个虚拟 CPU 的 1 个线程可以获取一个队列并在没有互斥锁的情况下运行所有​​任务。这减少了线程管理开销和互斥体开销,从而提高了性能。

于 2010-09-23T19:05:34.873 回答