线程的替代品

时间:2010-02-05 22:33:35

标签: c# multithreading

我读过线程非常有问题。有哪些替代品?什么能自动处理阻塞和填充?

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

任何人都在关心解释“简单”的选择吗?用户可以选择要使用的线程数(取决于它们的速度需求和计算机功率)。

有什么想法吗?

9 个答案:

答案 0 :(得分:6)

总结线程的问题:

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

竞赛的一个例子:假设两个线程共享一些存储号码的内存的访问权限。线程1从存储器地址读取并将其存储在CPU寄存器中。线程2也是如此。现在,线程1递增数字并将其写回内存。线程2然后做同样的事情。结果:数字只增加1,而两个线程都试图递增它。这种相互作用的结果取决于时间。更糟糕的是,你的代码可能看起来没有错误,但是在蓝色的月亮中,时机错了,坏事发生了。

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

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

答案 1 :(得分:5)

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

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

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

  • 声明性方法(.NET 4.0中的PLINQ是一个很好的选择)如果您有一些可以使用LINQ基元编码的更高级别的数据处理操作,那么您可以使用此技术。运行时将自动并行化您的代码,因为LINQ没有指定完全应该如何评估结果(您只需要说明您希望得到的结果)。

  • 消息传递允许您将两个写入程序作为并发运行的进程执行某些(相对简单的)任务,并通过相互发送消息进行通信。这很棒,因为您可以在没有通常的同步问题的情况下共享某些状态(发送消息)(您只需发送消息,然后执行其他操作或等待消息)。这是Robert Pickering的good introduction to message-passing in F#

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

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

答案 2 :(得分:2)

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

Parallel Computing Developer Center的MSDN link链接到Parellel Programming的大量信息,包括视频链接等。

答案 3 :(得分:1)

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

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

  • 线程数根据池中线程的工作负载动态变化。
  • 工作项可以返回值。
  • 可以取消工作项目。
  • 执行工作项(限制)时使用调用者线程的上下文。
  • 使用最少数量的Win32事件句柄,因此应用程序的句柄数不会爆炸。
  • 来电者可以等待多个或所有工作项完成。
  • 工作项可以有一个PostExecute回调,工作项完成后会立即调用。
  • 工作项目附带的状态对象可以自动处理。
  • 将工作项例外发送回来电。
  • 工作项目具有优先权。
  • 工作项目组。
  • 调用者可以暂停线程池和工作项组的启动。
  • 主题优先。
  • 可以运行具有单线程单元的COM对象。
  • 支持操作和Func代表。
  • 支持WindowsCE(限制版)
  • 可以在运行时更改MaxThreads和MinThreads。
  • 取消行为得到了改善。

答案 4 :(得分:1)

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

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

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

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

答案 5 :(得分:1)

每当你引入多个线程时,每次都会运行,你就可以开启race conditions的潜力。要避免这些,您往往需要添加同步,这会增加复杂性,以及deadlocks的潜力。

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

.NET 4将为解决这个问题做很多事情。 Task Parallel LibraryPLINQ极大地方便了多线程的使用。

至于你上次的评论:

  

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

.NET中的大多数例程都是基于ThreadPool构建的。在.NET 4中,当使用TPL时,工作负载实际上会在运行时扩展,从而消除了必须指定要使用的线程数的负担。但是,现在有办法做到这一点。

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

答案 6 :(得分:0)

如果您了解导致问题的原因,则线程不会有问题。

对于前。如果您避免静态,您知道要使用哪个API(例如使用synchronized streams),您将避免因使用不当而导致的许多问题。

答案 7 :(得分:0)

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

答案 8 :(得分:0)

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

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

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