多线程可以提高应用程序的效率,这是肯定的,但是,效率是不是最优的呢,是不是觉得多线程很复杂呢?
前面学习线程的知道,用多线程需要CreateThread创建线程,还要关闭线程。另外,多线程有时候还要对资源进行同步,也就是说,需要用到事件,信标,互斥对象。
当然,线程与进程比较,无论速度,对资源的访问,安全性上面线程都有非常大的优势。但是,创建与销毁线程并不是免费的。
要创建一个线程,需要分配和初始化一个内核对象,也需要分配和初始化线程的堆栈空间,而且 Windows® 为进程中的每个 DLL 发送一个 DLL_THREAD_ATTACH 通知,使磁盘中的页分配到内存中,从而执行代码。当线程终止时,给每个 DLL 都发送一个 DLL_THREAD_DETACH 通知,线程的堆栈空间被释放,内核对象亦被释放(如果其使用数达到 0)。因此,与创建和销毁线程相关的许多开销都和创建线程原本要执行的工作无关。
为了更优化效率,Windows提供了线程池的概念。
线程池使创建,管理与撤销线程变得更容易。
我觉得,线程池可以:
*初始化线程,动态得创建线程。
*预分配线程池的内存空间
*优先级的排队
*管理线程,撤销线程。
Jeffer Richter在"CLR线程池"文章中讲到CLR中线程的特性:
当 CLR 初始化时,其线程池中不含有线程。当应用程序要创建线程来执行任务时,该应用程序应请求线程池线程来执行任务。线程池知道后将创建一个初始线程。该新线程 经历的初始化和其他线程一样;但是任务完成后,该线程不会自行销毁。相反,它会以挂起状态返回线程池。如果应用程序再次向线程池发出请求,那么这个挂起的 线程将激活并执行任务,而不会创建新线程。这节约了很多开销。只要线程池中应用程序任务的排队速度低于一个线程处理每项任务的速度,那么就可以反复重用同一线程,从而在应用程序生存期内节约大量开销。
那么,如果线程池中应用程序任务排队的速度超过一个线程处理任务的速度,则线程池将创建额外的线程。当然,创建新线程确实会产生额外开销,但应用程序在其 生存期中很可能只请求几个线程来处理交给它的所有任务。因此,总体来说,通过使用线程池可以提高应用程序的性能。
现在您可能想知道,如果线程池包含许多线程而应用程序的工作负荷又在减少,将会发生什么事情。这种情况下,线程池包含几个长期挂起的线程,浪费着操作系统 的资源。Microsoft 也考虑到了这个问题。当线程池线程自身挂起时,它等待 40 秒钟。如果 40 秒过去后线程无事可做,则该线程将激活并自行销毁,释放出它使用的全部操作系统资源(堆栈、内核对象,等等)。同时,激活并自行销毁线程可能并不影响应用 程序的性能,因为应用程序做的事情毕竟不是太多,否则就会恢复执行该线程。顺便说一句,尽管我说线程池中的线程是在 40 秒内自行激活的,但实际上这个时间并没有验证并可以改变。
线程池的一个绝妙特性是:它是启发式的。如果您的应用程序需要执行很多任务,那么线程池将创建更多的线程。如果您的应用程序的工作负载逐渐减少,那么线程池线程将自行终止。线程池的算法确保它仅包含置于其上的工作负荷所需要的线程数!
线程池可以提供的功能:
1.异步调用功能:一般我们调用函数都是同步的,即函数返回后,才执行下一句代码。但是,用线程池可以异步的调用我们的函数,调用后,马上执行下一句,至于线程的什么时候返回,是主线程所不知道的。
另外,请注意,永远不要调用任何可以自己创建线程的方法; 如果需要,CLR 的线程池将自动创建线程,如果可能还将重用现有的线程。另外,线程处理回调方法后不会立即销毁该线程;它将返回到线程池并准备处理队列中的其他工作项。使 用System.Theading.TheadPool中的QueueUserWorkItem 会使您的应用程序更有效,因为您将不需要为每个客户端请求创建和销毁线程。
例:下面写了一个线程池来实现异步调用的方法:
using System.Collections;
using System.Threading;
public class MyClass
{
public static void Main()
{
Console.WriteLine("Main Thread:Queuing an aynchronous operation");
ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(MyAsyncOperation));
Console.WriteLine("Main Thread:Performing other operation");
Console.WriteLine("Main thread: Pausing to simulate doing other operations.");
Console.ReadLine();
}
static void MyAsyncOperation(object state)
{
Console.WriteLine("ThreadPool thread:Perform aynchronous operation");
Thread.Sleep(5000);
}
}
知道它的结果是什么吗?如果是传统的调用MyAsyncOperation函数,可以肯定得出结论,是输出:
Main Thread:Queuing an aynchronous operation
ThreadPool thread:Perform aynchronous operat
Main Thread:Performing other operation
Main thread: Pausing to simulate doing other operations
但是,用了线程池,创建一个线程来执行这个函数,结果却大不一样。以下是执行结果的截图:
2.以一定的时间间隔调用方法:
如果应用程序需要在某个时间执行某项任务,或者定时执行某个任务。那么,就使用线程池吧。
System.Threading.Timer类可以为你构造这样的功能。函数原型如下:
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);
用户定义的线程函数可以这样定义:
我们写一下让线程池线程立即调用一个方法,并且每隔 2000 毫秒(或两秒)再次调用的应用程序。
以下是程序:
using System.Collections;
using System.Threading;
public class MyClass
{
static int Times=0;
public 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),"Timeing",0,2000);
Console.ReadLine();
}
static void CheckStatus(object state)
{
Console.WriteLine("Checking Status:"+Convert.ToString(state)+" "+(Times++).ToString()+"'s");
}
}
以下是输出的截图:
3.当单个内核对象得到信号通知时调用方法
Jeffer Richter文章中说到:
Microsoft 研究人员在做性能研究时发现,许多应用程序生成线程,只是为了等待某单个内核对象得到信号通知。一旦该对象得到信号通知,这个线程就将某种通知发送给另一 个线程,然后环回,等待该对象再次发出信号。有些开发人员编写的代码中甚至有几个线程,而每个线程都在等待一个对象。这是系统资源的巨大浪费。因此,如果 当前您的应用程序中有多个线程在等待单个内核对象得到信号通知,那么线程池仍将是您提高应用程序性能的最佳资源。
至于它应该怎么使用呢?
要让线程池线程在内核对象得到信号通知时调用您的回调方法,您可以再次利用 System.Threading.ThreadPool 类中定义的一些静态方法。要让线程池线程在内核对象得到信号通知时调用方法,您的代码必须调用一个重载的 RegisterWaitHandle 方法
它的原型如下:
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);
第一个参数h表示你要等待的内核对象,第二个参数callback表示调用的用户线程函数,第三参数state是传递给用户线程函数的参数,第四个参数 milliseconds表示线程池内核对象得到信号通知前应该等待的时间,通常传递-1(就跟前面提到的函数WaitForSingleObject第 二个参数作用相同),表示无限超时。第五个参数executeOnlyOnce 为真,那么线程池线程将仅执行回调方法一次。但是,如果 executeOnlyOnce 为假,那么线程池线程将在内核对象每次得到信号通知时执行回调方法。
客户端定义的函数原型:
当调用回调方法时,会传递给它状态数据和 Boolean 值 timedOut。如果 timedOut 为假,则该方法知道它被调用的原因是内核对象得到信号通知。如果 timedOut 为真,则该方法知道它被调用的原因是内核对象在指定时间内没有得到信号通知。回调方法应该执行所有必需的操作。
看具体的代码:
using System.Collections;
using System.Threading;
public class MyClass
{
public 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();
}
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.");
}
}
执行如图所示:
Jeff的原文:http://blog.chinaunix.net/article.php?articleId=43400&blogId=5958