最近项目中使用到多线程,之前使用的不多。借此机会也好好学习和总结一下。
1. lock 与 Monitor
lock是我们最常用的,原因嘛简单易用代码干净。其内部就是通过Monitor来实现的,通过Monitor.Enter(obj)来获取排它锁,通过Monitor.Exit(obj)来释放锁。这样放在其中的代码就成为了临界区,当一个线程进入后另一个线程只能等待排它锁。
lock的参数必须是基于引用类型的对象,不要是基本类型像bool,int什么的,这样根本不能同步,原因是lock的参数要求是对象,如果传入基本类型,势必要发生装箱操作,这样每次lock的都将是一个新的不同的对象。最好避免使用 public类型或不受程序控制的对象实例,因为这样很可能导致死锁。特别是不要使用字符串作为lock的参数,因为字符串被CLR“暂留”,就是说整个应用程序中给定的字符串都只有一个实例,因此更容易造成死锁现象。建议使用不被“暂留”的私有或受保护成员作为参数。其实某些类已经提供了专门用于被锁的成员,比如Array类型提供SyncRoot,许多其它集合类型也都提供了SyncRoot。
所以,使用lock应该注意以下几点:
a. 如果一个类的实例是public的,最好不要lock(this)。因为使用你的类的人也许不知道你用了lock,如果他new了一个实例,并且对这个实例上锁,就很容易造成死锁。
b. 如果MyType是public的,不要lock(typeof(MyType))
c. 永远也不要lock一个字符串
Monitor为我们提供了更多的控制功能:
a. TryEnter 方法可以判断当前临界区是否可以进入,而不用等待。但注意的是如果此函数返回True说明已经进入临街区,不需要再调用Enter方法。而且还可以传入Try的超时时间,从而更灵活的进行控制。
b. Wait 方法可以使线程释放当前的所有资源和锁使用其它线程可以进入临街区,并且在调用Wait的地方挂起。直到其它线程调用Pulse来唤醒线程继续进行。
c. Pluse(Pluse, PluseAll)方法唤醒等待的线程继续工作。和Wait方法一起可以实现等待和唤醒机制。
2. Mutex 与 Semaphore
这两个都可以通过命名参数来跨进程进行同步,区别是Mutex一次只允许一个线程进入,而Semaphore可以N个线程进入,当N=1时和Mutex一致。
3. AutoResetEvent 与 ManualResetEvent
它们之间唯一不同的地方就是在激活线程之后,状态是否自动由终止变为非终止。 AutoResetEvent自动变为非终止,就是说一个AutoResetEvent只能激活一个线程。而ManualResetEvent要等到它的 Reset方法被调用,状态才变为非终止,在这之前,ManualResetEvent可以激活任意多个线程。
4. SynchronizationAttribute 与 MethodImplAttribute
当我们确定某个类的实例在同一时刻只能被一个线程访问时,我们可以直接将类标识成Synchronization的,这样,CLR会自动对这个类实施同步机制。
我们可以确保类的实例无法被多个线程同时访问
a.在类的声明中,添加System.Runtime.Remoting.Contexts.SynchronizationAttribute属性。
b. 继承至System.ContextBoundObject
需要注意的是,类必须继承至System.ContextBoundObject,换句话说,类必须是上下文绑定的。
如果临界区是跨越整个方法的,也就是说,整个方法内部的代码都需要上锁的话,使用MethodImplAttribute属性会更简单一些。锁整个方法在使用上不太灵活,不建议使用。
5. ReaderwriterLock
使用ReaderWriterLock进行资源访问时,如果在某一时刻资源并没有获取写的独占权,那么可以获得多个读的访问权,单个写入的独占权,如果某一时刻已经获取了写入的独占权,那么其它读取的访问权必须进行等待。
6. Interlocked
百度的一个面试题:
以下多线程对int型变量x的操作,哪几个不需要进行同步:
A. x=y; B. x++; C. ++x; D. x=1;
答案是D,可以++, -- 和赋值操作都不是原子操作。也就是说都不能一条机器指令完成。赋值操作需要将原操作数放入寄存器再从转存在目的操作数。而++,-- 还需要在放入寄存器后进行加减操作,在这些操作的过程中很可能发生线程切换。Interlocked为我们提供了加减和赋值的原子操作。
7. volatile 关键字
volatile的含义就是告诉处理器, 不要将我放入工作内存, 请直接在主存操作我。因此,当多线程同时访问该变量时,都将直接操作主存,从本质上做到了变量共享。但volatile并不能实现真正的同步,因为它的操作级别只停留在变量级别,而不是原子级别。如果是在单处理器系统中,是没有任何问题的,变量在主存中没有机会被其他人修改,因为只有一个处理器,这就叫作processor Self-Consistency。但在多处理器系统中,可能就会有问题。 每个处理器都有自己的data cach,而且被更新的数据也不一定会立即写回到主存。所以可能会造成不同步,但这种情况很难发生,因为cach的读写速度相当快,flush的频率也相当高,只有在压力测试的时候才有可能发生,而且几率非常非常小。
参考:http://www.cnblogs.com/michaelxu/archive/2008/09/20/1293716.html
http://blog.csdn.net/morewindows/article/details/7392749
http://archlord.blog.hexun.com/27358184_d.html