Windows服务的并行编程

3

我有一个Windows服务,代码类似于以下内容:

List<Buyer>() buyers = GetBuyers();
var results = new List<Result();

Parallel.Foreach(buyers, buyer =>
{
    // do some prep work, log some data, etc.

    // call out to an external service that can take up to 15 seconds each to return
    results.Add(Bid(buyer));    
}

// Parallel foreach must have completed by the time this code executes
foreach (var result in results)
{
    // do some work
}

这份代码运行良好,但我认为我们存在可拓展性问题。我们每分钟平均有20-30个入站连接,并且每个连接都会触发这段代码。对于每个入站连接,“购买者”集合中可能会有1-15个购买者。偶尔我们的入站连接数会激增到每分钟100多个,导致服务器停滞。
两台负载均衡8核服务器的 CPU 使用率仅约为50%,但线程数继续增加(在进程上达到350个),每个入站连接的响应时间从3-4秒变为1.5-2分钟。
我怀疑以上代码是我们可拓展性问题的原因。鉴于此使用情况(用于 I/O 操作的并行性)在 Windows 服务(无 UI)上,Parallel.ForEach 是否是最佳方法?我没有太多异步编程经验,期望利用这个机会学习更多,想从这里开始获取一些社区建议来补充我在 Google 上找到的信息。

在任何给定的时刻,平均有多少个请求正在执行? - Yacoub Massad
3
这似乎不是很好地使用了 Parallel.For,因为大部分线程将会被阻塞,导致线程池创建新的线程。如果是IO操作,请使用异步操作。 - Brian Rasmussen
减少MaxDegreeOfParallelism会有助于Parallel.ForEach(list, new ParallelOptions() { MaxDegreeOfParallelism = Environment.ProcessorCount, }, parallelAction);吗? - ziddarth
1个回答

3
Parallel.Foreach存在一个严重的设计缺陷。随着时间的推移,它很容易消耗所有可用的线程池资源。它将生成的线程数量实际上是无限制的。你可以通过没有人能理解的启发式方法每秒获得多达2个新线程。CoreCLR内置了一个爬坡算法,但它根本不起作用。

调用外部服务

也许,你应该找出调用该服务的正确并行度。你需要通过测试不同的数量来找出。然后,你需要限制Parallel.Foreach最多只生成您想要的线程数。你可以使用固定并发TaskScheduler来做到这一点。或者,你可以将其改为使用异步IO并使用SemaphoreSlim.WaitAsync。这样就不会阻塞线程。通过这种方式解决了池枯竭和外部服务过载的问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接