您的位置:首页 > 编程语言 > C#

C#线程同步方法汇总

2015-05-13 08:59 78 查看
 我们在编程的时候,有时会使用多线程来解决问题,比如你的程序需要在 后台处理一大堆数据,但还要使用户界面处于可操作状态;或者你的程序需要访问一些外部资源如数据库或网络文件等。这些情况你都可以创建一个子线程去处理, 然而,多线程不可避免地会带来一个问题,就是线程同步的问题。如果这个问题处理不好,我们就会得到一些非预期的结果。

  在网上也看过一些关于线程同步的文章,其实线程同步有好几种方法,下面我就简单的做一下归纳。

  一、volatile关键字

  volatile是最简单的一种同步方法,当然简单是要付出代价的。它只能在变量一级做同步,volatile的含义就是告诉处理器, 不要将我放入工作内存, 请直接在主存操作我。(【转自 www.bitsCN.com 】)因此,当多线程同时访问该变量时,都将直接操作主存,从本质上做到了变量共享。

  能够被标识为volatile的必须是以下几种类型:(摘自MSDN)

Any reference type.

Any pointer type (in an unsafe context).

The types sbyte, byte, short, ushort, int, uint, char, float, bool.

An enum type with an enum base type of byte, sbyte, short, ushort, int, or uint.
  如:

Code public class A { private volatile int _i; public int I { get { return _i; } set { _i = value; } } }
  但volatile并不能实现真正的同步,因为它的操作级别只停留在变量级别,而不是原子级别。如果是在单处理器系统中,是没有任何问题的,变量在主存中没有机会被其他人修改,因为只有一个处理器,这就叫作processor Self-Consistency。但在多处理器系统中,可能就会有问题。 每个处理器都有自己的data cach,而且被更新的数据也不一定会立即写回到主存。所以可能会造成不同步,但这种情况很难发生,因为cach的读写速度相当快,flush的频率也相当高,只有在压力测试的时候才有可能发生,而且几率非常非常小。

  二、lock关键字

  lock是一种比较好用的简单的线程同步方式,它是通过为给定对象获取互斥锁来实现同步的。它可以保证当一个线程在关键代码段的时候,另一个线程不会进来,它只能等待,等到那个线程对象被释放,也就是说线程出了临界区。用法:

