关于多线程:何时多线程不是一个好主意?

关于多线程:何时多线程不是一个好主意?

When is multi-threading not a good idea?

我最近正在开发一个通过以太网和串行发送和接收消息的应用程序。 然后,我受命添加对DIO离散量的监视。 我说

"No reason to interrupt the main
thread which is involved in message
processing, I'll just create
another thread that monitors DIO."

然而,这一决定证明是糟糕的。 有时主线程会在发送和接收串行消息之间中断。 这种中断会打乱时间,而且消息会丢失(永远)。

我找到了另一种无需使用其他线程即可监视DIO的方法,并且以太网和串行通信已恢复为正确的功能。

然而,整个惨败让我思考。 他们是否有关于何时不使用多线程的一般指导方针,和/或在使用多线程不是一个好主意的情况下,是否有人再有其他示例?

**编辑:基于您的评论,并且在浪费互联网的信息之后,我撰写了一篇博客文章,标题为"多线程何时不是一个好主意?


  • 在单处理器计算机和桌面应用程序上,您使用多线程,因此您不会冻结该应用程序,但实际上没有其他用途。
  • 在单处理器服务器和基于Web的应用程序上,不需要多线程,因为Web服务器可以处理大多数线程。
  • 建议在多处理器计算机和桌面应用程序上使用多线程和并行编程。使线程与处理器数量一样多。
  • 在多处理器服务器和基于Web的应用程序上,无需再次使用多线程,因为Web服务器可以处理它。
  • 总体而言,如果您将多个线程用于非冻结桌面应用程序和任何其他通用答案,则如果您使用的是单核计算机,则由于线程彼此中断,会使应用程序变慢。

    为什么?因为有硬件开关。硬件总共要花费时间在线程之间进行切换。在多核设备上,继续并为每个核使用1个线程,您将大大看到上升的趋势。


    改写一句老话:程序员遇到了问题。他想:"我知道,我会使用线程。"现在程序员有两个问题。 (通常归因于JWZ,但似乎早于他使用正则表达式时使用它。)

    一个好的经验法则是"不要使用线程,除非有非常令人信服的理由使用线程。"多个线程正在自找麻烦。尝试找到一种无需使用多个线程即可解决该问题的好方法,并且只有避免使用该线程会比使用线程付出额外的精力多,否则只能使用线程。另外,如果您在多核/多CPU的计算机上运行,??请考虑切换到多个线程,并且单线程版本的性能测试表明您需要额外的内核的性能。


    多线程是一个坏主意,如果:

    • 多个线程访问和更新相同的资源(设置变量,写入文件),并且您不了解线程安全性。

    • 多个线程彼此交互,您不了解互斥锁和类似的线程管理工具。

    • 您的程序使用静态变量(默认情况下,线程通常共享它们)。

    • 您尚未调试并发问题。


    实际上,多线程是不可扩展的,并且很难调试,因此在任何情况下都可以避免使用多线程。在少数情况下,强制执行是必不可少的:当多CPU的性能很重要时,或者当您处理的服务器上有很多客户端需要很长时间才能回答时。

    在任何其他情况下,都可以使用诸如队列+ cron作业之类的替代方法。


    多线程是不好的,除非在单线程情况下是好的。这种情况是

  • 作品是CPU绑定,或部分是CPU绑定
  • 这项工作是可并行的。
  • 如果缺少这两个条件之一或全部,则多线程将不是一个成功的策略。

    如果工作不受CPU约束,则您不是在等待线程完成工作,而是等待某些外部事件(例如网络活动)来完成该过程。使用线程,会在线程之间增加上下文切换的开销,同步的开销(互斥变量等)以及线程抢占的不规则性。最常见的替代方法是异步IO,其中单个线程侦听多个io端口,并在现在碰巧准备就绪的任何一个上进行操作,一次一次。如果偶然地所有这些慢速通道都同时准备就绪,则似乎您会遇到速度变慢的情况,但实际上这很少是正确的。通常,单独处理每个端口的成本与清空每个通道时同步多个线程上的状态的成本相比或更高。

    许多任务可能是受计算限制的,但使用多线程方法仍然不切实际,因为该过程必须在整个状态上进行同步。这样的程序无法从多线程中受益,因为无法同时执行任何工作。幸运的是,大多数需要大量CPU的程序都可以并行化到某个级别。


    您可能想看看Dan Kegel的" C10K问题"网页,该网页处理多个数据源/接收器。

    基本上,最好使用最少的线程,该线程可以在大多数操作系统的某些事件系统中(或者在Windows中使用IOCP异步执行)在套接字中完成。

    当您遇到操作系统和/或库不提供非阻塞方式进行通信的情况时,最好在报告回到同一事件循环时使用线程池来处理它们。

    布局示例图:

    1
    2
    3
    Per CPU [*] EVENTLOOP   ------ Handles nonblocking I/O using OS/library utilities
                           |                        \___  Threadpool for various blocking events
                           Threadpool for handling the I/O messages that would take long


    我写的一个最近的应用程序必须使用多线程(尽管线程数不是无限的),我必须通过两个协议在多个方向上进行通信,还要监视第三个资源的更改。这两个协议库都需要一个线程来运行相应的事件循环,并且在考虑了这些事件后,很容易为资源监视创建第三个循环。除了事件循环要求之外,通过线路传递的消息还具有严格的时序要求,一个循环不会冒阻塞另一个循环的风险,而使用多核CPU(SPARC)可以进一步缓解这种情况。

    关于是否应该将每个消息处理视为线程池中分配给线程的工作,还有进一步的讨论,但最后这是一个扩展,不值得做。

    如果可能,仅在可以将工作划分为定义良好的工作(或一系列工作)时才考虑所有线程,以使语义相对易于记录和实现,并且可以对线程设置上限。您使用且需要进行交互的线程数。最好应用此功能的系统几乎是消息传递系统。


    如果您需要保证精确的物理时序(例如您的示例),那么多线程不是一个好主意。其他缺点包括线程之间的密集数据交换。我要说的是,如果您不太在意它们的相对速度/优先级/定时,那么多线程对于真正的并行任务很有用。


    使用线程的更多可能原因:

  • 您的平台缺少异步I / O操作,例如Windows ME(没有完成端口或I / O重叠,在移植使用它们的XP应用程序时会很麻烦。)Java 1.3和更早版本。
  • 可以挂起的第三方库函数,例如如果远程服务器已关闭,并且该库无法取消操作,则无法对其进行修改。
  • 在密集处理期间保持GUI响应并不总是需要其他线程。一个回调函数通常就足够了。

    如果以上方法均不适用,并且由于某些原因我仍然希望并行化,那么我更愿意在可能的情况下启动一个独立的进程。


    我会说多线程通常用于:

  • 当GUI保持响应时,允许在后台进行数据处理
  • 将非常大的数据分析拆分为多个处理单元,以便更快地获得结果。
  • 当您从某些硬件接收数据并且需要一些东西来将其连续添加到缓冲区中时,而其他元素则决定如何处理它(写入磁盘,在GUI上显示等)。
  • 因此,如果您不解决这些问题之一,则添加线程不太可能使您的生活更轻松。实际上,几乎可以肯定,它将变得更加困难,因为正如其他人所提到的;调试多线程应用程序比单线程解决方案的工作量大得多。

    安全性可能是避免使用多个线程(在多个进程上)的原因。有关多进程安全功能的示例,请参见Google chrome。


    原则上,每次调用者在队列中等待时都没有开销。


    多线程具有可伸缩性,可以让您的UI保持其响应能力,同时在后台执行非常复杂的事情。我不了解其他响应在哪里获取有关多线程的信息。

    什么时候不应该使用多线程,这是对您的问题的误导性问题。您的问题是这样的:为什么对我的应用程序进行多线程处理会导致串行/以太网通信失败?

    该问题的答案将取决于实现,应在另一个问题中进行讨论。我知道一个事实,即可以在多线程应用程序中同时进行以太网和串行通信,而无需执行任何其他任务,而不会造成任何数据丢失。

    不使用多线程的原因之一是:

  • 只有一项任务,没有用户界面会干扰任务。
  • 使用多线程的原因有:

  • 为用户提供出色的响应能力
  • 同时执行多个任务以减少整体执行时间
  • 使用更多当前的多核CPU,以及将来的多核CPU。
  • 多线程编程的三种基本方法可以轻松实现线程安全-您只需使用一种方法即可成功:

  • 线程之间传递的线程安全数据类型。
  • 线程对象中的线程安全方法用于修改在数据之间传递的数据。
  • PostMessage在线程之间进行通信的功能。

  • 线程问题的常见根源是用于同步数据的常用方法。让线程共享状态,然后在所有适当的位置实现锁定是设计和调试复杂性的主要来源。获得锁定权以平衡稳定性,性能和可伸缩性始终是一个很难解决的问题。即使是最有经验的专家也会经常出错。处理线程的替代技术可以减轻这种复杂性。 Clojure编程语言实现了一些用于处理并发的有趣技术。


    流程是否平行?性能真的值得关注吗?是否像Web服务器一样有多个执行"线程"?我认为答案是有限的。


    推荐阅读