Mutex
Mutex 类似于C# lock, 区别在于一个Mutex可以在多个进程间使用.也就是说Mutex既是computer-wide又是application-wide.
注意: 获取和释放Mutex大概比lock要多五十倍时间.
调用WaitOne()来获得锁, ReleaseMutex()来解除锁.关闭或者杀死Mutex会自动释放掉锁.和lock一样, Mutex只能从拥有它的线程释放掉.
cross-process Mutex的常见用处是用来确保某个程序只有一个实例在运行.
代码如下:
class OneAtATimePlease { static void Main() { // Naming a Mutex makes it available computer-wide. Use a name that's // unique to your company and application (e.g., include your URL). using (var mutex = new Mutex (false, "oreilly.com OneAtATimeDemo")) { // Wait a few seconds if contended, in case another instance // of the program is still in the process of shutting down. if (!mutex.WaitOne (TimeSpan.FromSeconds (3), false)) { Console.WriteLine ("Another instance of the app is running. Bye!"); return; } RunProgram(); } } static void RunProgram() { Console.WriteLine ("Running. Press Enter to exit"); Console.ReadLine(); } }
Semaphore
一个信号量就如同夜总会:它有固定的容量。每次装满的时候,外面的人就不能进去了,需要在门外排队。没离开一个人,队列中第一个人就可以进去。构造器需要至少两个参数:一是剩余的空间,二是总的容量。
容量为1的信号量和Mutex或者lock类似,但是信号量没有“所有者”,即信号量是线程无关的(Thread-agnostic)。 任何线程都可以Release一个信号量。而Mutex和lock则只有拥有它的线程可以Release它。
注意:有两个功能类似的信号量。Semaphore和SemaphoreSlim。后者在.net 4中被引进,为实现并行开发的低延迟要求而优化。在传统多线程环境也可以使用,它提供一个选项来停止等待。然而他不能用来进行跨线程的信号操作。(Mutex和Semaphore都是可以跨线程的。)
Semaphore要求1ms来调用WaitOne或者Release,而SemaphoreSlim只要求1/4ms。
信号量用来降低同步很有效--防止太多线程同步执行同一个代码块。例子如下,限制每次只有三个线程进行同步,其他线程等待。
class TheClub // No door lists! { static SemaphoreSlim _sem = new SemaphoreSlim (3); // Capacity of 3 static void Main() { for (int i = 1; i <= 5; i++) new Thread (Enter).Start (i); } static void Enter (object id) { Console.WriteLine (id + " wants to enter"); _sem.Wait(); Console.WriteLine (id + " is in!"); // Only three threads Thread.Sleep (1000 * (int) id); // can be here at Console.WriteLine (id + " is leaving"); // a time. _sem.Release(); } }
生产中消费者队列:
using System; using System.Threading; using System.Collections.Generic; class ProducerConsumerQueue : IDisposable { EventWaitHandle _wh = new AutoResetEvent (false); Thread _worker; readonly object _locker = new object(); Queue<string> _tasks = new Queue<string>(); public ProducerConsumerQueue() { _worker = new Thread (Work); _worker.Start(); } public void EnqueueTask (string task) { lock (_locker) _tasks.Enqueue (task); _wh.Set(); } public void Dispose() { EnqueueTask (null); // Signal the consumer to exit. _worker.Join(); // Wait for the consumer's thread to finish. _wh.Close(); // Release any OS resources. } void Work() { while (true) { string task = null; lock (_locker) if (_tasks.Count > 0) { task = _tasks.Dequeue(); if (task == null) return; } if (task != null) { Console.WriteLine ("Performing task: " + task); Thread.Sleep (1000); // simulate work... } else _wh.WaitOne(); // No more tasks - wait for a signal } } }
常见的 concurrent collection 通常是用 mutex, reset event 来对 normal collection 进行控制。
如:
public T Dequeue(int timeout) { if(resetEvent.WaitOne(timeout,false)==true) { if(mutex.WaitOne(timeout,false)==true) { return base.Dequeue(); } } }