Code public void Function() { object lockThis = new object (); lock (lockThis) { // Access thread-sensitive resources. } }

  lock的参数必须是基于引用类型的对象,不要是基本类型像 bool,int什么的,这样根本不能同步,原因是lock的参数要求是对象,如果传入int,势必要发生装箱操作,这样每次lock的都将是一个新的不 同的对象。最好避免使用public类型或不受程序控制的对象实例,因为这样很可能导致死锁。特别是不要使用字符串作为lock的参数,因为字符串被 CLR“暂留”,就是说整个应用程序中给定的字符串都只有一个实例,因此更容易造成死锁现象。建议使用不被“暂留”的私有或受保护成员作为参数。其实某些 类已经提供了专门用于被锁的成员,比如Array类型提供SyncRoot,许多其它集合类型也都提供了SyncRoot。

  所以,使用lock应该注意以下几点: 

  1、如果一个类的实例是public的,最好不要lock(this)。因为使用你的类的人也许不知道你用了lock,如果他new了一个实例,并且对这个实例上锁,就很容易造成死锁。

  2、如果MyType是public的,不要lock(typeof(MyType))

  3、永远也不要lock一个字符串

  三、System.Threading.Interlocked

  对于整数数据类型的简单操作,可以用 Interlocked 类的成员来实现线程同步,存在于System.Threading命名空间。Interlocked类有以下方法:Increment ,Decrement , Exchange 和CompareExchange 。使用Increment 和Decrement 可以保证对一个整数的加减为一个原子操作。Exchange 方法自动交换指定变量的值。CompareExchange 方法组合了两个操作:比较两个值以及根据比较的结果将第三个值存储在其中一个变量中。比较和交换操作也是按原子操作执行的。如:

Code int i = 0 ; System.Threading.Interlocked.Increment( ref i); Console.WriteLine(i); System.Threading.Interlocked.Decrement( ref i); Console.WriteLine(i); System.Threading.Interlocked.Exchange( ref i, 100 ); Console.WriteLine(i); System.Threading.Interlocked.CompareExchange( ref i, 10 , 100 );
Output:

Code lock (x) { DoSomething(); } 等效于
object obj = ( object )x; System.Threading.Monitor.Enter(obj); try { DoSomething(); } finally { System.Threading.Monitor.Exit(obj); }

关于用法,请参考下面的代码:

Code private static object m_monitorObject = new object (); [STAThread] static void Main( string [] args) { Thread thread = new Thread( new ThreadStart(Do)); thread.Name = " Thread1 " ; Thread thread2 = new Thread( new ThreadStart(Do)); thread2.Name = " Thread2 " ; thread.Start(); thread2.Start(); thread.Join(); thread2.Join(); Console.Read(); } static void Do() { if ( ! Monitor.TryEnter(m_monitorObject)) { Console.WriteLine( " Can't visit Object " + Thread.CurrentThread.Name); return ; } try { Monitor.Enter(m_monitorObject); Console.WriteLine( " Enter Monitor " + Thread.CurrentThread.Name); Thread.Sleep( 5000 ); } finally { Monitor.Exit(m_monitorObject); } }
  当线程1获取了m_monitorObject对象独占权时,线程2尝试调用TryEnter(m_monitorObject),此时会由于无法获取独占权而返回false,输出信息如下: Code private static ReaderWriterLock m_readerWriterLock = new ReaderWriterLock(); private static int m_int = 0 ; [STAThread] static void Main( string [] args) { Thread readThread = new Thread( new ThreadStart(Read)); readThread.Name = " ReadThread1 " ; Thread readThread2 = new Thread( new ThreadStart(Read)); readThread2.Name = " ReadThread2 " ; Thread writeThread = new Thread( new ThreadStart(Writer)); writeThread.Name = " WriterThread " ; readThread.Start(); readThread2.Start(); writeThread.Start(); readThread.Join(); readThread2.Join(); writeThread.Join();
Console.ReadLine(); } private static void Read() { while ( true ) { Console.WriteLine( " ThreadName " + Thread.CurrentThread.Name + " AcquireReaderLock " ); m_readerWriterLock.AcquireReaderLock( 10000 ); Console.WriteLine(String.Format( " ThreadName : {0} m_int : {1} " , Thread.CurrentThread.Name, m_int)); m_readerWriterLock.ReleaseReaderLock(); } }
private static void Writer() { while ( true ) { Console.WriteLine( " ThreadName " + Thread.CurrentThread.Name + " AcquireWriterLock " ); m_readerWriterLock.AcquireWriterLock( 1000 ); Interlocked.Increment( ref m_int); Thread.Sleep( 5000 ); m_readerWriterLock.ReleaseWriterLock(); Console.WriteLine( " ThreadName " + Thread.CurrentThread.Name + " ReleaseWriterLock " ); } }
在程序中,我们启动两个线程获取m_int的读取访问权,使用一个线程获取m_int的写入独占权,执行代码后,输出如下:

Code [System.Runtime.Remoting.Contexts.Synchronization] public class SynchronizedClass : System.ContextBoundObject {
}

  八、MethodImplAttribute

  如果临界区是跨越整个方法的,也就是说,整个方法内部的代码都需要上锁的 话,使用MethodImplAttribute属性会更简单一些。这样就不用在方法内部加锁了,只需要在方法上面加上 [MethodImpl(MethodImplOptions.Synchronized)] 就可以了,MehthodImpl和MethodImplOptions都在命名空间System.Runtime.CompilerServices 里面。但要注意这个属性会使整个方法加锁,直到方法返回,才释放锁。因此,使用上不太灵活。如果要提前释放锁,则应该使用Monitor或lock。我们来看一个例子:

Code [MethodImpl(MethodImplOptions.Synchronized)] public void DoSomeWorkSync() { Console.WriteLine( " DoSomeWorkSync() -- Lock held by Thread " + Thread.CurrentThread.GetHashCode()); Thread.Sleep( 1000 ); Console.WriteLine( " DoSomeWorkSync() -- Lock released by Thread " + Thread.CurrentThread.GetHashCode()); } public void DoSomeWorkNoSync() { Console.WriteLine( " DoSomeWorkNoSync() -- Entered Thread is " + Thread.CurrentThread.GetHashCode()); Thread.Sleep( 1000 ); Console.WriteLine( " DoSomeWorkNoSync() -- Leaving Thread is " + Thread.CurrentThread.GetHashCode()); }
[STAThread] static void Main( string [] args) { MethodImplAttr testObj = new MethodImplAttr(); Thread t1 = new Thread( new ThreadStart(testObj.DoSomeWorkNoSync)); Thread t2 = new Thread( new ThreadStart(testObj.DoSomeWorkNoSync)); t1.Start(); t2.Start(); Thread t3 = new Thread( new ThreadStart(testObj.DoSomeWorkSync)); Thread t4 = new Thread( new ThreadStart(testObj.DoSomeWorkSync)); t3.Start(); t4.Start();
Console.ReadLine(); }
这里,我们有两个方法,我们可以对比一下,一个是加了属性MethodImpl 的DoSomeWorkSync(),一个是没加的DoSomeWorkNoSync()。在方法中Sleep(1000)是为了在第一个线程还在方法中 时,第二个线程能够有足够的时间进来。对每个方法分别起了两个线程,我们先来看一下结果:

Code static AutoResetEvent autoEvent;
static void DoWork() { Console.WriteLine( " worker thread started, now waiting on event

" ); autoEvent.WaitOne(); Console.WriteLine( " worker thread reactivated, now exiting

" ); }
[STAThread] static void Main( string [] args) { autoEvent = new AutoResetEvent( false );
Console.WriteLine( " main thread starting worker thread

" ); Thread t = new Thread( new ThreadStart(DoWork)); t.Start();
Console.WriteLine( " main thrad sleeping for 1 second

" ); Thread.Sleep( 1000 );
Console.WriteLine( " main thread signaling worker thread

" ); autoEvent.Set();
Console.ReadLine(); }
我们先来看一下输出:



在主函数中,首先创建一个AutoResetEvent的实例,参数false 表示初始状态为非终止,如果是true的话,初始状态则为终止。然后创建并启动一个子线程,在子线程中,通过调用AutoResetEvent的 WaitOne方法,使子线程等待指定事件的发生。然后主线程等待一秒后,调用AutoResetEvent的Set方法,使状态由非终止变为终止,重新 激活子线程。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: