Hello World
Spiga

浅谈线程池(下):相关试验及注意事项

2009-10-20 00:06 by 老赵, 23615 visits

三个月,整整三个月了,我忽然发现我还有三个月前的一个小系列的文章没有结束,我还欠一个试验!线程池是.NET中的重要组件,几乎所有的异步功能依赖于线程池。之前我们讨论了线程池的作用、独立线程池的存在意义,以及对CLR线程池和IO线程池进行了一定说明。不过这些说明可能有些“抽象”,于是我们还是要通过试验来“验证”这些说明。此外,我认为针对某个“猜想”来设计一些试验进行验证是非常重要的能力,如果您这方面的能力略有不足的话,还是尽量加以锻炼并提高吧。

CLR线程的使用与创建

首先,我们准备这样一段代码:

public static void ThreadUseAndConstruction()
{
    ThreadPool.SetMinThreads(5, 5); // set min thread to 5
    ThreadPool.SetMaxThreads(12, 12); // set max thread to 12

    Stopwatch watch = new Stopwatch();
    watch.Start();

    WaitCallback callback = index =>
    {
        Console.WriteLine(String.Format("{0}: Task {1} started", watch.Elapsed, index));
        Thread.Sleep(10000);
        Console.WriteLine(String.Format("{0}: Task {1} finished", watch.Elapsed, index));
    };

    for (int i = 0; i < 20; i++)
    {
        ThreadPool.QueueUserWorkItem(callback, i);
    }
}

这段代码很简单。首先将线程池最小和最大线程数量设为5和12,然后向线程池中连续推入20个任务,每个任务都是打印出执行时的当前时间,然后等待10秒钟。那么请您思考一下,这段代码的输出是什么样的呢?

展开

高位的零我们就直接忽略了,我们只观察“秒”及以下精度的时间。对这个数据进行简单观察之后,我们发现可以把时间精确到0.5秒来描述每个时刻所发生的事情:

  1. 0秒:任务0至任务3,共计4个任务开始执行。
  2. 1至3秒:任务4至任务8依次执行,间隔为0.5秒。
  3. 3至6秒:任务8至任务11依次执行,间隔为1秒。
  4. 10秒:任务0至任务3执行完成,任务12至任务15开始执行。
  5. 11至12.5秒:每执行完一个旧任务(4至7),便立即开始一个新任务(16至19)。
  6. 13至22.5秒:剩余任务(8至19)依次结束。  

您猜对了吗?我没有猜对,因为有两点:

  • 原来最小线程数量为5时,只有4个线程可以立即执行。经过进一步尝试,最小线程数量为10时,也只有9个线程可以立即执行。
  • 原来线程池创建线程的速度并非永远是“每秒2个”,而一些资料上写着“每秒不超过2个”的确是确切的说法。

但是,我们还是验证了以下几个结论:

  • 在线程池最小线程数量的范围之内,尽可能多的任务立即执行。
  • 线程池使用使用每秒不超过2个的频率创建线程(1秒一个或0.5秒一个)。
  • 当达到线程池最大线程数时(第6秒),停止创建新线程。
  • 在旧任务执行完毕后,新任务立即执行。

当然,由于我们在这之前已经“了解”了线程池是如何工作的,因此这里得到的结果可能会有“自圆其说”的倾向在里面。要减少这个可能性,则需要设计更完整的试验来“解释”问题。您也可以顺着这一点进行更深入的探索。

线程池中的线程是“公用”的

我们没有独立创建线程,而是选择使用线程池一定有其原因。不过,我们既然使用了线程池,就有一些额外的东西值得注意。

首先,我们要明确一个观念:线程并不“属于”任何一个任务,或者说任务并不“拥有”线程。我们只是借用一个线程来做事,用完以后便会还回。也就是说,任务在执行时修改线程的信息(名称,优先级,语言文化等等)是没有意义的,此外,任务也不应该依赖线程的这些状态。还记得上篇文章中谈到的QueueUserWorkItem和UnsafeQueueUserWorkItem之间的区别吗?如果您的任务需要依赖什么东西,也请自行准备。线程池中的线程状态是不可靠的。当然,也尽量不要直接对当前线程进行其他操作。

其次,由于线程池有大小限制,在某些时候还可能出现死锁的情况:

static void WaitCallback(object handle)
{
    ManualResetEvent waitHandle = (ManualResetEvent)handle;

    for (int i = 0; i < 10; i++)
    {
        ThreadPool.QueueUserWorkItem(state =>
        {
            int index = (int)state;
            if (index == 9)
            {
                waitHandle.Set(); // release all 
            }
            else
            {
                waitHandle.WaitOne(); // wait 
            }
        }, i);
    }
}

public static void DeadLock()
{
    ManualResetEvent waitHandle = new ManualResetEvent(false);

    ThreadPool.SetMaxThreads(5, 5);
    ThreadPool.QueueUserWorkItem(WaitCallback, waitHandle);

    waitHandle.WaitOne();
}

在上面的代码中,waitHandle将永远阻塞。因为我们放入线程池的10个任务,只有最后一个会将waitHandle打开,其余任务也统统阻塞在这个waitHandle上。但是请注意,我们使用SetMaxThreads方法把最大线程数限制为5,这样第10个任务根本无法执行,从而进入了死锁。避免这个问题最简单的做法是增加最大线程数,但是这还是会产生许多无法工作的线程,造成资源的浪费。因此,最好的做法是重新设计并行算法,并且时刻记住:“不要阻塞线程池里的线程”。

如何合理而有效的使用线程(既不多也不少还不阻塞),这是并行算法中最常见的课题之一。例如,让您设计一个并行计算斐波那契数列的算法,如果您每次计算Fib(n)时,都创建两个新的任务来并行计算Fib(n - 1)和Fib(n - 2),并等待它们结束,就会造成上述的死锁(或大量线程)。如何解决这个问题?您可以观察一下.NET 4.0中新增的Task并行类库,它提供了丰富而易用的并行运算API,帮我们省去了大量的工作1

最后,便是时刻记得系统中哪些功能依赖线程池。例如ASP.NET中的请求也会使用CLR线程池,那么您是否应该使用ThreadPool?是否应该直接使用委托的异步调用?您是否应该调整线程池的最大和最小线数?这些问题没有确定答案,这需要您根据实际情况自己做判断。

CLR线程池与IO线程池

当第一次了解到.NET准备了一个CLR线程池和一个IO线程池的时后,我在想,这两者真的是没有关系的吗?他们会互相影响吗?于是我做了这么一个试验:

public static void IoThread()
{
    ThreadPool.SetMinThreads(5, 3);
    ThreadPool.SetMaxThreads(5, 3);

    ManualResetEvent waitHandle = new ManualResetEvent(false);

    Stopwatch watch = new Stopwatch();
    watch.Start();

    WebRequest request = HttpWebRequest.Create("http://www.cnblogs.com/");
    request.BeginGetResponse(ar =>
    {
        var response = request.EndGetResponse(ar);
        Console.WriteLine(watch.Elapsed + ": Response Get");

    }, null);

    for (int i = 0; i < 10; i++)
    {
        ThreadPool.QueueUserWorkItem(index =>
        {
            Console.WriteLine(String.Format("{0}: Task {1} started", watch.Elapsed, index));
            waitHandle.WaitOne();

        }, i);
    }

    waitHandle.WaitOne();
}

得到的结果是这样的:

00:00:00.0923543: Task 0 started
00:00:00.1152495: Task 2 started
00:00:00.1153073: Task 3 started
00:00:00.1152439: Task 1 started
00:00:01.0976629: Task 4 started
00:00:01.5235481: Response Get

从中可以看出,我们将CLR线程池的最大线程数量设为了5,并使用与上一例类似的做法故意“阻塞”了线程池(而只有5个任务被执行了,说明线程池的确被阻塞了),其目的便是观察在这种情况下一个IO异步请求是否能够得到正确的回复。答案是肯定的,IO异步请求的回调函数正常执行了。这意味着,虽然CLR线程池被用完了,但是似乎的确还是有一个额外的IO线程池在处理IO的异步回调。这样看来,CLR线程池和IO线程池两者并没有影响。此外,从.NET框架所设计的类库来看,的确将两者作了区分,例如:

public static class ThreadPool 
{
    public static bool GetAvailableThreads(out int workerThreads, out int completionPortThreads);
}

不过,这并不意味着CLR线程池中线程被用完之后,还是可以发起异步IO请求。例如,您可以尝试着将这个例子中的WebRequest操作放到for循环后面(确保CLR线程池中线程已经被用完了),这是您会发现BeginGetRequest方法的调用抛出了一个异常,提示您说线程池中没有多余的线程了。从这个角度这样看来,CLR线程池的确还是可能影响异步IO操作的(多谢xiongli大哥指出“这是由具体实现决定的”)——虽然这在普通应用程序中一般不会出现这个问题。

其实在IO线程池方面还可以进行其他一些试验。例如,您可以缩小IO线程池的最大线程数量,然后一下子发起多个异步IO请求,观察一下它们的回调函数执行时刻。这些不如就由您来自行完成了?

相关文章

 

注1:.NET 4.0在多线程方面进行了明显的增强,除了Task并行类库之外,也将Parallel Library并入框架之内。此外,.NET 4.0还提供了许多线程安全的并行容器,以及轻量级的CountDownLatch、SemaphoreSlim、SpinWait等常用组件,无论是学习还是使用都是绝佳的范例。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

46 条回复

  1. 老赵
    admin
    链接

    老赵 2009-10-20 00:08:00

    终于解决掉一个太监系列了。

  2. 奇里斯玛
    *.*.*.*
    链接

    奇里斯玛 2009-10-20 00:15:00

    板凳了!收藏之,慢慢看!

  3. Justin
    *.*.*.*
    链接

    Justin 2009-10-20 00:20:00

    睡觉去咯,老赵也早点休息吧
    明天再仔细来学学这个是怎么太监的

  4. xiongli[未注册用户]
    *.*.*.*
    链接

    xiongli[未注册用户] 2009-10-20 00:29:00

    如果结合perfomrmance monitor里面关于CLR threading的counter来写的话,会更直观有趣的!

  5. xiongli[未注册用户]
    *.*.*.*
    链接

    xiongli[未注册用户] 2009-10-20 00:49:00

    另外,最后那个"从这个角度这样看来,CLR线程池的确还是会影响异步IO操作的"

    其实取决于具体实现. httpwebrequest里面有这样的强制判断. 这样的影响并不是clr或者threadpool强制的,完全看自己的实现.

    if (!this.RequestSubmitted && NclUtilities.IsThreadPoolLow())
    {
    Exception exception = new InvalidOperationException(SR.GetString("net_needmorethreads"));
    this.Abort(exception, 1);
    throw exception;
    }

    我记不清楚这样设计的具体原因好了,好像是是当io thread拿到request后,最后会交给worker pool去处理.(通过一个worker queue和一个queue monitor)如果worker pool满了的话, 那io thread也会被block在那里, 这样就会伤害io pool的perf, 使得perf或者deadlock的问题越发严重. 至于httpwebrequest是否这样,还得仔细看看.

  6. xiongli[未注册用户]
    *.*.*.*
    链接

    xiongli[未注册用户] 2009-10-20 00:57:00

    没道理啊,这个httpwebrequest不需要依赖worker作callback....

  7. 寒飞雨
    *.*.*.*
    链接

    寒飞雨 2009-10-20 01:07:00

        ThreadPool.SetMinThreads(5, 5); // set max thread to 10
    

    第一句的注释好像有点问题,我在这里疑惑了1分钟,因为我完全是业余的,平时不用,有些细节记不清,还以为自己记错了。呵呵。

    下面是我对线程的理解,向线程池中排入100个任务的变化过程。帮看看我的理解有什么问题,因我是完全猜想,没有验证。

    为了叙述方便,我们假设下限为5,上限为25。
    1.当线程池被创建后,里面就会创建5个空线程(和下限值相同)。
    2.当我们向线程池中排入一个任务后,就会有一个空线程接手该任务,然后运行起来。随着我们不断向线程池中排入任务,线程池中的空线程逐一接手任务并被执行。
    3.随着任务的增加,在某一时刻任务数量会超出下限。当任务请求数量超出下限时,线程池并不会立即创建新线程,而是等待大约500毫秒左右,这么做的目的是看看在这段时间内是否有其他线程完成任务来接手这个请求,这样就可以避免因创建新线程而造成的消耗。如果这段时间内没有线程完成任务,就创建一个新线程去执行新任务。
    4.当任务数量超过下限后,每排入一个新任务,就会增加一个新线程,这段期间,任务和线程数量都持续增加,直至线程数量达到上限值为止。
    5.当线程数量达到上限时,继续增加任务,线程数量将不再增加。比如你向线程池中排入100个任务,则只有25个进入线程池(和上限相同),另外75个在线程池外排队等待。当线程池中的某个线程完成任务后,并不会被终止,而是从等待队列中选择一个任务继续执行,这样就减少了因创建和销毁线程而消耗的时间。
    6.当排入所有的任务后,随着线程池内的任务被逐步完成,线程池外部等候的任务被逐步调入线程池,任务的数量逐步减少,但线程的数量保持恒定,始终和上限值相同。
    7.随着任务被逐步完成,总有某一时刻,任务数量会小于上限值,这时线程池内多余的线程会在空闲2分钟后被释放并回收相关资源。线程数目逐步减少,直到达到下限值为止。
    8.当任务数量减小到下限值之下时,线程池中的线程数目保持不变(始终和下限值相同),其中一部分在执行任务,另一部分处于空运行状态。
    9.当所有任务都完成后,线程池恢复初始状态,运行5个空线程。

    由上面的论述可以看出线程池提高效率的关键是一个线程完成任务后可以继续为其他任务服务,这样就可以使用有限的几个固定线程轮流为大量的任务服务,从而减少了因频繁创建和销毁线程所造成的消耗。

    按老赵的说法一开始有4个任务执行,迷惑中。

  8. xiongli[未注册用户]
    *.*.*.*
    链接

    xiongli[未注册用户] 2009-10-20 01:29:00

    当线程池被创建后,里面就会创建5个空线程

    这里就错了
    不会一下子给你5个的,是按需分配,到5个为止

    rotor里面的thread pool很容易看,代码非常清晰

  9. 寒飞雨
    *.*.*.*
    链接

    寒飞雨 2009-10-20 01:33:00

    @xiongli
    因为我已经设最小值为5了,所以一开始应有5个空线程,不然和最小值为1有什么区别?
    谢谢!

  10. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-10-20 07:32:00

    //原来线程池创建线程的速度并非永远是“每秒2个”,而一些资料上写着“每秒不超过2个”的确是确切的说法。//

    我隐隐约约记得 这个数值和运行环境的cpu内核数有关

    老赵有没有4核岛16核的服务器阿 在上面测试下可能有不同结果

  11. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-10-20 07:46:00

    xiongli:
    另外,最后那个"从这个角度这样看来,CLR线程池的确还是会影响异步IO操作的"

    其实取决于具体实现. httpwebrequest里面有这样的强制判断. 这样的影响并不是clr或者threadpool强制的,完全看自己的实现.

    if (!this.RequestSubmitted && NclUtilities.IsThreadPoolLow())
    {
    Exception exception = new InvalidOperationException(SR.GetString("net_needmorethreads"));
    ...


    在道理上很有可信度阿~ 我从“如果我来设计”的角度来理解的话 似乎是不二选择

  12. 笨五木
    *.*.*.*
    链接

    笨五木 2009-10-20 08:35:00

    好文,
    收藏之,慢慢看

  13. 亚历山大同志
    *.*.*.*
    链接

    亚历山大同志 2009-10-20 09:03:00

    如果MaxThread=能够发起的线程+1,那么是否可以认为主线程也其实是在线程池中的一个线程?

  14. 老赵
    admin
    链接

    老赵 2009-10-20 09:07:00

    @韦恩卑鄙
    我刚看了CLR via C#是这么写的:
    Specifically, the thread pool tries to avoid creating more than one thread per 500 milliseconds.

    一会儿我再去一台4核的机器跑跑看,这片文章结果是2核跑的。

  15. 老赵
    admin
    链接

    老赵 2009-10-20 09:08:00

    @亚历山大同志
    我不知道能否这样理解啊,呵呵。

  16. 老赵
    admin
    链接

    老赵 2009-10-20 09:24:00

    @韦恩卑鄙

    00:00:00.0101830: Task 4 started
    00:00:00.0100939: Task 3 started
    00:00:00.0101079: Task 1 started
    00:00:00.0100964: Task 2 started
    00:00:00.0101232: Task 0 started
    00:00:00.9969657: Task 5 started
    00:00:01.4969480: Task 6 started
    00:00:01.9970399: Task 7 started
    00:00:02.4968885: Task 8 started
    00:00:03.4968284: Task 9 started
    00:00:04.4968134: Task 10 started
    00:00:05.4968314: Task 11 started
    00:00:10.0119682: Task 0 finished
    00:00:10.0121169: Task 3 finished
    00:00:10.0127611: Task 13 started
    00:00:10.0119836: Task 2 finished
    00:00:10.0128827: Task 14 started
    00:00:10.0119754: Task 4 finished
    00:00:10.0121392: Task 12 started
    00:00:10.0130098: Task 15 started
    00:00:10.0119646: Task 1 finished
    00:00:10.0132469: Task 16 started
    00:00:10.9962926: Task 5 finished
    00:00:10.9964455: Task 17 started
    00:00:11.4962803: Task 6 finished
    00:00:11.4967612: Task 18 started
    00:00:11.9962692: Task 7 finished
    00:00:11.9968184: Task 19 started
    00:00:12.4962668: Task 8 finished
    00:00:13.4962161: Task 9 finished
    00:00:14.4961905: Task 10 finished
    00:00:15.4961583: Task 11 finished
    00:00:20.0116431: Task 13 finished
    00:00:20.0117318: Task 14 finished
    00:00:20.0116729: Task 16 finished
    00:00:20.0116733: Task 12 finished
    00:00:20.0116871: Task 15 finished
    00:00:20.9959818: Task 17 finished
    00:00:21.4959710: Task 18 finished
    00:00:21.9959527: Task 19 finished
    
    这是四核上跑得结果,大致结果一致,细节上略有变化:MinThread等于5时,的确立即执行了5个线程。

  17. whzncut
    *.*.*.*
    链接

    whzncut 2009-10-20 09:37:00

    奇怪啊,我也试了一下你的代码;
    如IoThread方法,如果minThread设置小于4, 线程池初始创建的CLR线程则是min,如果大于或等于4,则为min-1;我的是双核机器。

    还有想问一下,work thread 和 IO thread 职责不同,但从底层Win32本质上来讲应该还是一样的吧;比如 都是WinAPI: CreateThread 创建出来的吧

  18. xiongli[未注册用户]
    *.*.*.*
    链接

    xiongli[未注册用户] 2009-10-20 09:38:00

    你看看CPU

    韦恩卑鄙:
    //原来线程池创建线程的速度并非永远是“每秒2个”,而一些资料上写着“每秒不超过2个”的确是确切的说法。//

    我隐隐约约记得 这个数值和运行环境的cpu内核数有关

    老赵有没有4核岛16核的服务器阿 在上面测试下可能有不同结果



    你的记忆是基于machine.config的配置
    你看MSDN就清楚了
    你的理解来源是:
    http://support.microsoft.com/kb/821268

  19. 强袭装甲[未注册用户]
    *.*.*.*
    链接

    强袭装甲[未注册用户] 2009-10-20 09:40:00

    老赵能否结合自身经历写一些学习方法和技术成长方面的文章,供后生们学习和借鉴

  20. 老赵
    admin
    链接

    老赵 2009-10-20 09:42:00

    @强袭装甲
    想到了我会写的,现在也写过一些了吧。

  21. 老赵
    admin
    链接

    老赵 2009-10-20 09:42:00

    @whzncut
    看来一些细节的确不是那么容易确定的。
    worker thread和IO thread我也不清楚是否都是CreateThread创建出来的。
    其实windows里也有线程池,不过印象中.NET的线程池是自己管理的,用的不是操作系统的线程池。所以你的说法也是有可能的。

  22. whzncut
    *.*.*.*
    链接

    whzncut 2009-10-20 09:46:00

    恩,技术细节确实太难弄清, 真是很好的实验,多谢老赵分享.

  23. yeml[未注册用户]
    *.*.*.*
    链接

    yeml[未注册用户] 2009-10-20 09:57:00

    ThreadPool还有个问题,没法设置线程为STA,这个在job里面初始化STA的COM组件时不方便,不过我用了一个折中的方法

    TP限制还是太多了点,最灵活的是自己管理线程,那样才爽

  24. 亚历山大同志
    *.*.*.*
    链接

    亚历山大同志 2009-10-20 10:04:00

    可不可以这么理解,应该是每个核上分配2个线程?

  25. 老赵
    admin
    链接

    老赵 2009-10-20 10:45:00

    @亚历山大同志
    不是的吧,我在四核机器上测试的结果基本相同。

  26. 亚历山大同志
    *.*.*.*
    链接

    亚历山大同志 2009-10-20 11:09:00

    @Jeffrey Zhao
    我是这样考虑的,如果4核CPU的话,按每个颗2个线程预设的话,可以同时跑8个,那么设定MaxThread为5的话,CPU还有空闲,于是主线程还能跑。如果是设定MaxThread为10的话就为了确保主线程就只好空一个线程出来莫法跑了。

    不知道这样子看对不

  27. 老赵
    admin
    链接

    老赵 2009-10-20 11:39:00

    @亚历山大同志
    我觉得这东西如果不是看代码的话还真没法猜……

  28. Duron800[未注册用户]
    *.*.*.*
    链接

    Duron800[未注册用户] 2009-10-20 11:48:00

    向线程池中连续推入10个任务?
    是10个还是20个?

  29. 老赵
    admin
    链接

    老赵 2009-10-20 11:53:00

    @Duron800
    20,已改正,谢谢。

  30. 汝熹
    *.*.*.*
    链接

    汝熹 2009-10-20 16:57:00

    在两核的U上开四线程,在四核上开五线程,ThreadPool打开的线程也受到U数量的影响,但如果想效率高的话,应该有多少个U就同进开多少个线程比较好,线程的轮换也需要开销的,要看看它代码才知道以什么标准开动线程了。

  31. caidian
    *.*.*.*
    链接

    caidian 2009-10-20 17:57:00

    关注中

  32. 道法自然
    *.*.*.*
    链接

    道法自然 2009-10-20 18:05:00

    不错,让我知道了一些以前不知道的细节。自己做池的时候,当工作项长度大于最大线程池的时候,我是一个劲分配线程直到最大线程数,没想到微软的线程池竟然是限制每秒最多2个线程增长,另外,从文中也发现了当线程数小于最小线程数的时候,线程的增长就没有每秒最多2个的限制。这种设计想的真周到啊!!也发现细节的东西不好把握。

  33. Jerry Qian
    *.*.*.*
    链接

    Jerry Qian 2009-10-20 19:55:00

    waitHandle.Set(); 是发信号啊,不是阻塞啊

  34. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-10-20 22:07:00

    Jeffrey Zhao:
    @亚历山大同志
    我觉得这东西如果不是看代码的话还真没法猜……


    我的印象来自年初JR的课 与其说是2堂课 不如说是 clr via c# SE的补丁包 里面有很多 SE出版后 他了解到的新信息


    JR那次说 Thread Pool的确对多核心有优化 但是我不是很Sure在哪里优化 开始同时数目 还是每秒新增数量.

    现在重新考虑了下

    同时初始化线程数目确实会带来性能消耗高峰 这个是内存性能问题 所以不大可能因为cpu内核数目增加 而放松对新线程建立速度的限制

    所以很可能是在初始运行的时候有所优化



    JR还说了一些有趣的现象 比如说 min 5 max 10的时候 在01234任务做完的时候 如果一个新的task到来 往往thread pool 还会增加新线程. 这个是有所保留的优化算法 和硬件的实际情况参数有关

    JR也说不清楚 他也是被告知:这里头很强大 有很多优化,老老实实用吧,

  35. 老赵
    admin
    链接

    老赵 2009-10-20 22:14:00

    Jerry Qian:waitHandle.Set(); 是发信号啊,不是阻塞啊


    我这篇文章代码的注释都是后来在HTML里补的,而且后面又手动改过代码,可谓残不忍睹……
    现在改好了,谢谢提醒。

  36. 老赵
    admin
    链接

    老赵 2009-10-20 22:14:00

    @韦恩卑鄙
    看到JR就想到“jianren”啊,这缩写不好,嗯嗯。

  37. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-10-20 22:23:00

    Jeffrey Zhao:
    @韦恩卑鄙
    看到JR就想到“jianren”啊,这缩写不好,嗯嗯。


    啊啊... 被吐了....

  38. 老赵
    admin
    链接

    老赵 2009-10-20 22:36:00

    @韦恩卑鄙
    你当时怎么和JR说了那么多,记得好几次你提到JR年初说的东西了,呵呵。

  39. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-10-20 22:39:00

    Jeffrey Zhao:
    @韦恩卑鄙
    你当时怎么和JR说了那么多,记得好几次你提到JR年初说的东西了,呵呵。


    我是好听众 就问了他一个问题 其他都是他课堂上的内容啊~~

    饿...我不是炫耀听力....

  40. 老赵
    admin
    链接

    老赵 2009-10-20 23:10:00

    @韦恩卑鄙
    好吧我承认他那场我没去……

  41. Duron800[未注册用户]
    *.*.*.*
    链接

    Duron800[未注册用户] 2009-10-21 00:07:00

    韦恩卑鄙:

    Jeffrey Zhao:
    @亚历山大同志
    我觉得这东西如果不是看代码的话还真没法猜……


    我的印象来自年初JR的课 与其说是2堂课 不如说是 clr via c# SE的补丁包 里面有很多 SE出版后 他了解到的新信息


    JR那次说 Thread Pool的确对多核心有优化 但是我不是很Sure在哪里优化 开始同时数目 还是每秒新增数量.

    现在重新考虑了下

    同时初始化线程数目确实会带来性能消耗高峰 这个是内存性能问题 所以不大可能因为cpu内核数目增加 而放松对新线程建立速度的限制

    所以很可能是在初始运行的时候有所优化



    JR还说了一些有趣的现象 比如说 min 5 max 10的时候 在01234任务做完的时候 如果一个新的task到来 往往thread pool 还会增加新线程. 这个是有所保留的优化算法 和硬件的实际情况参数有关

    JR也说不清楚 他也是被告知:这里头很强大 有很多优化,老老实实用吧,


    看了最后一句,我得到的结论是知道怎么用就行了。老赵还是提供些ThreadPool的最佳实践得了。

  42. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-10-27 16:08:00

    这篇文章等了好久了。

  43. 萧萧空间
    *.*.*.*
    链接

    萧萧空间 2009-11-03 10:11:00

    好文,学习。以前对这些了解不是很清晰,现在明白了,多谢。

  44. Arthraim
    *.*.*.*
    链接

    Arthraim 2009-12-01 13:46:00

    唉?原来这篇文章已经写好了啊~ 没注意就在Google Reader里错过了,囧

  45. 链接

    ilovehaley.kid 2010-06-04 16:51:11

    学习了,谢谢。

  46. chenjun
    59.39.41.*
    链接

    chenjun 2010-11-25 10:24:27

    这事什么意思 WaitCallback callback = index =>

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我