多线程之共享资源
1.lock
Lock锁定一段代码
lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。常见的结构 lock (this)、lock (typeof (MyType)) 和 lock ("myLock")
违反此准则:
- 如果实例可以被公共访问,将出现 lock (this) 问题。
- 如果 MyType 可以被公共访问,将出现 lock(typeof (MyType)) 问题。
由于进程中使用同一字符串的任何其他代码将共享同一个锁,所以出现 lock(“myLock”) 问题。最佳做法是定义 private 对象来锁定, private static 对象变量来保护所有实例所共有的数据。
2.Monitor
Monitor用来锁定一个对象
Monitor具有以下的功能:
- 与某个对象关联。
- 它是未绑定的,可以直接从上下文中引用。
- 不能创建Monitor实例
将为每个同步对象维护以下信息:
- 对当前持有锁的线程的引用。
- 对就绪队列的引用,它包含准备获取锁的线程。
- 对等待队列的引用,它包换正在等待锁定对象状态变化通知的线程。
Code:
1 class MonitorSample 2 { 3 const int MAX_LOOP_TIME = 1000; 4 Queue mQueue; 5 6 public MonitorSample() 7 { 8 mQueue = new Queue(); 9 } 10 public void FirstThread() 11 { 12 int counter = 0; 13 lock (mQueue) 14 { 15 while (counter < MAX_LOOP_TIME) 16 { 17 Monitor.Wait(mQueue); 18 mQueue.Enqueue(counter); 19 Console.WriteLine("Write:{0}", counter); 20 Monitor.Pulse(mQueue); 21 counter++; 22 } 23 } 24 } 25 public void SecondThread() 26 { 27 lock (mQueue) 28 { 29 Monitor.Pulse(mQueue); 30 while (Monitor.Wait(mQueue, 1000)) 31 { 32 int counter = (int)mQueue.Dequeue(); 33 Console.WriteLine("Read:{0}",counter); 34 Monitor.Pulse(mQueue); 35 } 36 } 37 } 38 public int GetQueueCount() 39 { 40 return mQueue.Count; 41 } 42 43 static void Main(string[] args) 44 { 45 MonitorSample test = new MonitorSample(); 46 Thread tFirst = new Thread(new ThreadStart(test.FirstThread)); 47 Thread tSecond = new Thread(new ThreadStart(test.SecondThread)); 48 tFirst.Start(); 49 tSecond.Start(); 50 tFirst.Join(); 51 tSecond.Join(); 52 Console.WriteLine("Queue Count = " + test.GetQueueCount().ToString()); 53 Console.ReadKey(); 54 } 55 }
上面的例子FirstThread方法首先写入一个值,SecondThead方法处于等待状态,直到收到Monitor.Pulse信号读出值,FirstThread再等待Monitor.Pulse信号写入值。
3.Mutex.
当两个或更多线程需要同时访问一个共享资源时,系统需要使用同步机制来确保一次只有一个线程使用该资源。 Mutex 是同步基元,它只向一个线程授予对共享资源的独占访问权。 如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程释放该互斥体。
可以使用 WaitHandle.WaitOne 方法请求互斥体的所属权。 拥有互斥体的线程可以在对 WaitOne 的重复调用中请求相同的互斥体而不会阻止其执行。 但线程必须调用 ReleaseMutex 方法同样多的次数以释放互斥体的所属权。 Mutex 类强制线程标识,因此互斥体只能由获得它的线程释放。 相反,Semaphore 类不强制线程标识。
Code:
1 class Test 2 { 3 private static Mutex mut = new Mutex(); 4 private const int numIterations = 1; 5 private const int numThreads = 3; 6 7 static void Main() 8 { 9 for (int i = 0; i < numThreads; i++) 10 { 11 Thread myThread = new Thread(new ThreadStart(MyThreadProc)); 12 myThread.Name = String.Format("Thread{0}", i + 1); 13 myThread.Start(); 14 Console.ReadKey(); 15 } 16 } 17 18 private static void MyThreadProc() 19 { 20 for (int i = 0; i < numIterations; i++) 21 { 22 UseResource(); 23 } 24 } 25 private static void UseResource() 26 { 27 mut.WaitOne(); 28 Console.WriteLine("{0} has entered the protected area", 29 Thread.CurrentThread.Name); 30 Thread.Sleep(500); 31 Console.WriteLine("{0} is leaving the protected area\r\n", 32 Thread.CurrentThread.Name); 33 mut.ReleaseMutex(); 34 } 35 }
4.Semaphore
限制可同时访问某一资源或资源池的线程数。
使用 Semaphore 类可控制对资源池的访问。 线程通过调用 WaitOne 方法(从 WaitHandle 类继承)进入信号量,并通过调用 Release 方法释放信号量。
信号量的计数在每次线程进入信号量时减小,在线程释放信号量时增加。 当计数为零时,后面的请求将被阻塞,直到有其他线程释放信号量。 当所有的线程都已释放信号量时,计数达到创建信号量时所指定的最大值。
被阻止的线程并不一定按特定的顺序(如 FIFO 或 LIFO)进入信号量。
信号量分为两种类型:局部信号量和已命名的系统信号量。 如果您使用接受名称的构造函数创建 Semaphore 对象,则该对象与具有该名称的操作系统信号量关联。 已命名的系统信号量在整个操作系统中都可见,可用于同步进程活动。 您可以创建多个 Semaphore 对象来表示同一个已命名的系统信号量,也可以使用 OpenExisting 方法打开现有的已命名系统信号量。
下面的代码示例创建一个最大计数为 3、初始计数为 0 的全局信号量。 此示例启动五个线程,这些线程都将阻止等待该信号量。 主线程使用 Release(Int32) 方法重载,以便将信号量计数增加为其最大值,从而允许三个线程进入该信号量。 每个线程都使用 Thread.Sleep 方法等待一秒钟以便模拟工作,然后调用 Release() 方法重载以释放信号量。 每次释放信号量时,都显示前一个信号量计数。 控制台消息对信号量的使用进行跟踪。 每个线程的模拟工作间隔都稍有增加,以使输出更为易读。
1 public class Example 2 { 3 public static Semaphore _pool; 4 private static int _padding; 5 6 public static void Main() 7 { 8 ExampleTest my = new ExampleTest(); //实例化。声明全局信号量mySemaphore 9 _pool = Semaphore.OpenExisting("mySemaphore"); 10 for (int i = 1; i <= 5; i++) 11 { 12 Thread t = new Thread(new ParameterizedThreadStart(Worker)); 13 t.Start(i); 14 } 15 Thread.Sleep(500); 16 Console.WriteLine("Main thread calls Release(3)."); 17 _pool.Release(3); 18 Console.WriteLine("Main thread exits."); 19 Console.ReadKey(); 20 } 21 22 private static void Worker(object num) 23 { 24 Console.WriteLine("Thread {0} begins " + "and waits for the semaphore.", num); 25 _pool.WaitOne(); 26 int padding = Interlocked.Add(ref _padding, 100); 27 Console.WriteLine("Thread {0} enters the semaphore.", num); 28 Thread.Sleep(1000 + padding); 29 Console.WriteLine("Thread {0} releases the semaphore.", num); 30 Console.WriteLine("Thread {0} previous semaphore count: {1}", num, _pool.Release()); 31 } 32 } 33 34 public class ExampleTest 35 { 36 private Semaphore mySemaphore = new Semaphore(0, 3, "mySemaphore"); 37 }
5.Interlocked
为多个线程共享的变量提供原子操作
1 class MyInterlockedExchangeExampleClass 2 { 3 private static int usingResource = 0; 4 private const int numThreadIterations = 5; 5 private const int numThreads = 10; 6 static void Main() 7 { 8 Thread myThread; 9 Random rnd = new Random(); 10 for (int i = 0; i < numThreads; i++) 11 { 12 myThread = new Thread(new ThreadStart(MyThreadProc)); 13 myThread.Name = String.Format("Thread{0}", i + 1); 14 Thread.Sleep(rnd.Next(0, 1000)); 15 myThread.Start(); 16 } 17 Console.ReadKey(); 18 } 19 private static void MyThreadProc() 20 { 21 for (int i = 0; i < numThreadIterations; i++) 22 { 23 UseResource(); 24 Thread.Sleep(1000); 25 } 26 } 27 static bool UseResource() 28 { 29 if (0 == Interlocked.Exchange(ref usingResource, 1)) 30 { 31 Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name); 32 Thread.Sleep(500); 33 Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name); 34 Interlocked.Exchange(ref usingResource, 0); 35 return true; 36 } 37 else 38 { 39 Console.WriteLine(" {0} was denied the lock", Thread.CurrentThread.Name); 40 return false; 41 } 42 } 43 44 }
5.volatile
volatile关键字指示一个字段可以由多个同时执行的线程修改。
volatile变量可以实现线程安全,但其应用有限,只有其状态完全独立于其他状态时才可使用。