【转】 [转]C#线程同步的几种方法 (二)

来源:互联网 发布:时时彩源码出售.txt 编辑:程序博客网 时间:2024/06/07 09:32

 

【转】 [转]C#线程同步的几种方法 (二)
2010-12-08 23:18
转载自 oktell
最终编辑 oktell

 

来自:http://www.cnblogs.com/michaelxu/archive/2008/09/20/1293716.html

  六、ReaderWriterLock

  在考虑资源访问的时候,惯性上我们会对资源实施lock机制,但是在某些情况下,我们仅仅需要读取资源的数据,而不是修改资源的数据,在这种情况下获取资源的独占权无疑会影响运行效率,因此.Net提供了一种机制,使用ReaderWriterLock进行资源访问时,如果在某一时刻资源并没有获取写的独占权,那么可以获得多个读的访问权,单个写入的独占权,如果某一时刻已经获取了写入的独占权,那么其它读取的访问权必须进行等待,参考以下代码:

//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的写入独占权,执行代码后,输出如下:

可以看到,当WriterThread获取到写入独占权后,任何其它读取的线程都必须等待,直到WriterThread释放掉写入独占权后,才能获取到数据的访问权,应该注意的是,上述打印信息很明显显示出,可以多个线程同时获取数据的读取权,这从ReadThread1和ReadThread2的信息交互输出可以看出。

  七、SynchronizationAttribute

  当我们确定某个类的实例在同一时刻只能被一个线程访问时,我们可以直接将类标识成Synchronization的,这样,CLR会自动对这个类实施同步机制,实际上,这里面涉及到同步域的概念,当类按如下设计时,我们可以确保类的实例无法被多个线程同时访问
  1). 在类的声明中,添加System.Runtime.Remoting.Contexts.SynchronizationAttribute属性。
     2). 继承至System.ContextBoundObject
     需要注意的是,要实现上述机制,类必须继承至System.ContextBoundObject,换句话说,类必须是上下文绑定的。
     一个示范类代码如下:

//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)是为了在第一个线程还在方法中时,第二个线程能够有足够的时间进来。对每个方法分别起了两个线程,我们先来看一下结果:

可以看出,对于线程1和2,也就是调用没有加属性的方法的线程,当线程2进入方法后,还没有离开,线程1有进来了,这就是说,方法没有同步。我们再来看看线程3和4,当线程3进来后,方法被锁,直到线程3释放了锁以后,线程4才进来。

  九、同步事件和等待句柄

  用lock和Monitor可以很好地起到线程同步的作用,但它们无法实现线程之间传递事件。如果要实现线程同步的同时,线程之间还要有交互,就要用到同步事件。同步事件是有两个状态(终止和非终止)的对象,它可以用来激活和挂起线程。

  同步事件有两种:AutoResetEvent和 ManualResetEvent。它们之间唯一不同的地方就是在激活线程之后,状态是否自动由终止变为非终止。AutoResetEvent自动变为非终止,就是说一个AutoResetEvent只能激活一个线程。而ManualResetEvent要等到它的Reset方法被调用,状态才变为非终止,在这之前,ManualResetEvent可以激活任意多个线程。

  可以调用WaitOne、WaitAny或WaitAll来使线程等待事件。它们之间的区别可以查看MSDN。当调用事件的 Set方法时,事件将变为终止状态,等待的线程被唤醒。

  来看一个例子,这个例子是MSDN上的。因为事件只用于一个线程的激活,所以使用 AutoResetEvent 或 ManualResetEvent 类都可以。

//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方法,使状态由非终止变为终止,重新激活子线程。

 

原创粉丝点击