We were unable to locate this content in zh-cn.

Here is the same content in en-us.

.NET
The CLR's Thread Pool
Jeffrey Richter
Microsoft is always trying to improve the performance of its platforms and applications. Many years ago, Microsoft researched how threads were being used by application developers to see what could be done to improve their use. Out of this research came a very important discovery: developers frequently created a new thread to perform a single task and when the task was complete, the thread would die.
This pattern is extremely common in a server application. A client makes a request of the server, the server creates a thread to process the client's request, and then, when the client's request is complete, the server's thread would die. Compared to a process, creating and destroying a thread is fast and uses fewer OS resources. But creating and destroying threads is certainly not free.
To create a thread, a kernel object is allocated and initialized, the thread's stack memory is allocated and initialized, and Windows® sends every DLL in the process a DLL_THREAD_ATTACH notification, causing pages from disk to be faulted into memory so that code can execute. When a thread dies, every DLL is sent a DLL_THREAD_DETACH notification, the thread's stack memory is freed, and the kernel object is freed (if its usage count goes to 0). So, there is a lot of overhead associated with creating and destroying a thread that has nothing to do with the work that the thread was created to perform in the first place.
The Birth of the Thread Pool
The result of this study led Microsoft to implement a thread pool, which first appeared in Windows 2000. When a Microsoft® .NET Framework team was designing and building the common language runtime (CLR), they decided to implement a thread pool right into the CLR itself. This way, any managed application could take advantage of a thread pool even if the application was running on a version of Windows prior to Windows 2000 (such as Windows 98).
When the CLR initializes, its thread pool contains no threads. When the application wants to create a thread to perform a task, the application should request the task be performed by a thread pool thread. The thread pool knows that and will create an initial thread. This new thread will go through the same initialization as any other thread; but, when the task is complete, the thread will not destroy itself. Instead, the thread will return to the thread pool in a suspended state. If the application makes another request of the thread pool, then the suspended thread will just wake up and perform the task and a new thread will not be created. This saves a lot of overhead. As long as the application queues tasks to the thread pool no faster than the one thread can handle each task, the same thread gets reused over and over again saving an enormous amount of overhead over the app's lifetime.
Now, if the application queues up tasks for the thread pool faster than the one thread can handle it, then the thread pool will create additional threads. Of course, creating new threads does generate overhead, but it is very likely that the application will require just a few threads to handle all of the tasks thrown at it over the application's lifetime. So, overall, the application's performance improves by using the thread pool.
Now, you might be wondering what happens if the thread pool contains many threads and the workload on the application diminishes. In this case, the thread pool contains several threads that are sitting suspended for long periods of time, wasting OS resources. Microsoft thought about this, too. When a thread pool thread suspends itself, it waits for 40 seconds. If 40 seconds elapses and the thread is given nothing to do, then the thread wakes up and destroys itself, freeing all the OS resources (stack, kernel object, and so forth) that it was using. Also, it probably doesn't hurt performance to have the thread wake up and destroy itself because the application can't be doing too much anyway or the thread would have resumed execution. By the way though, I said that threads in the thread pool wake themselves up in 40 seconds, the actual amount of time is not documented and is subject to change.
The cool thing about a thread pool is that it is heuristic. If your application needs to perform many tasks, then the thread pool creates more threads. If your application's work load dies down, then the thread pool threads kill themselves. The thread pool's algorithms ensure that it contains as many threads as required by the workload placed on it!
So, hopefully, you now understand the general concept behind a thread pool and see the performance advantages that it can offer. At this time, I'd like to show you some code demonstrating how to use the thread pool. First, you should know that the thread pool offers four capabilities:

  • Calling a method asynchronously
  • Calling a method at a timed interval
  • Calling a method when a single kernel object is signaled
  • Calling a method when an asynchronous I/O request completes
