ReaderWriterLock class
它定义了一种锁,提供惟一写/多读的机制,使得读写的同步.任意数目的线程均可以读数据,数据锁在有线程更新数据时将是须要的.读的线程能够获取锁,当且仅当这里没有写的线程.当没有读线程和其余的写线程时,写线程能够获得锁.所以,一旦writer-lock被请求,全部的读线程将不能读取数据直到写线程访问完毕.它支持暂停而避免死锁.它也支持嵌套的读/写锁.支持嵌套的读锁的方法是ReaderWriterLock.AcquireReaderLock,若是一个线程有写锁则该线程将暂停
;
支持嵌套的写锁的方法是ReaderWriterLock.AcquireWriterLock,若是一个线程有读锁则该线程暂停.若是有读锁将容易却是死锁.安全的办法是使用ReaderWriterLock.UpgradeToWriterLock方法,这将使读者升级到写者.你能够用ReaderWriterLock.DowngradeFromWriterLock方法使写者降级为读者.调用ReaderWriterLock.ReleaseLock将释放锁, ReaderWriterLock.RestoreLock将从新装载锁的状态到调用ReaderWriterLock.ReleaseLock之前
.
结论
:
这部分讲述了.NET平台上的线程同步的问题.造接下来的系列文章中我将给出一些例子来更进一步的说明这些使用的方法和技巧.虽然线程同步的使用会给咱们的程序带来很大的价值,可是咱们最好可以当心使用这些方法.不然带来的不是受益,而将却是性能降低甚至程序崩溃.只有大量的联系和体会才能使你驾驭这些技巧.尽可能少使用那些在同步代码块完成不了或者不肯定的阻塞的东西,尤为是I/O操做;尽量的使用局部变量来代替全局变量;同步用在那些部分代码被多个线程和进程访问和状态被不一样的进程共享的地方;安排你的代码使得每个数据在一个线程里获得精确的控制;不是共享在线程之间的代码是安全的;在下一篇文章中咱们将学习线程池有关的知识.
若是你仔细阅读了我前面的三篇文章,我相信你对用.NET Framework提供的System.Threading.Thread类和一些线程同步的类基本的线程知识和多线程编程知识很了解。咱们将在这里进一步讨论一些.NET类,以及他们在多线程编程中扮演的角色和怎么编程。它们是
:
System.Threading.ThreadPool 类
System.Threading.Timer 类
若是线程的数目并非不少,并且你想控制每一个线程的细节诸如线程的优先级等,使用Thread是比较合适的;可是若是有大量的线程,考虑使用线程池应该更好一些,它提供了高效的线程管理机制来处理多任务。 对于按期的执行任务Timer类是合适的;使用表明是异步方法调用的首选。
System.Threading.ThreadPool Class
当你建立应用程序时,你应该认识到大部分时间你的线程在空闲的等待某些事件的发生(诸如按下一个键或侦听套节子的请求)。毫无疑问的,你也会认为这是绝对的浪费资源。
若是这里有不少的任务须要完成,每一个任务须要一个线程,你应该考虑使用线程池来更有效的管理你的资源而且从中受益。线程池是执行的多个线程集合,它容许你添加以线程自动建立和开始的任务到队列里面去。使用线程池使得你的系统能够优化线程在CPU使用时的时间碎片。可是要记住在任何特定的时间点,每个进程和每一个线程池只有一个一个正在运行的线程。这个类使得你的线程组成的池能够被系统管理,而使你的主要精力集中在工做流的逻辑而不是线程的管理。
当第一次实例化ThreadPool类时线程池将被建立。它有一个默认的上限,即每处理器最多能够有25个,可是这个上限是能够改变的。这样使得处理器不会闲置下来。若是其中一个线程等待某个事件的发生,线程池将初始化另一个线程并投入处理器工做,线程池就是这样不停的建立工做的线程和分配任务给那些没有工做的在队列里的线程。惟一的限制是工做线程的数目不能超过最大容许的数目。每一个线程将运行在默认的优先级和使用默认的属于多线程空间的堆栈大小空间。一旦一项工做任务被加入队列,你是不能取消的。
请求线程池处理一个任务或者工做项能够调用QueueUserWorkItem方法。这个方法带一个WaitCallback表明类型的参数,这个参数包装了你药完成的任务。运行时自动为每个的任务建立线程而且在任务释放时释放线程。
下面的代码说明了如何建立线程池和怎样添加任务:
public void afunction(object o)
{
// do what ever the function is supposed to do.
}
//thread entry code
{
// create an instance of WaitCallback
WaitCallback myCallback = new WaitCallback (afunction);
//add this to the thread pool / queue a task
ThreadPool.QueueUserWorkItem (myCallback);
}
你也能够经过调用ThreadPool.RegisterWaitForSingleObject方法来传递一个System.Threading.WaitHandle,当被通知或者时间超过了调用被System.Threading.WaitOrTimerCallback包装的方法。
线程池和基于事件的编程模式使得线程池对注册的WaitHandles的监控和对合适的WaitOrTimerCallback表明方法的调用十分简单(当WaitHandle被释放时)。这些作法其实很简单。这里有一个线程不断的观测在线程池队列等待操做的状态。一旦等待操做完成,一个线程将被执行与其对应的任务。所以,这个方法随着出发触发事件的发生而增长一个线程。
让咱们看看怎么随事件添加一个线程到线程池,其实很简单。咱们只须要建立一个ManualResetEvent类的事件和一个WaitOrTimerCallback的表明,而后咱们须要一个携带表明状态的对象,同时咱们也要决定休息间隔和执行方式。咱们将上面的都添加到线程池,而且激发一个事件:
public void afunction(object o)
{
// do what ever the function is supposed to do.
}
//object that will carry the status information
public class anObject
{
}
//thread entry code
{
//create an event object
ManualResetEvent aevent = new ManualResetEvent (false);
// create an instance of WaitOrTimerCallback
WaitOrTimerCallback thread_method = new WaitOrTimerCallback (afunction);
// create an instance of anObject
anObject myobj = new anObject();
// decide how thread will perform
int timeout_interval = 100;
// timeout in milli-seconds.
bool onetime_exec = true;
//add all this to the thread pool.
ThreadPool. RegisterWaitForSingleObject (aevent, thread_method, myobj, timeout_interval, onetime_exec);
// raise the event
aevent.Set();
}
在QueueUserWorkItem和RegisterWaitForSingleObject方法中,线程池建立了一个后台的线程来回调。当线程池开始执行一个任务,两个方法都将调用者的堆栈合并到线程池的线程堆栈中。若是须要安全检查将耗费更多的时间和增长系统的负担,所以能够经过使用它们对应的不安全的方法来避免安全检查。就是ThreadPool.UnsafeRegisterWaitForSingleObject 和ThreadPool.UnsafeQueueUserWorkItem。
你也能够对与等待操做无关的任务排队。 Timer-queue timers and registered wait operations也使用线程池。它们的返回方法也被放入线程池排队。
线程池是很是有用的,被普遍的用于。NET平台上的套节子编程,等待操做注册,进程计时器和异步的I/O。对于小而短的任务,线程池提供的机制也是十分便利处于多线程的。线程池对于完成许多独立的任务并且不须要逐个的设置线程属性是十分便利的。可是,你也应该很清楚,有不少的状况是能够用其余的方法来替代线程池的。好比说你的计划任务或给每一个线程特定的属性,或者你须要将线程放入单个线程的空间(而线程池是将全部的线程放入一个多线程空间),抑或是一个特定的任务是很冗长的,这些状况你最好考虑清楚,安全的办法比用线程池应该是你的选择。
System.Threading.Timer Class
Timer类对于周期性的在分离的线程执行任务是很是有效的,它不能被继承。
这个类尤为用来开发控制台应用程序,由于System.Windows.Forms.Time是不可用的。好比同来备份文件和检查数据库的一致性。
当建立Timer对象时,你药估计在第一个代理调用以前等待的时间和后来的每次成功调用之间的时间。一个定时调用发生在方法的应得时间过去,而且在后来周期性的调用这个方法。你能够适应Timer的Change方法来改变这些设置的值或者使Timer失效。当定时器Timer再也不使用时,你应该调用Dispose方法来释放其资源。
TimerCallback表明负责指定与Timer对象相关联的方法(就是要周期执行的任务)和状态。它在方法应得的时间过去以后调用一次而且周期性的调用这个方法直到调用了Dispose方法释放了Timer的全部资源。系统自动分配分离的线程。
让咱们来看一段代码看看事如何建立Timer对象和使用它的。咱们首先要建立一个TimerCallback代理,在后面的方法中要使用到的。若是须要,下一步咱们要建立一个状态对象,它拥有与被代理调用的方法相关联的特定信息。为了使这些简单一些,咱们传递一个空参数。咱们将实例化一个Timer对象,而后再使用Change方法改变Timer的设置,最后调用Dispose方法释放资源。
// class that will be called by the Timer
public class WorkonTimerReq
{
public void aTimerCallMethod()
{
// does some work
}
}
//timer creation block
{
//instantiating the class that gets called by the Timer.
WorkonTimerReq anObj = new WorkonTimerReq () ;
// callback delegate
TimerCallback tcallback = new TimerCallback(anObj. aTimerCallMethod) ;
// define the dueTime and period
long dTime = 20 ;
// wait before the first tick (in ms)
long pTime = 150 ;
// timer during subsequent invocations (in ms)
// instantiate the Timer object
Timer atimer = new Timer(tcallback, null, dTime, pTime) ;
// do some thing with the timer object
...
//change the dueTime and period of the Timer
dTime=100;
pTime=300;
atimer.Change(dTime, pTime) ;
// do some thing
...
atimer.Dispose() ;
...
}
异步编程
这部份内容若是要讲清楚原本就是很大的一部分,在这里,我不打算详细讨论这个东西,咱们只是须要直到它是什么,由于多线程编程若是忽律异步的多线程编程显然是不该该的。异步的多线程编程是你的程序可能会用到的另一种多线程编程方法。
在前面的文章咱们花了很大的篇幅来介绍线程的同步和怎么实现线程的同步,可是它有一个固有的致命的缺点,你或许注意到了这一点。那就是每一个线程必须做同步调用,也就是等到其余的功能完成,不然就阻塞。固然,某些状况下,对于那些逻辑上相互依赖的任务来讲是足够的。异步编程容许更加复杂的灵活性。一个线程能够做异步调用,不须要等待其余的东西。你可使用这些线程做任何的任务,线程负责获取结果推动运行。这给予了那些须要管理数目巨大的请求并且负担不起请求等待代价的企业级的系统更好的可伸缩性。
.NET平台提供了一致的异步编程机制用于ASP.NET,I/O,Web Services,Networking,Message等。
后记
因为学习的时候很难找到中文这方面的资料,所以我就只好学习英文的资料,因为水平不高,翻译的时候可能不免曲解原文的意思,但愿你们可以指出,同时但愿这些东西可以给你们在学习这方面知识给予必定的参考和帮助,那怕是一点点,就很欣慰了。