在线程里,若是须要共享数据,那么必定须要使用同步技术,确保一次只有一个线程访问和改变共享数据的状态。在.net中,lock语句、Interlocked类和Monitor类可用于进程内部的同步。安全
lock语句是设置锁定和解除锁定的一种简单方式。在使用lock语句以前,先进入另外一个争用条件。例如:this
public class SharedState { public int State { get; set; } } public class Job { SharedState sharedState; public Job(SharedState sharedState) { this.sharedState = sharedState; } public void DoTheJob() { for (int i = 0; i < 50000; i++) { sharedState.State += 1; } } } static void Main() { int numTasks = 20; var state = new SharedState(); var tasks = new Task[numTasks];//定义20个任务 for (int i = 0; i < numTasks; i++) { tasks[i] = Task.Run(() => new Job(state).DoTheJob());//启动20个任务,同时对数据进行修改 } for (int i = 0; i < numTasks; i++) { tasks[i].Wait();//等待全部任务结束 } Console.WriteLine("summarized {0}", state.State);//预想应该输出:summarized 1000000 }
实际上的输出与预想输出并不一致,每次运行的输出结果都不一样,但没有一个是正确的。若是将线程数量减小,那么获得正确值的次数会增多,但也不是每次都正确。spa
使用lock关键字,能够实现多个线程访问同一个数据时的同步问题。lock语句表示等待指定对象的锁定,该对象只能时引用类型。进行锁定后——只锁定了一个线程,就运行lock语句块中的代码,在lock块最后接触锁定,以便另外一个线程能够锁定该对象。.net
lock(obj) { //执行代码 } //锁定静态成员,能够因此其类型(object) lock(typeof(StaticCalss)) { //执行代码 }
因此修改以上的代码,使用SyncRoot模式。可是,若是是对属性的访问进行锁定:线程
public class SharedState { private object syncRoot = new object(); private int state = 0; public int State { get { lock (syncRoot) return state; } set { lock (syncRoot) state = value; } } }
仍会出现前面的争用状况。在方法调用get存储器,以得到state的当前值,而后set存储器给state设置新值。在调用对象的get和set存储器期间,对象并无锁定,另外一个线程仍然能够得到临时值。最好的方法是在不改变SharedState类的前提下,在调用方法中,将lock语句添加到合适的地方:code
public class SharedState { public int State { get; set; } } public class Job { SharedState sharedState; public Job(SharedState sharedState) { this.sharedState = sharedState; } public void DoTheJob() { for (int i = 0; i < 50000; i++) { lock (sharedState) { sharedState.State += 1; } } } }
在一个地方使用lock语句并不意味着访问对象的其余线程都在等待。必须对每一个访问共享数据的线程显示使用同步功能。对象
为使对state的修改做为一个原子操做,修改代码:blog
public class SharedState { private int state = 0; public int State { get { return state; } } public int IncrementState() { lock (this) { return ++state; } } } //外部访问 public void DoTheJob() { for (int i = 0; i < 50000; i++) { sharedState.IncrementState(); } }
Interlocked类用于使变量的简单语句原子化。i++并不是线程安全的,它涉及三个步骤:取值、自增、存值。这些操做可能被线程调度器打断。Interlocked类提供了以线程安全的方式递增、递减、交换和读取值的方法。Interlocked类只能用于简单的同步问题,并且很快。所以,上面的IncrementState()方法的代码能够改成:return Interlocked.Increment(ref state);进程
lcok语句最终会有C#编译器解析为使用Monitor类。rem
lock(obj) { //执行代码 }
简单的lock(obj)语句会被解析为调用Enter()方法,该方法会一直等待,直到线程锁定对象。一次只有一个线程能锁定对象,只要解除锁定,线程就能够进入同步阶段。Monitor类的Exit()方法解除锁定。编译器把Exit()方法放在try块的finally中,不管是否抛出异常,都将在语句块运行末尾解除锁定。
Monitor.Enter(obj); try { //执行代码 } finally { Monitor.Exit(obj); }
相对于lock语句,Mpnitor类能够设置一个等待被锁定的超时值。这样就不会无限期的等待锁定,若是等待锁定时间超过规定时间,则返回false,表示未被锁定,线程再也不等待,执行其余操做。也许之后,该线程会再次尝试得到锁定:
bool lockTaken = false; Monitor.TryEnter(obj,500, ref lockTaken);//在500ms内,是否锁定了对象 if (lockTaken) { try { //执行代码 } finally { Monitor.Exit(obj); } } else { //未得到锁定,执行代码 }
若是基于对象的锁定对象(Monitor)的系统开销因为垃圾回收而太高,能够使用SpinLock结构。,SpinLock结构适用于:有大量的锁定,并且锁定时间老是很是短的状况。应避免使用多个SpinLock结构,也不要调用任何可能阻塞的内容。