The first three capabilities are quite useful and I will demonstrate them in this column. However, the fourth capability is very rarely used by application developers, so I will not demonstrate it here; perhaps I'll cover it in a future column.
Capability 1: Calling a Method Asynchronously
In your application, if you have code in which you create a new thread to perform a task, I recommend that you replace that code with new code that directs the thread pool to perform the task instead. In fact, you'll generally find that it is easier to have the thread pool perform a task then it is to have a new, dedicated thread.
To queue a task for the thread pool, you use the ThreadPool class defined in the System.Threading namespace. The ThreadPool class offers only static methods and no instance of it can be constructed. To have a thread pool thread call a method asynchronously, your code must call one of ThreadPool's overloaded QueueUserWorkItem methods, as shown here:

 
public static Boolean QueueUserWorkItem(WaitCallback wc, Object state);
public static Boolean QueueUserWorkItem(WaitCallback wc);
These methods queue a "work item" (and optional state data) to a thread in the thread pool and return immediately. A work item is simply a method (identified by the wc parameter) that is called and passed a single parameter, state (the state data). The version of QueueUserWorkItem without the state parameter passes null to the callback method. Eventually, some thread in the pool will process the work item, causing your method to be called. The callback method you write must match the System.Threading.WaitCallback delegate type, which is defined as follows:

 
public delegate void WaitCallback(Object state);
Notice that you never call any method that creates a thread yourself; the CLR's thread pool will automatically create a thread, if necessary, and reuse an exiting thread if possible. Also, this thread is not immediately destroyed after it processes the callback method; it goes back into the thread pool so that it is ready to handle any other work items in the queue. Using QueueUserWorkItem might make your application more efficient because you won't be creating and destroying threads for every single client request.
The code in Figure 1 demonstrates how to have the thread pool call a method asynchronously.
  Figure 1 Thread Pool Calls a Method
 
