多线程的同步

一.同步一般有一下几种方式

1. 维护自由锁(InterLocked)实现同步

2. 监视器(Monitor)和互斥锁(lock)

3. 读写锁(ReadWriteLock)

4. 系统内核对象

1) 互斥(Mutex), 信号量(Semaphore), 事件(AutoResetEvent/ManualResetEvent)

2) 线程池

监视器(Moniter)

///
/// 共同做的工作
///
private void Run()
{
string stringValue = null;
while (true)
{
Monitor.Enter(this);//锁定,保持同步
stringValue = (string)stringList[0];
Console.WriteLine(Thread.CurrentThread.Name + "删除了" + stringValue);
stringList.RemoveAt(0);//删除ArrayList中的元素
if (stringList.Count == 0)
{
OnNumberClear(this, new EventArgs());//引发完成事件
}
Monitor.Exit(this);//取消锁定
Thread.Sleep(5);
}
}


 互斥锁(lock)

static public void ThreadFunc(object name)
{
 string str = name as string;
 Random rand = new Random();
 int count = rand.Next(100, 200);
 for (int i = 0; i < count; i++)
 {
 lock (NumList)
 {
 NumList.Add(i);
 Console.WriteLine("{0} {1}", str, i);
 }
 }
}
读写锁(ReadWriteLock)
static private ReaderWriterLock _rwlock = new ReaderWriterLock();
static public void ReaderThread(int thrdId) 
{ 
  try
  { // 请求读锁,如果100ms超时退出 
  _rwlock.AcquireReaderLock(10); 
  try
  { 
   int inx = _rand.Next(_list.Count); 
   if (inx < _list.Count) 
   Console.WriteLine("{0}thread {1}", thrdId, _list[inx]); 
  } 
  finally
  {
   _rwlock.ReleaseReaderLock(); 
  } 
  } 
  catch (ApplicationException) // 如果请求读锁失败 
  { 
  Console.WriteLine("{0}thread get reader lock out time!", thrdId); 
  } 
 } 
 static public void WriterThread() 
 { 
  try
  {
  // 请求写锁 
  _rwlock.AcquireWriterLock(100); 
  try
  { 
   string val = _rand.Next(200).ToString(); 
   _list.Add(val); // 写入资源 
   Console.WriteLine("writer thread has written {0}", val); 
  } 
  finally
  { // 释放写锁 
   _rwlock.ReleaseWriterLock(); 
  } 
  } 
  catch (ApplicationException) 
  { 
  Console.WriteLine("Get writer thread lock out time!"); 
  } 
}

系统内核对象 互斥对象(Mutex)

互斥对象的作用有点类似于监视器对象,确保一个代码块在同一时刻只有一个线程在执 行。互斥对象和监视器对象的主要区别就是,互斥对象一般用于跨进程间的线程同步,而监视器对象则用于进程内的线程同步。互斥对象有两种:一种是命名互斥; 另一种是匿名互斥。在跨进程中使用到的就是命名互斥,一个已命名的互斥就是一个系统级的互斥,它可以被其他进程所使用,只要在创建互斥时指定打开互斥的名 称就可以。在.Net中互斥是通过Mutex类来实现。

其实对于OpenExisting函数有两个重载版本,

?
1
Mutex.OpenExisting (String)
?
1
Mutex.OpenExisting (String, MutexRights)

对于默认的第一个函数其实是实现了第二个函数 MutexRights.Synchronize|MutexRights.Modify操作。

由于监视器的设计是基于.Net框架,而Mutex类是系统内核对象封装了win32的一个内核结构来实现互斥,并且互斥操作需要请求中断来完成,因此在进行进程内线程同步的时候性能上要比互斥要好。

典型的使用Mutex同步需要完成三个步骤的操作:1.打开或者创建一个Mutex实例;2.调用WaitOne()来请求互斥对象;3.最后调用ReleaseMutex来释放互斥对象。

?

static public void AddString( string str)
{
  // 设置超时时限并在wait前退出非默认托管上下文
  if (_mtx.WaitOne(1000, true ))
  {
  _resource.Add(str);
  _mtx.ReleaseMutex();
  }
}

需要注意的是,WaitOne和ReleaseMutex必须成对出现,否则会导致进程死锁的发生,这时系统(.Net2.0)框架会抛出AbandonedMutexException异常。

信号量(Semaphore)

信号量就像一个夜总会:它有确切的容量,并被保镖控制。一旦满员,就没有人能再进入,其他人必须在外面排队。那么在里面离开一个人后,队头的人就可以进入。信号量的构造函数需要提供至少两个参数-现有的人数和最大的人数。

信号量的行为有点类似于Mutex或是lock,但是信号量没有拥有者。任意线程都可以调用Release来释放信号量而不像Mutex和lock那样需要线程得到资源才能释放。

?
22
class SemaphoreTest
{
  static Semaphore s = new Semaphore(3, 3); // 当前值=3; 容量=3
  static void Main()
  {
   for ( int i = 0; i < 10; i++)
   new Thread(Go).Start();
  }
  static void Go()
  {
   while ( true )
   {
   s.WaitOne();
   Thread.Sleep(100); // 一次只有个线程能被处理
   s.Release();
   }
  }
}
 
事件(ManualResetEvent/AutoResetEvent)
type= "text/javascript" >

AutoResetEvent

