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();
}
}
在Terminal Services下运行时, computer-wide Mutex 只有在同一个terminal server session 中的程序可见, 如果要让它在所有 Terminal Serverces sessions 可见, 则需要在它名字前面加上\.
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();
}
}
}