using System;
using System.Threading; class App {
static void Main() {
Console.WriteLine("Main thread: Queuing an aynchronous
operation.");
ThreadPool.QueueUserWorkItem(new WaitCallback(MyAsyncOperation)); Console.WriteLine("Main thread: Performing other operations.");
// ... Console.WriteLine("Main thread: Pausing to simulate doing other
operations.");
Console.ReadLine();
} // The callback method's signature MUST match that of a
// System.Threading.WaitCallback delegate (it takes an
// Object parameter and returns void)
static void MyAsyncOperation(Object state) {
Console.WriteLine("ThreadPool thread: Performing aynchronous
operation.");
// ...
Thread.Sleep(5000); // Sleep for 5 seconds to simulate doing
// work // Returning from this method causes the thread to
// suspend itself waiting for another task
}
}
Capability 2: Calling a Method at Timed Intervals
If your application needs to perform a certain task at a certain time or if your application needs to execute some method periodically, the thread pool is the perfect thing for you to use. The System.Threading namespace defines the Timer class. When you construct an instance of the Timer class, you are telling the thread pool that you want a method of yours called back at a particular time in the future. The Timer class offers four constructors:

 
public Timer(TimerCallback callback, Object state,
Int32 dueTime, Int32 period);
public Timer(TimerCallback callback, Object state,
UInt32 dueTime, UInt32 period);
public Timer(TimerCallback callback, Object state,
Int64 dueTime, Int64 period);
public Timer(TimerCallback callback, Object state,
Timespan dueTime, TimeSpan period);
All four constructors construct a Timer object identically. The callback parameter identifies the method that you want called back by a thread pool thread. Of course, the callback method you write must match the System.Threading.TimerCallback delegate type, which is defined as follows:

 
public delegate void TimerCallback(Object state);
The constructor's state parameter allows you to pass state data to the callback method; you can pass null if you have no state data to pass. You use the dueTime parameter to tell the thread pool how many milliseconds to wait before calling your callback method for the very first time. You can specify the number of milliseconds using a signed or unsigned 32-bit value, a signed 64-bit value, or a TimeSpan value. If you want the callback method called immediately, specify 0 for the dueTime parameter. The last parameter, period, allows you to specify how long, in milliseconds, to wait before each successive call. If you pass 0 for this parameter, then the thread pool will call the callback method just once.
After constructing a Timer object, the thread pool knows what to do and monitors the time automatically for you. However, the Timer class offers some additional methods allowing you to communicate with the thread pool to modify when (or if) the method should be called back. Specifically, the Timer class offers several Change and Dispose methods:

 
public Boolean Change(Int32    dueTime, Int32    period);
public Boolean Change(UInt32 dueTime, UInt32 period);
public Boolean Change(Int64 dueTime, Int64 period);
public Boolean Change(TimeSpan dueTime, TimeSpan period);
public Boolean Dispose();
public Boolean Dispose(WaitHandle notifyObject);
The Change method allows you to change the Timer object's due time and period. The Dispose method allows you to cancel the callback altogether and optionally signal the kernel object identified by the notifyObject parameter when all pending callbacks for the time have completed.
The code in Figure 2 demonstrates how to have a thread pool thread call a method immediately and every 2000 milliseconds (or two seconds) thereafter.
  Figure 2 Using the Period Parameter
 
using System;
using System.Threading; class App {
static void Main() {
Console.WriteLine("Checking for status updates every 2 seconds.");
Console.WriteLine(" (Hit Enter to terminate the sample)");
Timer timer = new Timer(new TimerCallback(CheckStatus), null, 0,
2000);
Console.ReadLine();
} // The callback method's signature MUST match that of a
// System.Threading.TimerCallback delegate (it takes an
// Object parameter and returns void)
static void CheckStatus(Object state) {
Console.WriteLine("Checking Status.");
// ...
}
}
Capability 3: Calling a Method When a Single Kernel Object Becomes Signaled
While doing their performance studies, Microsoft researchers discovered that many applications spawn threads simply to wait for a single kernel object to become signaled. Once the object is signaled, the thread posts some sort of notification to another thread and then loops back, waiting for the object to signal again. Some developers even write code in which several threads each wait on a single object. This is a big waste of system resources. So, if you currently have threads in your application that wait for single kernel objects to become signaled, then the thread pool is, again, the perfect resource for you to increase your application's performance.
To have a thread pool thread call your callback method when a kernel object becomes signaled, you again use some static methods defined in the System.Threading.ThreadPool class. To have a thread pool thread call a method when a kernel object becomes signaled, your code must call one of the overloaded RegisterWaitHandle methods you see in Figure 3.
  Figure 3 RegisterWaitHandle Methods
 
public static RegisterWaitHandle RegisterWaitForSingleObject(
WaitHandle h, WaitOrTimerCallback callback, Object state,
UInt32 milliseconds, Boolean executeOnlyOnce); public static RegisterWaitHandle RegisterWaitForSingleObject(
WaitHandle h, WaitOrTimerCallback callback, Object state,
Int32 milliseconds, Boolean executeOnlyOnce); public static RegisterWaitHandle RegisterWaitForSingleObject(
WaitHandle h, WaitOrTimerCallback callback, Object state,
TimeSpan milliseconds, Boolean executeOnlyOnce); public static RegisterWaitHandle RegisterWaitForSingleObject(
WaitHandle h, WaitOrTimerCallback callback, Object state,
Int64 milliseconds, Boolean executeOnlyOnce);
When you call one of these methods, the h parameter identifies the kernel object that you want the thread pool to wait on. Since this parameter is of the abstract base class System.Threading.WaitHandle, you can specify any class derived from this base class. Specifically, you can pass a reference to an AutoResetEvent, ManualResetEvent, or Mutex object. The second parameter, callback, identifies the method that you want the thread pool thread to call. The callback method that you implement must match the System.Threading.WaitOrTimerCallback delegate type, which is defined in the following line of code:

 
public delegate void WaitOrTimerCallback(Object state,
Boolean timedOut);
The third parameter, state, allows you to specify some state data that should be passed to the callback method; pass null if you have no special state data to pass. The fourth parameter, milliseconds, allows you to tell the thread pool how long it should wait for the kernel object to become signaled. It is common to pass -1 here to indicate an infinite timeout. If the last parameter, executeOnlyOnce, is true, then a thread pool thread will execute the callback method just once. But, if executeOnlyOnce is false, then a thread pool thread will execute the callback method every time the kernel object is signaled. This is most useful with an AutoResetEvent object.
When the callback method is called, it is passed state data and a Boolean value, timedOut. If timedOut is false, then the method knows that it is being called because the kernel object became signaled. If timedOut is true, then the method knows it is being called because the kernel object did not become signaled in the time specified. The callback method should perform whatever action is necessary.
In the prototypes shown earlier, you'll notice that the RegisterWaitForSingleObject method returns a RegisteredWaitHandle object. This object identifies the kernel object that the thread pool is waiting on. If, for some reason, your application wants to tell the thread pool to stop watching the registered wait handle, your application can call RegisteredWaitHandle's Unregister method:

 
public Boolean Unregister(WaitHandle waitObject);
The waitObject parameter indicates how you want to be notified when all queued work items have executed. You should pass null for this parameter if you don't want a notification. If you pass a valid reference to a WaitHandle-derived object, then the thread pool will signal the object when all pending work items for the registered wait handle have executed.
The code in Figure 4 demonstrates how to have a thread pool thread call a method whenever a kernel object becomes signaled.
  Figure 4 Method Called When Object Signaled
 
using System;
using System.Threading; class App {
static void Main() {
AutoResetEvent are = new AutoResetEvent(false);
RegisteredWaitHandle rwh = ThreadPool.RegisterWaitForSingleObject(
are, new WaitOrTimerCallback(EventSignalled), null, -1, false);
for (Int32 x = 0 ; x < 5; x++) {
Thread.Sleep(5000);
are.Set();
} rwh.Unregister(null);
Console.WriteLine("Hit Enter to terminate the sample");
Console.ReadLine();
} // The callback method's signature MUST match that of a
// System.Threading.WaitOrTimerCallback delegate (it takes an
// Object parameter and a Boolean and returns void)
static void EventSignalled(Object state, Boolean timedOut) {
if (timedOut) {
Console.WriteLine("Timed-out while waiting for the
AutoResetEvent.");
} else {
Console.WriteLine("The AutoResetEvent became signalled.");
}
// ...
}
}
Conclusion
In this column, I've explained the need for thread pools and demonstrated how to use the various capabilities offered by the CLR's thread pool. By now you should see the value that a thread pool can bring to your own development efforts to improve your application's performance and simplify your own code.

Send your questions and comments for Jeff to  dot-net@microsoft.com.

The CLR's Thread Pool的更多相关文章

  1. CLR thread pool

    Thread Pooling https://msdn.microsoft.com/en-us/library/windows/desktop/ms686756(v=vs.85).aspx Threa ...

  2. Reporting Service 告警"w WARN: Thread pool pressure. Using current thread for a work item"

    如果Reporting Service偶尔出现不可访问或访问出错情况,这种情况一般没有做监控的话,很难捕捉到.出现这种问题,最好检查Reporting Service的日志文件. 今天早上就遇到这样一 ...

  3. MySQL thread pool【转】

    本文来自:http://blog.chinaunix.net/uid-26896862-id-3993773.html 刚刚经历了淘宝的双11,真实感受到了紧张的氛围.尽管DB淡定的度过,但是历程中的 ...

  4. worksteal thread pool

    worksteal的场景 对于一个线程池,每个线程有一个队列,想象这种场景,有的线程队列中有大量的比较耗时的任务堆积,而有的线程队列却是空的,现象就是有的线程处于饥饿状态,而有的线程处于消化不良的状态 ...

  5. Improve Scalability With New Thread Pool APIs

    Pooled Threads Improve Scalability With New Thread Pool APIs Robert Saccone Portions of this article ...

  6. MySQL Thread Pool: Problem Definition

    A new thread pool plugin is now a part of the MySQL Enterprise Edition.In this blog we will cover th ...

  7. Thread Pool Engine, and Work-Stealing scheduling algorithm

    http://pages.videotron.com/aminer/threadpool.htm http://pages.videotron.com/aminer/zip/threadpool.zi ...

  8. DUBBO Thread pool is EXHAUSTED!

    一.问题 在测试环境遇到的异常信息,如下: 16-10-17 00:00:00.033 [New I/O server worker #1-6] WARN  com.alibaba.dubbo.com ...

  9. C++笔记--thread pool【转】

    版权声明:转载著名出处 https://blog.csdn.net/gcola007/article/details/78750220 背景 刚粗略看完一遍c++ primer第五版,一直在找一些c+ ...

随机推荐

  1. 浅谈C语言变量声明的解析

    C语言本身提供了一种不甚明确的变量声明方式——基于使用的声明,如int *a,本质上是声明了*a的类型为int,所以得到了a的类型为指向int的指针.对于简单类型,这样声明并不会对代码产生多大的阅读障 ...

  2. “XXX::Invoke”类型的已垃圾回收委托进行了回调。这可能会导致应用程序崩溃、损坏和数据丢失。向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用它们。

    症状描述如下: 如果将一个委托作为函数指针从托管代码封送到非托管代码,并且在对该委托进行垃圾回收后对该函数指针发出了一个回调,则将激活 callbackOnCollectedDelegate 托管调试 ...

  3. [C#常用代码]类库中读取解决方案web.Config字符串

    对于类库里读取解决方案web.config文件里字符串的方法一.读取键值对的方法:1.添加引用using System.Configuration;2.web.Config配置节<appSett ...

  4. [SQL]查询数据库里都有哪些自己建的表

    方法一:SELECT * FROM FTCE_ACCS.INFORMATION_SCHEMA.TABLES Where TABLE_TYPE='BASE TABLE'----------------- ...

  5. [转]Installing SharePoint 2013 on Windows Server 2012 R2

    转自:http://www.avivroth.com/2013/07/09/installing-sharepoint-2013-on-windows-server-2012-r2-preview/ ...

  6. 共享业务稳定性测试&技术创新组

    本文其实是一篇招聘贴,不管你以前是做开发还是测试,都欢迎加入我们的小组. 2014年阿里巴巴的共享业务事业部有很大变化,共享的测试团队也做了一些调整,我不再担任共享业务的测试经理,但是仍然会留在共享测 ...

  7. 【逐步完善】MySql索引相关

    在表中对某个字段添加索引: alter table tablename add index (columnname);

  8. 初识ios自动化(一)

    Appium进行自动化测试有两个好处: 1. Appium在不同平台中使用了标准的自动化APIs,所以在跨平台时,不需要重新编译或者修改自己的应用. 2. Appium支持Selenium WebDr ...

  9. 庆祝下:iOS 开发者企业级计划(299美元/年帐户+邓白氏码免费) 和 Windows Phone公司应用(公司帐户99美元+Symantec企业证书299美元/年))顺利发布成功

    时间:2013-11-15,地址:http://192.168.0.8  网站可下载三个终端应用直接安装IOS,Windows Phone,Android iOS: 企业版IDP通过 iTunes.使 ...

  10. Maven依赖版本冲突的分析及解决小结

    1:前言 做软件开发这几年遇到了许多的问题,也总结了一些问题的解决之道,之后慢慢的再遇到的都是一些重复性的问题了,当然,还有一些自己没有完全弄明白的问题.如果做的事情是重复的,遇到重复性问题的概率也就 ...