一个AutoResetEvent象是一个"检票轮盘":插入一张通行证然后让一个 人通过。"auto"的意思就是这个"轮盘"自动关闭或者打开让某人通过。线程将在调用WaitOne后进行等待或者是阻塞,并且通过调用Set操作来插 入线程。如果一堆线程调用了WaitOne操作,那么"轮盘"就会建立一个等待队列。一个通行证可以来自任意一个线程,换句话说任意一个线程都可以通过访 问AutoResetEvent对象并调用Set来释放一个阻塞的线程。

如果在Set被调用的时候没有线程等待,那么句柄就会一直处于打开状态直到有线程调 用了WaitOne操作。这种行为避免了竞争条件-当一个线程还没来得急释放而另一个线程就开始进入的情况。因此重复的调用Set操作一个"轮盘"哪怕是 没有等待线程也不会一次性的让所有线程进入。

WaitOne操作接受一个超时参数-当发生等待超时的时候,这个方法会返回一个 false。当已有一个线程在等待的时候,WaitOne操作可以指定等待还是退出当前同步上下文。Reset操作提供了关闭"轮盘"的操作。 AutoResetEvent能够通过两个方法来创建: 1.调用构造函数 EventWaitHandle wh = new AutoResetEvent (false); 如果boolean值为true,那么句柄的Set操作将在创建后自动被调用 ;2. 通过基类EventWaitHandle方式 EventWaitHandle wh = new EventWaitHandle (false, EventResetMode.Auto); EventWaitHandle构造函数允许创建一个ManualResetEvent。人们应该通过调用Close来释放一个Wait Handle在它不再使用的时候。当在应用程序的生存期内Wait handle继续被使用,那么如果遗漏了Close这步,在应用程序关闭的时候也会被自动释放。

?
 
class BasicWaitHandle
{
  static EventWaitHandle wh = new AutoResetEvent( false );
  static void Main()
  {
   new Thread(Waiter).Start();
   Thread.Sleep(1000); // 等待一会儿
   wh.Set(); // 唤醒
  }
  static void Waiter()
  {
   Console.WriteLine( "Waiting..." );
   wh.WaitOne(); // 等待唤醒
   Console.WriteLine( "Notified" );
  }
}

ManualResetEvent

ManualResetEvent是AutoResetEvent的一个特例。它的 不同之处在于在线程调用WaitOne后不会自动的重置状态。它的工作机制有点象是开关:调用Set打开并允许其他线程进行WaitOne;调用 Reset关闭那么排队的线程就要等待,直到下一次打开。可以使用一个带volatile声明的boolean字段来模拟间断休眠 - 通过重复检测标志,然后休眠一小段时间。

ManualResetEvent常常被用于协助完成一个特殊的操作,或者让一个线程在开始工作前完成初始化。

线程池(Thread Pooling)

如果你的应用程序拥有大量的线程并花费大量的时间阻塞在一个Wait Handle上,那么你要考虑使用线程池(Thead pooling)来处理。线程池通过合并多个Wait Handle来节约等待的时间。当Wait Handle被激活时,使用线程池你需要注册一个Wait Handle到一个委托去执行。通过调用ThreadPool.RegisterWaitForSingleObject方法:

?
 
class Test
{
  static ManualResetEvent starter = new ManualResetEvent( false );
  public static void Main()
  {
   ThreadPool.RegisterWaitForSingleObject(starter,Go, "hello" ,-1, true );
   Thread.Sleep(5000);
   Console.WriteLine( "Signaling worker..." );
   starter.Set();
   Console.ReadLine();
  }
  public static void Go( object data, bool timedOut)
  {
   Console.WriteLine( "Started " + data); // Perform task...
  }
}

对于Wait Handle和委托,RegisterWaitForSingleObject接受一个"黑盒"对象并传递给你的委托(就像 ParameterizedThreadStart),超时设置和boolean标志指示了关闭和循环的请求。所有进入池中的线程都被认为是后台线程,这 就意味着它们不再由应用程序控制,而是由系统控制直到应用程序退出。

注意:如果这时候调用Abort操作,可能会发生意想不到的情况。

你也可以通过调用QueueUserWorkItem方法使用线程池,指定委托并立即被执行。这时你不能在多任务情况下保存共享线程,但是可以得到另外的好处:线程池会保持一个线程的总容量,当作业数超出容量时自动插入任务。

?
 
class Test
{
  static object workerLocker = new object ();
  static int runningWorkers = 100;
  public static void Main()
  {
   for ( int i = 0; i < runningWorkers; i++)
   {
   ThreadPool.QueueUserWorkItem(Go, i);
   }
   Console.WriteLine( "Waiting for threads to complete..." );
   lock (workerLocker)
   {
   while (runningWorkers > 0)
    Monitor.Wait(workerLocker);
   }
   Console.WriteLine( "Complete!" );
   Console.ReadLine();
  }
  public static void Go( object instance)
  {
   Console.WriteLine( "Started: " + instance);
   Thread.Sleep(1000);
   Console.WriteLine( "Ended: " + instance);
   lock (workerLocker)
   {
   runningWorkers--;
   Monitor.Pulse(workerLocker);
   }
  }
}

为了传递多个对象到目标方法,你必须定义一个客户对象并包含所有属性或通过调用异步的委托。如Go方法接受两参数:




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值