上一篇介绍了如何开启线程,线程间相互传递参数,及线程中本地变量和全局共享变量区别。
本篇主要说明线程同步。
如果有多个线程同时访问共享数据的时候,就必须要用线程同步,防止共享数据被破坏。如果多个线程不会同时访问共享数据,可以不用线程同步。
线程同步也会有一些问题存在:
性能损耗。获取,释放锁,线程上下文建切换都是耗性能的。
同步会使线程排队等待执行。
当线程调用Sleep,Join,EndInvoke,线程就处于阻塞状态(Sleep使调用线程阻塞,Join、EndInvoke使另外一个线程阻塞),会立即从cpu退出。(阻塞状态的线程不消耗cpu)
当线程在阻塞和非阻塞状态间切换时会消耗几毫秒时间。
加锁使多个线程同一时间只有一个线程可以调用该方法,其他线程被阻塞。
同步对象的选择:
使用引用类型,值类型加锁时会装箱,产生一个新的对象。
使用private修饰,使用public时易产生死锁。(使用lock(this),lock(typeof(实例))时,该类也应该是private)。
string不能作为锁对象。
不能在lock中使用<code>await</code>关键字
如果被锁定的方法是静态的,那么这个锁必须是静态类型。这样就是在全局锁定了该方法,不管该类有多少个实例,都要排队执行。
如果被锁定的方法不是静态的,那么不能使用静态类型的锁,因为被锁定的方法是属于实例的,只要该实例调用锁定方法不产生损坏就可以,不同实例间是不需要锁的。这个锁只锁该实例的方法,而不是锁所有实例的方法.*
同步对象可以兼作它lock的对象
如:
<code>lock</code>其实是<code>Monitors</code>的简洁写法。
两者其实是一样的。
互斥锁是一个互斥的同步对象,同一时间有且仅有一个线程可以获取它。可以实现进程级别上线程的同步。
互斥锁可以在不同的进程间实现线程同步
使用互斥锁实现一个一次只能启动一个应用程序的功能。
互斥锁的带有三个参数的构造函数
initiallyOwned: 如果initiallyOwned为true,互斥锁的初始状态就是被所实例化的线程所获取,否则实例化的线程处于未获取状态。
name:该互斥锁的名字,在操作系统中只有一个命名为name的互斥锁mutex,如果一个线程得到这个name的互斥锁,其他线程就无法得到这个互斥锁了,必须等待那个线程对这个线程释放。
createNew:如果指定名称的互斥体已经存在就返回false,否则返回true。
<code>lock</code>和<code>mutex</code>可以实现线程同步,确保一次只有一个线程执行。但是线程间的通信就不能实现。如果线程需要相互通信的话就要使用<code>AutoResetEvent</code>,<code>ManualResetEvent</code>,通过信号来相互通信。它们都有两个状态,终止状态和非终止状态。只有处于非终止状态时,线程才可以阻塞。
<code>AutoResetEvent</code> 构造函数可以传入一个bool类型的参数,<code>false</code>表示将<code>AutoResetEvent</code>对象的初始状态设置为非终止。如果为<code>true</code>标识终止状态,那么<code>WaitOne</code>方法就不会再阻塞线程了。但是因为该类会自动的将终止状态修改为非终止,所以,之后再调用<code>WaitOne</code>方法就会被阻塞。
<code>WaitOne</code> 方法如果<code>AutoResetEvent</code>对象状态非终止,则阻塞调用该方法的线程。可以指定时间,若没有获取到信号,返回false
<code>set</code> 方法释放被阻塞的线程。但是一次只可以释放一个被阻塞的线程。
<code>ManualResetEvent</code>和<code>AutoResetEvent</code>用法类似。
<code>AutoResetEvent</code>在调用了<code>Set</code>方法后,会自动的将信号由释放(终止)改为阻塞(非终止),一次只有一个线程会得到释放信号。而<code>ManualResetEvent</code>在调用<code>Set</code>方法后不会自动的将信号由释放(终止)改为阻塞(非终止),而是一直保持释放信号,使得一次有多个被阻塞线程运行,只能手动的调用<code>Reset</code>方法,将信号由释放(终止)改为阻塞(非终止),之后的再调用Wait.One方法的线程才会被再次阻塞。
如果一个变量被多个线程修改,读取。可以用<code>Interlocked</code>。
计算机上不能保证对一个数据的增删是原子性的,因为对数据的操作也是分步骤的:
将实例变量中的值加载到寄存器中。
增加或减少该值。
在实例变量中存储该值。
<code>Interlocked</code>为多线程共享的变量提供原子操作。
<code>Interlocked</code>提供了需要原子操作的方法:
public static int Add (ref int location1, int value); 两个参数相加,且把结果和赋值该第一个参数。
public static int Increment (ref int location); 自增。
public static int CompareExchange (ref int location1, int value, int comparand);
location1 和comparand比较,被value替换.
value 如果第一个参数和第三个参数相等,那么就把value赋值给第一个参数。
comparand 和第一个参数对比。
如果要确保一个资源或数据在被访问之前是最新的。那么就可以使用<code>ReaderWriterLock</code>.该锁确保在对资源获取赋值或更新时,只有它自己可以访问这些资源,其他线程都不可以访问。即排它锁。但用改锁读取这些数据时,不能实现排它锁。
<code>lock</code>允许同一时间只有一个线程执行。而<code>ReaderWriterLock</code>允许同一时间有多个线程可以执行读操作,或者只有一个有排它锁的线程执行写操作。
本文转自zsdnr 51CTO博客,原文链接:http://blog.51cto.com/12942149/1949803,如需转载请自行联系原作者