Parallel.Foreach产生太多线程的方式

时间:2010-01-04 22:51:42

标签: .net f# parallel-processing task-parallel-library parallel-extensions

问题

虽然我在这里谈论的代码我用F#编写,但它基于.NET 4框架,并没有特别取决于F#的任何特殊性(至少看起来如此!)。

我的磁盘上有一些数据,我应该从网络更新,将最新版本保存到磁盘:

type MyData =
    { field1 : int;
      field2 : float }

type MyDataGroup =
    { Data : MyData[];
      Id : int }

// load : int -> MyDataGroup
let load dataId =
    let data = ... // reads from disk
    { Data = data;
      Id = dataId }

// update : MyDataGroup -> MyDataGroup
let update dg =
    let newData = ... // reads from the network and process
                      // newData : MyData[]

    { dg with Data = dg.Data
                     |> Seq.ofArray
                     |> Seq.append newData
                     |> processDataSomehow
                     |> Seq.toArray }

// save : MyDataGroup -> unit
let save dg = ... // writes to the disk

let loadAndSaveAndUpdate = load >> update >> save

问题是,对于loadAndSaveAndUpdate我的所有数据,我必须执行许多次函数:

{1 .. 5000} |> loadAndSaveAndUpdate

每一步都可以

  • 某些磁盘IO,
  • 一些数据处理,
  • 某些网络IO(可能存在大量延迟),
  • 更多数据处理,
  • 和一些磁盘IO。

在某种程度上并行完成并不是一件好事吗?不幸的是,我的阅读和解析功能都不是“async-workflows-ready”。

我想出的第一个(不是很好的)解决方案

任务

我做的第一件事就是设置Task[]并启动它们:

let createTask id = new Task(fun _ -> loadAndUpdateAndSave id)
let tasks = {1 .. 5000}
            |> Seq.map createTask
            |> Seq.toArray

tasks |> Array.iter (fun x -> x.Start())
Task.WaitAll(tasks)

然后我点击CTRL + ESC只是为了看看它使用了多少线程。 15,17,......,35,......,170,...直到杀死了申请!出了点问题。

并行

我做了几乎相同的事情,但使用Parallel.ForEach(...)并且结果是相同的:很多很多很多线程。

一种有效的解决方案......

然后我决定只开始n个帖子Task.WaitAll(of them),然后开始其他n,直到没有更多任务可用。

这样可行,但问题是,当它完成处理(例如n-1个任务)时,它会等待,等待,等待由于大量网络延迟而坚持阻止的该死的最后一个任务。这不好!

那么,你会如何解决这个问题?我很欣赏能够查看不同的解决方案,包括异步工作流(在这种情况下如何调整我的非异步函数),并行扩展,奇怪的并行模式等。

感谢。

4 个答案:

答案 0 :(得分:11)

ParallelOptions.MaxDegreeOfParallelism限制并行方法调用运行的并发操作数

答案 1 :(得分:10)

使用'async'可以让你在不加热线程的情况下进行I / O绑定工作,而各种I / O调用是'海上',所以这将是我的第一个建议。将代码转换为异步应该很简单,通常是沿着

  • 将每个职能部门包裹在async{...}中,并在必要时添加return
  • 通过Async.FromBeginEnd
  • 创建库中尚未存在的任何I / O基元的异步版本
  • let r = Foo()表单的来电切换为let! r = AsyncFoo()
  • 使用Async.Parallel将5000个异步对象转换为单个并行运行的异步

有各种各样的教程可以做到这一点;一个这样的网络直播是here

答案 2 :(得分:7)

您确定您的个人任务是否及时完成?我相信Parallel.ForEachTask类都已使用.NET线程池。任务通常应该是短期工作项,在这种情况下,线程池只会产生少量实际线程,但如果您的任务没有进展并且还有其他任务排队,那么使用的线程数将稳步增加到最大值(在.NET 2.0 SP1中默认为250/processor,但在不同版本的框架下有所不同)。值得注意的是(至少在.NET 2.0 SP1中)新的线程创建被限制为每秒2个新线程,因此达到您所看到的线程数量表明任务未在短时间内完成时间(因此将责任归咎于Parallel.ForEach)可能不完全准确。

我认为Brian建议使用async工作流是一个很好的建议,特别是如果长期任务的来源是IO,因为async会将你的线程返回到线程池直到IO完成。另一个选择是简单地接受你的任务没有快速完成并允许产生许多线程(可以通过使用System.Threading.ThreadPool.SetMaxThreads在某种程度上控制) - 根据你的情况,它可能不是一个大问题你使用了很多线程。

答案 3 :(得分:0)

您始终可以使用ThreadPool

http://msdn.microsoft.com/en-us/library/system.threading.threadpool.aspx

基本上是:

  1. 创建线程池
  2. 设置最大线程数
  3. 使用QueueUserWorkItem(WaitCallback)
  4. 排队所有任务