帮我理解F#线程

时间:2010-12-29 02:50:07

标签: multithreading f# asynchronous mono

在使用一些F#(通过MonoDevelop)时,我编写了一个例程,用一个线程列出目录中的文件:

let rec loop (path:string) = 
  Array.append
    (
        path |> Directory.GetFiles
    )
    (
        path 
        |> Directory.GetDirectories
        |> Array.map loop
        |> Array.concat
    )

然后是它的异步版本:

let rec loopPar (path:string) = 
  Array.append
    ( 
        path |> Directory.GetFiles
    )
    ( 
        let paths = path |> Directory.GetDirectories
        if paths <> [||] then
            [| for p in paths -> async { return (loopPar p) } |]
            |> Async.Parallel
            |> Async.RunSynchronously 
            |> Array.concat
        else 
            [||]
    ) 

在小目录上,异步版本工作正常。在较大的目录(例如数千个目录和文件)上,异步版本似乎挂起了。我错过了什么?

我知道创建数千个线程永远不会是最有效的解决方案 - 我只有8个CPU - 但我感到困惑的是,对于较大的目录,异步功能只是没有响应(即使在半小时后)。然而,它并没有明显地失败,这令我感到困惑。是否有一个耗尽的线程池?

这些线程如何实际运作?

修改

根据this document

  

Mono&gt; = 2.8.x有一个新的线程池,它更难以死锁。如果你得到一个线程池死锁,你的程序可能会陷入僵局。

:d

2 个答案:

答案 0 :(得分:6)

是的,很有可能你压倒了Mono线程池,这会使你的系统性能停滞不前。

如果你还记得一件事,那就是线程昂贵。每个线程都需要自己的堆栈(大小为兆字节)和CPU时间片(需要上下文切换)。因此,为短期任务启动自己的线程并不是一个好主意。这就是.NET有一个ThreadPool的原因。

ThreadPool是用于短任务的现有线程集合,它是异步工​​作流的F#用户。每当您运行F#异步操作时,它只是将操作委托给线程池。

问题是,当您在F#中同时生成数千个异步操作时会发生什么?一个简单的实现只会根据需要生成尽可能多的线程。但是,如果您需要1,000个线程,则意味着您需要1,000 x 4MB的堆栈空间。即使你有足够的内存用于所有堆栈,你的CPU也会不断地在不同的线程之间切换。 (并在内存中分页本地堆栈。)

IIRC,Windows .NET实现足够聪明,不会产生大量线程,只是排队工作直到有一些备用线程来执行操作。换句话说,它会继续添加线程,直到它有一个固定的数字,并只使用它们。但是,我不知道Mono的线程池是如何实现的。

tl; dr:这是按预期工作的。

答案 1 :(得分:0)

克里斯可能是对的。另一个需要考虑的角度是文件系统不是固定的东西 - 当你试图处理列表时,那些有数千个文件变化的目录是什么?如果是这样,那可能会在某处造成竞争条件。