【问题标题】:Alternative to Threads线程的替代品
【发布时间】:2011-01-13 17:36:16
【问题描述】:

我读到线程是非常有问题的。有哪些替代方案?自动处理阻塞和填充的东西?

很多人推荐后台工作,但我不知道为什么。

有人愿意解释“简单”的替代方案吗?用户将能够选择要使用的线程数(取决于他们的速度需求和计算机能力)。

有什么想法吗?

【问题讨论】:

  • 您能否详细说明您认为线程问题如此严重的原因?
  • 为什么让用户决定使用多少线程是个好主意?您打算如何训练用户判断 3 个线程是优于还是低于 10 个线程?
  • “我已经读到线程非常有问题” - 听起来你不是 100% 熟悉线程(或后台工作线程)是什么以及如何正确使用它们。这个链接很好地概述了线程和一些很好的开始信息。 msdn.microsoft.com/en-us/library/aa645740(VS.71).aspx .Net 4.0 具有并行编程扩展,但它仍然是线程,并且与何时使用它的许多相同原因适用。让用户调整线程也不总是最好的主意。
  • 您的意思是“线程”是指“并行运行程序部分的通用编程概念”还是“特定线程库/实现”?如果是前者,那么就没有“替代方案”了。 “后台工作者”是一种特定类型的线程,而“阻塞”无非是一个线程等待另一个线程完成某事。如果是后者,请编辑您的问题以提及您想知道的特定库/实现。

标签: c# multithreading


【解决方案1】:

.Net 4 中的并行编程选项不是使用线程的“简单”方式吗?我不确定我对 .Net 3.5 及更早版本有何建议...

这个MSDN link并行计算开发者中心有很多关于并行编程信息的链接,包括视频链接等。

【讨论】:

  • 虽然System.Threading.Tasks 将向 .NET 框架添加一些很棒的新多线程工具,但它并不能神奇地保护您免受竞争条件的影响。有点像给别人一把机关枪而不是普通枪来射击自己的脚。
  • 更像是机关枪而不是火枪。更复杂、更容易使用、不太可能在你的脸上爆炸,而且同样危险。
【解决方案2】:

我可以推荐这个项目。 Smart Thread Pool

项目描述 Smart Thread Pool 是用 C# 编写的线程池。它比 .NET 内置线程池先进得多。 以下是线程池功能列表:

  • 线程数根据池中线程的工作负载动态变化。
  • 工作项可以返回一个值。
  • 可以取消工作项。
  • 执行工作项时使用调用者线程的上下文(受限)。
  • 使用最小数量的 Win32 事件句柄,因此应用程序的句柄计数不会爆炸。
  • 调用者可以等待多个或所有工作项完成。
  • 工作项可以有一个 PostExecute 回调,该回调会在工作项完成后立即调用。
  • 可以自动处置伴随工作项的状态对象。
  • 工作项异常被发送回调用者。
  • 工作项具有优先权。
  • 工作项组。
  • 调用者可以暂停线程池和工作项组的启动。
  • 线程具有优先级。
  • 可以运行具有单线程单元的 COM 对象。
  • 支持 Action 和 Func 委托。
  • 支持 WindowsCE(有限)
  • MaxThreads 和 MinThreads 可以在运行时更改。
  • 改进了取消行为。

【讨论】:

  • 假设有问题的部分是碰撞,这似乎根本没有解决它们,只是一些易用性问题。
【解决方案3】:

如果您了解导致线程出现问题的原因,线程就不会出现问题。

例如。如果您避免静态,您知道要使用哪些 API(例如使用 synchronized streams),您将避免许多因使用不当而出现的问题。

【讨论】:

  • 我喜欢在 Synchronized 上使用我自己的锁,因为它可以让我更好地控制流程:stackoverflow.com/questions/338712/…
  • 判断对错:老虎并不危险,只要您知道如何对付它们。我说老虎天生就很危险。
  • 如果你理解线程,那么根据理智的定义,你是不理智的。线程被称为非确定性是有原因的......
【解决方案4】:

