Monitor :提供同步访问对象的机制。
Monitor 类通过向单个线程授予对象锁来控制对对象的访问。对象锁提供限制访问代码块(通常称为临界区)的能力。当一个线程拥有对象的锁时,其他任何线程都不能获取该锁。还可以使用 Monitor 来确保不会允许其他任何线程访问正在由锁的所有者执行的应用程序代码节,除非另一个线程正在使用其他的锁定对象执行该代码。
使用 Monitor 锁定对象(即引用类型)而不是值类型。
Monitor 具有以下功能:
-
它根据需要与某个对象相关联。
-
它是未绑定的,也就是说可以直接从任何上下文调用它。
-
不能创建 Monitor 类的实例。
将为每个同步对象来维护以下信息:
-
对当前持有锁的线程的引用。
-
对就绪队列的引用,它包含准备获取锁的线程。
-
对等待队列的引用,它包含正在等待锁定对象状态变化通知的线程。
下表描述了访问同步对象的线程可以采取的操作:
操作 | 说明 |
---|---|
获取对象锁。此操作同样会标记临界区的开头。其他任何线程都不能进入临界区,除非它使用其他锁定对象执行临界区中的指令。 | |
释放对象上的锁以便允许其他线程锁定和访问该对象。在其他线程访问对象时,调用线程将等待。脉冲信号用于通知等待线程有关对象状态的更改。 | |
向一个或多个等待线程发送信号。该信号通知等待线程锁定对象的状态已更改,并且锁的所有者准备释放该锁。等待线程被放置在对象的就绪队列中以便它可以最后接收对象锁。一旦线程拥有了锁,它就可以检查对象的新状态以查看是否达到所需状态。 | |
释放对象上的锁。此操作还标记受锁定对象保护的临界区的结尾。 |
尽管锁定和释放给定对象的 Enter 和 Exit 语句可以跨越成员或类的边界或同时跨越两者的边界,但并不推荐这样做。
当选择要同步的对象时,应只锁定私有或内部对象。锁定外部对象可能导致死锁,这是因为不相关的代码可能会出于不同的目的而选择锁定相同的对象。
------------------------------------------------------------------------------------------------------------------------------------
Mutex :一个同步基元,也可用于进程间同步。
当两个或更多线程需要同时访问一个共享资源时,系统需要使用同步机制来确保一次只有一个线程使用该资源。Mutex 是同步基元,它只向一个线程授予对共享资源的独占访问权。如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程释放该互斥体。
可以使用 WaitHandle..::.WaitOne 方法请求互斥体的所属权。拥有互斥体的线程可以在对 WaitOne 的重复调用中请求相同的互斥体而不会阻止其执行。但线程必须调用 ReleaseMutex 方法同样多的次数以释放互斥体的所属权。Mutex 类强制线程标识,因此互斥体只能由获得它的线程释放。相反,Semaphore 类不强制线程标识。
如果线程在拥有互斥体时终止,则称此互斥体被放弃。将此 mutex 的状态设置为收到信号,下一个等待线程将获得所有权
// This example shows how a Mutex is used to synchronize access // to a protected resource. Unlike Monitor, Mutex can be used with // WaitHandle.WaitAll and WaitAny, and can be passed across // AppDomain boundaries. using System; using System.Threading; class Test { // Create a new Mutex. The creating thread does not own the // Mutex. private static Mutex mut = new Mutex(); private const int numIterations = 1; private const int numThreads = 3; static void Main() { // Create the threads that will use the protected resource. for(int i = 0; i < numThreads; i++) { Thread myThread = new Thread(new ThreadStart(MyThreadProc)); myThread.Name = String.Format("Thread{0}", i + 1); myThread.Start(); } // The main thread exits, but the application continues to // run until all foreground threads have exited. } private static void MyThreadProc() { for(int i = 0; i < numIterations; i++) { UseResource(); } } // This method represents a resource that must be synchronized // so that only one thread at a time can enter. private static void UseResource() { // Wait until it is safe to enter. mut.WaitOne(); Console.WriteLine("{0} has entered the protected area", Thread.CurrentThread.Name); // Place code to access non-reentrant resources here. // Simulate some work. Thread.Sleep(500); Console.WriteLine("{0} is leaving the protected area/r/n", Thread.CurrentThread.Name); // Release the Mutex. mut.ReleaseMutex(); } }
------------------------------------------------------------------------------------------------------------------------------------
EventWaitHandle、AutoResetEvent 和 ManualResetEvent
事件等待句柄允许线程通过彼此发送信号和等待彼此的信号来同步活动。这些同步事件是基于 Win32 等待句柄的,可分为两种类型:一种收到信号时自动重置;另一种需手动重置。
事件等待句柄在与 Monitor 类相同的许多同步情况下十分有用。事件等待句柄通常比使用 Monitor..::.Wait 和 Monitor..::.Pulse 方法更简单,并且可以对信号发送提供更多控制。命名事件等待句柄也可用于跨应用程序域和进程同步活动,而监视器对于应用程序域是本地的。
本节内容
相关章节
表示一个线程同步事件。
EventWaitHandle 类允许线程通过发信号互相通信。通常,一个或多个线程在 EventWaitHandle 上阻止,直到一个未阻止的线程调用 Set 方法,以释放一个或多个被阻止的线程。线程可以通过调用 static(在 Visual Basic 中为 Shared)WaitHandle..::.SignalAndWait 方法,以原子操作的形式向 EventWaitHandle 发出信号,然后在它上面阻止。
已终止的 EventWaitHandle 的行为取决于它的重置模式。在释放单个等待线程后,用 EventResetMode..::.AutoReset 标志创建的 EventWaitHandle 在终止时会自动重置。用 EventResetMode..::.ManualReset 标志创建的 EventWaitHandle 一直保持终止状态,直到它的 Reset 方法被调用。
自动重置事件提供对资源的独占访问。如果没有线程等待时自动重置事件处于终止状态,则该事件一直保持终止状态,直到某个线程尝试在该事件上等待。该事件释放线程并立即重置,以阻止后面的线程。
手动重置事件类似于入口。当事件不处于终止状态时,在该事件上等待的线程将阻止。当事件处于终止状态时,所有等待的线程都被释放,而事件一直保持终止状态(即后面的等待不阻止),直到它的 Reset 方法被调用。如果一个线程必须完成一项活动后,其他线程才能继续,则手动重置事件很有用。
EventWaitHandle 对象可以与 static(在 Visual Basic 中为 Shared)WaitHandle..::.WaitAll 及 WaitHandle..::.WaitAny 方法一起使用。
有关线程同步机制的更多信息,请参见 EventWaitHandle、AutoResetEvent 和 ManualResetEvent。
--------------------------------------------------------------------------------------------------------------------------
Stopwatch :
Stopwatch 实例可以测量一个时间间隔的运行时间,也可以测量多个时间间隔的总运行时间。在典型的 Stopwatch 方案中,先调用 Start 方法,然后调用 Stop 方法,最后使用 Elapsed 属性检查运行时间。
Stopwatch 实例或者在运行,或者已停止;使用 IsRunning 可以确定 Stopwatch 的当前状态。使用 Start 可以开始测量运行时间;使用 Stop 可以停止测量运行时间。通过属性 Elapsed、ElapsedMilliseconds 或 ElapsedTicks 查询运行时间值。当实例正在运行或已停止时,可以查询运行时间属性。运行时间属性在 Stopwatch 运行期间稳固递增;在该实例停止时保持不变。
默认情况下,Stopwatch 实例的运行时间值相当于所有测量的时间间隔的总和。每次调用 Start 时开始累计运行时间计数;每次调用 Stop 时结束当前时间间隔测量,并冻结累计运行时间值。使用 Reset 方法可以清除现有 Stopwatch 实例中的累计运行时间。
Stopwatch 在基础计时器机制中对计时器的刻度进行计数,从而测量运行时间。如果安装的硬件和操作系统支持高分辨率性能的计数器,则 Stopwatch 类将使用该计数器来测量运行时间;否则,Stopwatch 类将使用系统计数器来测量运行时间。使用 Frequency 和 IsHighResolution 字段可以确定实现 Stopwatch 计时的精度和分辨率。
Stopwatch 类为托管代码内与计时有关的性能计数器的操作提供帮助。具体说来,Frequency 字段和 GetTimestamp 方法可以用于替换非托管 Win32 API QueryPerformanceFrequency 和 QueryPerformanceCounter。
注意:
在多处理器计算机上,线程在哪个处理器上运行无关紧要。但是,由于 BIOS 或硬件抽象层 (HAL) 中的 bug,在不同的处理器上可能会得出不同的计时结果。若要为线程指定处理器关联,请使用 ProcessThread..::.ProcessorAffinity 方法。
Stopwatch sw = Stopwatch.StartNew();
Thread t1 = new Thread(() =>
{
Thread.Sleep(1000);
result = 100;
});
t1.Start();
Thread.Sleep(500);
while (t1.IsAlive) ;
Console.WriteLine(sw.ElapsedMilliseconds);
Console.WriteLine(result);
-------------------------------------------------------------------------------------------------------------------------------
Semaphore 类 :限制可同时访问某一资源或资源池的线程数。
使用 Semaphore 类可控制对资源池的访问。线程通过调用 WaitOne 方法(从 WaitHandle 类继承)进入信号量,并通过调用 Release 方法释放信号量。
信号量的计数在每次线程进入信号量时减小,在线程释放信号量时增加。当计数为零时,后面的请求将被阻塞,直到有其他线程释放信号量。当所有的线程都已释放信号量时,计数达到创建信号量时所指定的最大值。
被阻止的线程并不一定按特定的顺序(如 FIFO 或 LIFO)进入信号量。
线程可通过重复调用 WaitOne 方法多次进入信号量。为释放这些入口中的部分或全部,线程可多次调用无参数的 Release()()() 方法重载,也可以调用 Release(Int32) 方法重载来指定要释放的入口数。
Semaphore 类不对 WaitOne 或 Release 调用强制线程标识。程序员负责确保线程释放信号量的次数不能太多。例如,假定信号量的最大计数为 2,并且线程 A 和线程 B 同时进入信号量。如果线程 B 中的编程错误导致它两次调用 Release,则两次调用都成功。这样,信号量的计数已满,当线程 A 最终调用 Release 时便会引发 SemaphoreFullException。
信号量分为两种类型:局部信号量和已命名的系统信号量。如果您使用接受名称的构造函数创建 Semaphore 对象,则该对象与具有该名称的操作系统信号量关联。已命名的系统信号量在整个操作系统中都可见,可用于同步进程活动。您可以创建多个 Semaphore 对象来表示同一个已命名的系统信号量,也可以使用 OpenExisting 方法打开现有的已命名系统信号量。
局部信号量仅存在于您的进程内。您的进程中任何引用局部 Semaphore 对象的线程都可以使用它。每个 Semaphore 对象都是一个单独的局部信号量。