进程
是应用程序的一个实例要使用的资源的一个集合,每个进程都被赋予了一个虚拟空间,以确保另一个其它进程无法访问。
进程在者创建时需要分配大量的内存并且初始化加载EXE以及大量的DLL资源。
线程
相当于逻辑CPU用以解决进程长时间占用资源的问题,在创建时被分配大约1M的内存。线程以CPU时间片单位运行,当时间片到期后自动切换到另一个线程执行,从而缓解一个进程或线程长期占用资源的情况。线程切换伴随着上下文资源的切换,所以大量的线程切换也是浪费性能的事情。
一个进程拥有一个专用线程,理论上有多少进程最少就有多少线程,当然实际情况上一个进程可能拥有很多线程。我们可以通过任务管理器的添加选项列线程数来查看一个进程拥有多少线程。可以看出大部分进程的CPU使用率为零,但是线程却占据着大量内存资源。(一个线程栈要分配1M内存)。
一般进程的主线程会被标记[STAThread]属性指示应用程序的默认线程模型是单线程单元 (STA)。这个属性主要针对与COM Interop互操作,如果在当前操作COM Interop对象的线程未标记STAThread或设置线程STAThread属性,则会出现异常。如果没有使用COM Interop,一般不需要这个Attribute。
线程安全
只有在多个线程争夺共享资源并且进行破坏操作(如:写操作等)的情况才会有线程安全问题,如果多个线程在调用同一个实例的同一方法时并没出现资源争夺与破坏(如:只读),当然不会存在安全问题。但是大多数情况,非静态类的实例方法大都是操作某些属性字段资源,因此不可必免的出现对同一字段的资源的写操作,从而破坏了数据。同理,静态方法如果引用静态字段也会碰到线程安全的问题。判断一个方法是否线程安全,是看该方法是否存在对共享资源的争夺破坏。
静态类/静态字段/静态方法
静态构造函数(类型构造器)以互斥方式只被CLR执行一次,保证静态构造函数只被执行一次。
静态字段是非线程安全的,在多个线程*享着同一个实例或静态类的静态字段。
静态方法在多个线程中为同一个实例或静态类的副本,所以是安全的。但是一理静态方法引用了静态字段,就不是线程安全了,因此应在静态方法必免使用静态字段,要么以互斥体的方式操作静态。
大多数据库操作类如SQLHelper此类都设计为静态类的,因为静态方法在不引用静态字段的前提下是线程安全的,所以基本上看到SQL 单连接的情况都是使用静态方法,可以防止单连接并发操作。
静态字段设计
定义静态字段时需要考虑线程安全
const常量定义(const在IL编译自动缀加static,所以不需要在static修饰),常量字段需要定义时就必须初始化一个值。
static readonly 只读静态变量 ,有一次构造初始化静态变量的机会。
当然你如果确实需要线程中修改静态变量的时,那就需要设计一个互斥锁或者InterLocked.CompareExchange方式操作。
静态事件
静态事件同非静态事件一样,区别就在于一个需要类实例化才能注册订阅,而另一个则是直接注册订阅。来看一下场景:
1.当A类(非静态事件)一个实例做一件事想同时通知X,Y,Z几个对象时,则我们要保证传递给X,Y,Z的A的实例必须是同一个。也就是说无论通过构造注入或者属性注入或者方法参数传递,都要保证是同一个A的实例,这样才能保证X,Y,Z注册到的是同一个事件。
但是如果A类是静态事件呢,则我们直接在X,Y,Z中直接注册订阅静态事件,并不需要解决同一个实例传递的问题。 想想如果有大量的不同的对象要通知,那如何保证同一个非静态事件的实例在这些对象中传递呢?
2.假如希望A类(非静态事件)的所有实例做的任何事情都通知到X,Y,Z ,那我们应该怎么做呢? 那肯定是要A所有实例的非静态事件都被X,Y,Z订阅,这样才能保证X,Y,Z接收到所有A实例事件触发的通知,但是想想这有多困难呢,你怎么知道A类会在哪些地方被实例化呢?并且还要在所有实例化的地方都要重复相同订阅的代码,这是否麻烦呢?我们看一下静态事件怎么解决这个问题的
假如A类使用了静态事件,而X,Y,Z则分别订阅了这个静态事件(只要针对静态事件订阅一次)。我们让A类的非静态方法完成时触发这个静态事件,想想会是什么情况? 不管A有多少实例化的对象,只要这些对象调用了非态方法触发了静态事件,则X,Y,Z都会收到通知。是不是简单多少了,不用担心A所有实例做的事件你不知道了。 这个应用场景很容易让人联想到数据库操作类,你想知道所有SQL操作,完全可以为你的数据库操作类设计一个静态事件的通知以便能在某个地方监控到的所有SQL语句。
以上两种情况可以看出,静态事件很容易的解决了一对多订阅以及多对一或多对多的订阅通知。当然我们也可以利用单例模式或者IOC容器容易的实现非静态事件分散通知的困难,但是要注意到一点静态字段不等同于静态类,单例模式以静态字段的形式保证了唯一实例,却很可能导致了线程的不安全。因为静态变量资源是被多个线程共享,你无法保证你的实例做为静态使用时,其中不会出资源争夺的情况发生。
lock/using/foreach/前台线程/后台线程
在多线程使用lock时,lock代码IL会自动编译为try..catch..finally... ,以确保在发生异常时lock可以正常解锁释放资源。相比Moniter.Entry与Monitr.Exit 就要自己解决异常释放锁的操作。
在using使用时也会自动封装try..catch..finally... ,在finally时会调用资源的Dispose释放对象资源. foreach同样也是如此。
开启线程时,要注意设置前台线程还是后台线程,因为线程终结的方式不一样。
前台线程:进程在关闭时,必须等待前台线程执行完成。
后台线程:进程在关闭时,会直接终止后台线程,导致线程任务无法完成。