pthread相比GCD有哪些优势?

20

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

现在,我开始学习pthread,但是我感到有些不知所措。线程join、互斥锁和条件变量-所有这些都不需要在GCD中使用,但在pthread中有很多API调用。

pthread相对于GCD提供了优势吗?它是否更高效?是否有正常的使用情况,其中pthread可以做GCD无法做到的事情(排除内核级软件)?

就跨平台兼容性而言,我并不太担心。毕竟,libdispatch是开源的,苹果已将其闭包更改提交为GCC的补丁,clang支持闭包,并且已经(例如FreeBSD)开始看到一些非苹果的实现GCD。我主要关注API的使用(具体示例将非常棒!)。


这似乎很相关:https://dev59.com/emzXa4cB1Zd3GeqPYdo7 - user1031420
6个回答

17
我从另一个方向来:我在我的应用程序中开始使用pthreads,最近将其替换为C++11的std::thread。现在,我正在尝试更高级的构造,如伪-boost线程池,甚至更抽象的Intel's Threading Building Blocks。我认为GCD和TBB一样强大,甚至更加强大。
几点评论:
  • 我认为pthread并不比GCD更复杂:在基本核心方面,pthread实际上只包含很少的命令(只有几个命令: 只使用OP中提到的命令就可以获得95%以上的功能)。像任何低级库一样,它的强大之处在于你如何组合它们以及如何使用它们。不要忘记,最终,像GCD和TBB这样的库将调用类似pthreadsstd::thread这样的线程库。
  • 有时候,决定成功与否的不是你用了什么,而是你如何使用它。支持使用库的人可能会告诉您使用其库的所有好处,例如TBB或GCD,但在实际应用环境中尝试之前,所有这些都只是理论上的好处。例如,当我读到使用细粒度parallel_for是多么容易时,我立即将其用于我认为可以从并行性中受益的任务中。自然地,我也被TBB处理有关最佳负载平衡和线程分配的所有细节所吸引。结果呢?TBB比单线程版本慢了五倍!但我不怪TBB:事后看来,这显然是一个误用parallel_for的例子,当我阅读细则时,我发现使用parallel_for会产生开销,并推断在我的情况下,上下文切换和增加函数调用的成本超过了使用多个线程的好处。因此,您必须分析您的情况,以查看哪个运行速度更快。您可能需要重新组织算法以减少线程开销。
  • 为什么会发生这种情况?pthread或无线程如何比GCD或TBB更快?当设计师设计GCD或TBB时,他必须做出假设关于任务将在其中运行的环境。实际上,库必须足够通用,以便它可以处理开发人员的奇怪且未预见到的用例。这些通用实现不会免费提供。好处是,库将查询硬件和当前运行环境以更好地执行负载平衡。这是否对您有益?唯一的方法是尝试。
  • 学习像std::thread这样的低级库,当有高级库可用时是否有任何好处?答案是肯定的。使用高级库的优点在于抽象出实现细节。使用高级库的缺点也在于抽象出实现细节。当使用pthreads时,我非常清楚共享状态和对象生命周期,因为如果我松懈警惕,尤其是在中到大型项目中,很容易出现竞态条件或内存故障。使用高级库能解决这些问题吗?不完全是。看起来似乎不需要考虑这些问题,但实际上,如果我粗心处理这些细节,库的实现也会崩溃。因此,您会发现,如果您了解更低级别的构造,所有这些库都是有意义的,因为在某个时候,如果您使用更低级别的调用,则将考虑自己实现它们。当然,在那时,最好使用经过时间测试和调试的库调用。
    那么,让我们分解可能的实现:
    • TBB/GCD 库调用:最大的好处是针对多线程编程初学者。与学习较低级别的库相比,它们具有较低的入门门槛。然而,它们也忽略/隐藏了使用多线程的一些陷阱。动态负载平衡将使您的应用程序更具可移植性,而无需额外编写代码。
    • pthreadstd::thread 调用:实际上要学习的调用很少,但要正确使用它们需要注意细节,并深入了解您的应用程序是如何工作的。如果您能够在这个级别上理解线程,那么高级库的API肯定会更加合理。
    • 单线程算法: 不要忘记简单单线程的优点。对于大多数应用程序来说,单线程比多线程更容易理解,也比多线程出错的几率小得多。实际上,在许多情况下,它可能是恰当的设计选择。事实是,真正的应用程序经历了各种多线程和单线程阶段:并非总是需要使用多线程。

    哪个更快? 令人惊讶的真相是,以上三者中任何一个都可能是最快的。要获得多线程的速度优势,您可能需要彻底重新组织自己的算法。利弊是否超过成本高度依赖于具体情况。

    哦,而且OP问到了不适合使用线程池的情况。 简单的情况是:如果您有一个紧密的循环,每个循环不需要很多计算周期,则使用线程池可能比没有严重重构带来的好处还要昂贵。 还要注意通过线程池调用lambda等函数调用的开销与使用单个紧密循环的开销之间的差异。

    对于大多数应用程序而言,多线程是一种优化方式,因此请在正确的时间和正确的位置进行。


    15

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

    在最基本的层面上,存在线程,pthread是线程的POSIX API,因此您可以在任何兼容的操作系统中编写代码并期望其正常工作。 GCD建立在线程之上(尽管我不确定他们是否实际上使用了pthread作为API)。 我相信GCD仅适用于OS X和iOS - 这是它的主要缺点。

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


    1
    除了跨平台兼容性之外,使用GCD没有任何缺点(包括速度)吗? - Mike
    5
    实际上,速度是GCD相对于直接使用pthread的优势之一。即使像Apache那样在pthread中自行实现复杂的线程池,也很少能像GCD一样好,因为GCD对操作系统和底层硬件有更好的了解。 - slebetman
    4
    一旦线程被创建,操作系统将视它们为任何其他线程一样,无论是哪个库创建的都不会有区别。因此,GCD 和 Pthreads 一旦被创建后,运行速度将完全相同。现在,如果 GCD 实现了自己的互斥锁,则其可能比手动使用 pthread 互斥锁更快或更慢(这主要取决于编程)。GCD 管理线程的创建和取消本身,因此可能提供最佳解决方案;这同样也适用于 pthreads,只不过程序员需要使用 pthreads 自己负责创建/取消线程,并且可以潜在地优化应用程序。 - Chris S
    3
    根据苹果的文档,GCD 的真正优点在于它的“管理”功能。手动调整和优化线程池是相当困难的,而 GCD 可以做到。苹果的 GCD 实现与内核集成,而不是基于 pthreads。 - eonil
    另一个微妙的问题是,GCD线程实际上并没有实现所有pthread语义。有一堆pthread_*调用只被标记为“不要从GCD队列中调用此函数”。我不知道这是否会加速(大多数情况下只是为了让线程池能够正确管理它们),但违反pthread语义的意愿在某些情况下可能会有所帮助。另一方面,GCD的优先级系统不像pthread那样细粒度。 - Catfish_Man
    显示剩余3条评论

    4

    GCD是苹果公司的一项技术,不太跨平台兼容;而pthread则几乎在所有操作系统上都可用,包括这个烤面包机

    GCD针对线程池并行性进行了优化。Pthreads(正如您所说)是用于并行处理的非常复杂的构建块,您需要自己开发模型。如果您有兴趣了解更多关于pthread和不同并行模型的知识,我强烈建议您阅读相关书籍。


    我并不太关心跨平台兼容性。毕竟,GCD是开源的,FreeBSD已经采用了它(尽管是一个不完美的实现)。我主要关注编写并行程序。有哪些情况下线程池并行不适用?我对具体的例子特别感兴趣。 - Mike
    1
    FreeBSD正在积极移植CGD(实际上是libdispatch,由于它不是代码,因此您无法完全“移植”商标)。在编译器方面,苹果已经提交了补丁到gcc以支持闭包语法,从而支持GCD。 - slebetman
    线程池并行处理对于小型/短期工作负载非常有效。许多Web服务器都是这样构建的,因为大多数页面请求涉及最少的处理。 对于具有不同用户角色(考虑输入和输出方式,而不是人员用户)的应用程序,您可能希望使用非常不同的线程来处理每个角色。例如,您的应用程序可能具有磁盘缓存管理线程、用户连接线程和处理队列线程。这将使您的程序能够利用多核处理器。如果GCD正在执行您需要的操作,请坚持使用它,它更简单。Pthreads具有更多选项和复杂性。 - Chris S
    对于你的例子,使用GCD的事件监听器(带有适当的文件描述符)而不是分配单独的线程会更有意义吧? - Mike
    好的,假设你只需要例子中提到的三个线程。使用GCD,你基本上需要一个处理程序来确定你想执行哪种类型的函数,然后调用正确的函数。同时实现异步IO可能会变得有趣。 - Chris S

    4

    OpenMPIntel TBB这样的声明性/辅助方法一样,GCD应该非常擅长于尴尬地并行问题,并且很可能轻松击败天真的手动pthread并行排序。我建议你仍然学习pthread。你会更好地理解并发性,能够在每个特定情况下应用正确的工具,而且如果仅仅是因为有大量基于pthread的代码存在,你也可以阅读“遗留”代码。


    0
    GCD抽象线程并提供调度队列。它根据可用的处理器核心数量在必要时创建线程。 GCD是开源的,并可通过libdispatch库获取。FreeBSD自8.1版本起包含libdispatch。GCD和C Blocks是Apple对C编程社区的重大贡献。我绝不会使用不支持GCD的任何操作系统。

    0

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


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接