线程是一个很重要的概念。在对待复杂逻辑或者要求程序高响应速度时线程会显得非常重要。尤其随着个人电脑上多核CPU的普及,合理的利用线程更能提高计算速度。掌握线程这把利剑是学习和使用.net中最重要的工作之一。
.net中的线程是灵活高效的。通过System.Threading.Thread就可以轻松创建线程,通过System.Threading.ThreadPool也可以自如的使用线程。那在具体的应用过程该怎样决定使用手工线程还是线程池线程。有一把简单的尺子可以帮你决定:若该工作在执行过程中不需要太多的干涉,可以采用线程池线程;若在工作执行的过程中需要很多线程方面的控制,采用手工线程可能会更方便一点。
另外线程有一个很重要的IsBackGround属性,该属性主要决定线程和主线程的生命期关系。若IsBackGround为false,则该线程和主线程的生命期是不统一的,即使主线程已经结束,只要该线程还没有结束,整个进程仍然是有效的。若你在作多线程的时候发现自己的程序总是不能彻底的退出,不妨查一下IsBackGround属性。若IsBackGround为true,则该线程和主线程的生命期是统一的,一旦主线程结束,该线程也将关闭,若没有其他活动的线程,进程将彻底终止。
用多线程很关键的一点就是线程之间该怎样的传递数据。有一点得明确,一般情况下,进程中所有线程对进程数据都是共享的。也就是说对象中的数据可以被多个线程访问。不过通过LocalDataStorSlot可以创建属于线程自身的数据,这在某些特殊应用中很关键。另外[ThreadStatic]特性修饰的类字段(静态字段)也是线程数据访问中的一个特例,具有该特性的字段,对同一个线程内的各个对象都是统一的,但对不同的线程访问是完全独立的。
懂得了怎样去创建线程,线程访问数据的原则后很重要的工作就是怎样去同步互斥多个线程的运行。同步上AutoResetEvnet和ManualRestetEvent两个可以看作是红绿灯,使用起来也是铿锵有力。Mutex是一个更强大的同步工具,不过得注意它是操作系统级的,相应的性能开销是比较大的。互斥中lock是最常用的保护数据的手段了,lock的范围应该尽量的小,对大范围的lock应该尽量避免。值类型因处于栈上,不能进行lock操作,替代的做法是
object mutex = new object();
lock(mutex)
{
// your code here
}。
更多的上下文同步可以对类使用[Synchronization((Int32)SynchronizationAttribute.REQUIRED)]特性,或者对方法使用System.Runtime.CompileService名字空间中的[MethodImpl(MethodImplOptions.Synchronized)]特性。
若你发现程序跑着跑着某些线程丢了,那得注意一下,看看线程里是不是发生了异常。.net中线程终止的条件主要有:工作完成自然死亡、发生未处理异常而意外死亡、被别人用Abort给谋杀了、其他更高级的死亡方式。如果工作线程有可能抛出异常,那就应该在该线程中捕获并处理它,若异常足够严重以影响线程工作,那只好终止线程了。
说到线程的手工终止,最好不要使用Abort,因为并不能此时线程执行到了什么地方,Abort产生的结果将是不可预料的。一个推荐的方法是使用一个死亡标记,要线程终止的时候只要将continueFlag设置为false即可。
bool continueFlag = true;
void MyProc()
{
while(continueFlag)
{
//do some work
}
}
但该方法也有一定的局限性,当设置死亡标记后只有工作线程完成该次工作后再次检测continueFlag的值时才会终止,某些复杂的环境中可能需要的是线程的立即终止。对于顺序执行的工作线程,因没有while形式的结构,该方法也是无效的。
以上是我自己使用.net线程中自己的一点体会,若有不妥之处,还请不吝指教。
评论