如果线程是一个问题(如果您有不安全/不受管理的第 3 方 dll 不能支持多线程,则可能会发生这种情况。在这种情况下,可以选择创建一个机制来对操作进行排队。即将操作的参数存储到数据库,一次只运行一个。这可以在 Windows 服务中完成。显然这将花费更长的时间,但在某些情况下是唯一的选择。

【讨论】:

    【解决方案5】:

    总结线程的问题:

    • 如果线程共享内存,你可以获得 比赛条件
    • 如果您通过大量使用锁来避免竞争,您 可能会出现死锁(请参阅dining philosophers problem

    比赛的一个例子:假设两个线程共享对存储数字的内存的访问。线程 1 从内存地址读取并将其存储在 CPU 寄存器中。线程 2 也是如此。现在线程 1 增加数字并将其写回内存。线程 2 然后做同样的事情。最终结果:数字只增加了 1,而两个线程都试图增加它。这种互动的结果取决于时间。更糟糕的是,您的代码可能看起来没有错误,但一旦出现问题,时机就错了,坏事就会发生。

    要避免这些问题,答案很简单:避免共享可写内存。相反,使用消息传递在线程之间进行通信。一个极端的例子是将线程放在不同的进程中,并通过 TCP/IP 连接或命名管道进行通信。

    另一种方法是只共享只读数据结构,这就是functional programming 语言可以很好地与多线程一起工作的原因。

    【讨论】:

      【解决方案6】:

      “有问题”不是我用来描述使用线程的词。 “乏味”是更恰当的描述。

      如果您不熟悉线程编程,我建议您先阅读this thread。它绝不是详尽无遗的,但有一些很好的介绍性信息。从那里开始,我将继续搜索该网站和其他编程网站,以获取与您可能遇到的特定线程问题相关的信息。

      至于 C# 中的特定线程选项,这里有一些关于何时使用每个选项的建议。

      • 如果您有一个在后台运行且需要与 UI 交互的任务,请使用 BackgroundWorker。将数据和方法调用编组到 UI 线程的任务通过其基于事件的模型自动处理。如果 (1) 您的程序集尚未引用 System.Windows.Form 程序集,(2) 您需要线程成为前台线程,或者 (3) 您需要操纵线程优先级,请避免使用 BackgroundWorker。
      • 如果需要提高效率,请使用ThreadPool 线程。 ThreadPool 有助于避免与创建、启动和停止线程相关的开销。如果 (1) 任务在应用程序的生命周期内运行,(2) 您需要线程成为前台线程,(3) 您需要操作线程优先级,或者 (4) 您需要线程,请避免使用 ThreadPool具有固定的身份(中止、暂停、发现)。
      • Thread 类用于长时间运行的任务以及当您需要正式线程模型提供的功能时,例如,在前台和后台线程之间进行选择、调整线程优先级、对线程执行进行细粒度控制等。

      【讨论】:

        【解决方案7】:

        任何时候你引入多个线程,每个线程同时运行,你就打开了race conditions 的潜力。为了避免这些,您往往需要添加同步,这会增加复杂性以及deadlocks 的可能性。

        许多工具使这更容易。 .NET 有很多类专门用于减轻处理多线程的痛苦,包括 BackgroundWorker 类,它使运行后台工作和与用户界面交互变得更加简单。

        .NET 4 将做很多工作来进一步缓解这个问题。 Task Parallel LibraryPLINQ 极大地简化了多线程的工作。

        至于你最后的评论:

        用户将能够选择要使用的线程数(取决于他们的速度需求和计算机能力)。

        .NET 中的大多数例程都建立在ThreadPool 之上。在 .NET 4 中,使用 TPL 时,工作负载实际上会在运行时扩展,为您消除了必须指定要使用的线程数的负担。不过,现在有办法做到这一点。

        目前,您可以使用ThreadPool.SetMaxThreads 来帮助限制生成的线程数。在 TPL 中,您可以指定 ParallelOptions.MaxDegreesOfParallelism,并将 ParallelOptions 的一个实例传递给您的例程来控制它。当您添加更多处理内核时,默认行为会随着更多线程而扩大,这通常是任何情况下的最佳行为。

        【讨论】:

          【解决方案8】:

          这是一个更高级别的答案,但如果您想考虑线程的其他替代方案,它可能会很有用。无论如何,大多数答案都讨论了基于线程(或线程池)或可能来自 .NET 4.0 的任务的解决方案,但还有另一种选择,称为 message-passing。这已在 Erlang(爱立信使用的一种函数式语言)中成功使用。由于函数式编程在这些日子里变得越来越主流(例如 F#),我想我可以提一下。一般来说:

          • 线程(或线程池)通常可​​以在您进行一些相对长时间运行的计算时使用。当它需要与其他线程共享状态时,它会变得很棘手(您必须正确使用锁或其他同步原语)。

          • 任务(在 .NET 4.0 的 TPL 中可用)非常轻量级 - 您可以将程序拆分为数千个任务,然后让运行时运行它们(它将使用最佳数量的线程)。如果您可以使用任务而不是线程来编写算法,这听起来是个好主意 - 当您使用较小的步骤运行计算时,您可以避免一些同步。

          • 声明性方法(.NET 4.0 中的 PLINQ 是一个很好的选择)如果您有一些可以使用 LINQ 原语进行编码的高级数据处理操作,那么您可以使用这种技术.运行时会自动并行化您的代码,因为 LINQ 没有指定确切它应该如何评估结果(您只需说出想要得到的结果)。

          • 消息传递 允许您将两个程序编写为同时运行的进程,这些进程执行一些(相对简单的)任务并通过相互发送消息进行通信。这很棒,因为您可以共享一些状态(发送消息)而不会出现通常的同步问题(您只需发送一条消息,然后做其他事情或等待消息)。这是来自罗伯特·皮克林的good introduction to message-passing in F#

          请注意,最后三种技术与函数式编程非常相关 - 在函数式编程中,您以不同的方式设计程序 - 作为返回结果的计算(这使得 Tasks 更易于使用)。您还经常编写声明性和更高级别的代码(这使得声明性方法更容易使用)。

          在实际实现方面,F# 在核心库中拥有出色的消息传递库。在 C# 中,您可以使用Concurrency & Coordination Runtime,感觉有点“hacky”,但可能也很强大(但可能看起来太复杂)。

          【讨论】:

            【解决方案9】:

            线程是解决许多问题不可或缺的工具,成熟的开发人员应该知道如何有效地使用它们。但像许多工具一样,它们可能会导致一些非常难以发现的错误。

            不要仅仅因为它会导致问题而回避一些如此有用的东西,而是要学习和练习,直到你成为多线程应用程序的首选。

            Joe Albahari 的文章是一个很好的起点:http://www.albahari.com/threading/

            【讨论】:

              猜你喜欢
              • 2017-10-31
              • 2011-07-16
              • 1970-01-01
              • 1970-01-01
              • 2017-08-03
              • 1970-01-01
              • 2016-03-30
              • 2018-11-15
              • 1970-01-01
              相关资源
              最近更新 更多