多线程设计最佳实践

Posted

技术标签:

【中文标题】多线程设计最佳实践【英文标题】:Multithreading Design Best Practice 【发布时间】:2010-09-05 19:56:16 【问题描述】:

考虑这个问题:我有一个程序应该从数据库中获取(比如说)100 条记录,然后对于每条记录,它都应该从 Web 服务中获取更新的信息。在这种情况下,有两种方法可以引入并行性:

    我在新线程上启动对 Web 服务的每个请求。同时线程的数量由一些外部参数控制(或以某种方式动态调整)。

    我创建较小的批次(假设每个 10 条记录)并在单独的线程上启动每个批次(以我们的示例为例,10 个线程)。

哪种方法更好,您为什么这么认为?

【问题讨论】:

动态/可配置,因为最佳数量完全取决于环境和瓶颈的实际情况。 这听起来像是 ThreadPool 的工作。只需将作业排队,然后让 .net 处理其余的工作。 @Patrick 好吧,我在考虑动态控制方面的线程池。但我想我想弄清楚这两种方法之间是否存在任何性能差异(实际上,ThreadPool 可以在这两种方法中使用)。如果不是性能,是否应该遵循任何最佳实践。 【参考方案1】:

选项 3 是最好的:

使用异步 IO。

除非您的请求处理复杂且繁重,否则您的程序将花费 99% 的时间等待 HTTP 请求。

这正是 Async IO 的设计目的 - 让 windows 网络堆栈(或 .net 框架或其他)担心所有等待,只需使用单个线程来分派和“获取”结果。

不幸的是,.NET 框架使它成为一个真正的痛苦。如果您只使用原始套接字或 Win32 api,它会更容易。这是一个使用 C#3 的(经过测试的!)示例:

using System.Net; // need this somewhere

// need to declare an class so we can cast our state object back out
class RequestState 
    public WebRequest Request  get; set; 


static void Main( string[] args ) 
    // stupid cast neccessary to create the request
    HttpWebRequest request = WebRequest.Create( "http://www.***.com" ) as HttpWebRequest;

    request.BeginGetResponse(
        /* callback to be invoked when finished */
        (asyncResult) =>  
            // fetch the request object out of the AsyncState
            var state = (RequestState)asyncResult.AsyncState; 
            var webResponse = state.Request.EndGetResponse( asyncResult ) as HttpWebResponse;

            // there we go;
            Debug.Assert( webResponse.StatusCode == HttpStatusCode.OK ); 

            Console.WriteLine( "Got Response from server:" + webResponse.Server );
        ,
        /* pass the request through to our callback */
        new RequestState  Request = request   
    );

    // blah
    Console.WriteLine( "Waiting for response. Press a key to quit" );
    Console.ReadKey();

编辑:

对于 .NET,“完成回调”实际上是在 ThreadPool 线程中触发的,而不是在您的主线程中,因此您仍然需要锁定任何共享资源,但它仍然为您省去了管理的所有麻烦线程。

【讨论】:

您真的需要使用状态对象传递请求还是可以将请求用作闭包绑定变量?【参考方案2】:

需要考虑两件事。

1。处理记录需要多长时间?

如果记录处理非常快,将记录移交给线程的开销可能会成为瓶颈。在这种情况下,您可能希望捆绑记录,这样您就不必经常交出它们。

如果记录处理运行时间相当长,则差异可以忽略不计,因此更简单的方法(每个线程 1 条记录)可能是最好的。

2。您打算启动多少线程?

如果您不使用线程池,我认为您要么需要手动限制线程数,要么需要将数据分成大块。如果记录数量变大,为每条记录启动一个新线程会使您的系统崩溃。

【讨论】:

是的,这些都是有用的考虑因素。由于这是调用公共域 Web 服务,所以我想我们可能想要运行一些测试来查看开销是否超过工作本身(我对此表示怀疑)。是的,我们肯定会考虑使用 ThreadPool。【参考方案3】:

运行程序的计算机可能不是瓶颈,所以: 请记住,HTTP 协议有一个 keep-alive 标头,它可以让您在同一个套接字上发送多个 GET 请求,从而避免与 TCP/IP 握手。不幸的是,我不知道如何在 .net 库中使用它。 (应该可以。)

回复您的请求可能也会有所延迟。您可以尝试确保始终有给定数量的服务器未完成请求。

【讨论】:

【参考方案4】:

获取Parallel Fx。查看 BlockingCollection。使用一个线程为其提供成批的记录,并使用 1 到 n 个线程将记录从集合中拉出以提供服务。您可以控制收集收集的速率,以及调用 Web 服务的线程数。通过 ConfigSection 使其可配置,并通过提供集合 Action 委托使其通用,您将拥有一个不错的小批处理器,您可以重用您的心脏内容。

【讨论】:

以上是关于多线程设计最佳实践的主要内容,如果未能解决你的问题,请参考以下文章

C++最佳实践 | 5. 可移植性及多线程

C++最佳实践 | 5. 可移植性及多线程

多线程处理数据库记录的最佳实践

JMS 队列上多线程消息处理的最佳实践

Android最佳实践之性能 - 多线程

Java 中,编写多线程程序的时候你会遵循哪些最佳实践?