在使用一些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 - 但我感到困惑的是,对于较大的目录,异步功能只是没有响应(即使在半小时后)。然而,它并没有明显地失败,这令我感到困惑。是否有一个耗尽的线程池?
这些线程如何实际运作?
修改
Mono&gt; = 2.8.x有一个新的线程池,它更难以死锁。如果你得到一个线程池死锁,你的程序可能会陷入僵局。
:d
答案 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)