使用AutoResetEvent和ManualResetEvent进行线程同步和通信
@[toc]
介绍
在多线程编程中,AutoResetEvent 和 ManualResetEvent 是两个常用的同步原语。它们用于线程间的通信和协调,以确保线程按照特定的顺序执行。本篇博客将介绍这两种同步原语的概念、用法和区别。
AutoResetEvent
AutoResetEvent (自动重置事件)是一个同步基元,它允许一个线程等待其他线程在信号状态之前进行等待,用于在线程间提供简单的信号通知机制。它的工作方式是,当一个线程通过调用 WaitOne()
方法等待事件信号时,如果事件处于非终止状态,线程将被阻塞。当另一个线程调用 Set()
方法将事件设置为终止状态时,等待的线程将被唤醒,并且事件将自动重置为非终止状态。
ManualResetEvent
ManualResetEvent (手动重置事件)也是一个同步基元,它与AutoResetEvent类似,也用于在线程间提供信号通知机制。与 AutoResetEvent 不同的是,ManualResetEvent 在设置为终止状态后,会一直保持终止状态,直到调用 Reset()
方法将其重置为非终止状态。另外,它允许所有等待的线程在同一个信号状态下被唤醒。当一个线程通过调用 WaitOne()
方法等待事件信号时,如果事件处于非终止状态,线程将被阻塞。只有当事件被设置为终止状态时,线程才会被唤醒。
异同点
虽然 AutoResetEvent 和 ManualResetEvent 都用于线程间的同步和通信,它们之间有以下几个关键的异同点:
- 重置行为:AutoResetEvent 在一个等待线程被唤醒后会自动将事件重置为非终止状态,而 ManualResetEvent 则需要显式地调用
Reset()
方法将事件重置为非终止状态。 - 信号通知:AutoResetEvent 只允许一个等待线程被唤醒,即使有多个线程等待;而 ManualResetEvent 允许多个等待线程被唤醒。
- 等待过程:AutoResetEvent 在一个等待线程被唤醒后,其他等待线程仍然会继续等待;而 ManualResetEvent 在一个等待线程被唤醒后,所有等待线程都会被唤醒。
使用场景和代码示例
根据上述的异同点,我们可以根据不同的需求来选择使用 AutoResetEvent 或 ManualResetEvent。
AutoResetEvent 使用示例
我们创建了两个工作线程,并使用 AutoResetEvent 来同步它们的执行。在主线程中,我们先唤醒第一个等待线程,然后等待一段时间再唤醒第二个等待线程。这样,每个线程只会被唤醒一次,然后自动重置事件,继续等待下一个信号。
using System;
using System.Threading;
class Program
{
static AutoResetEvent autoResetEvent = new AutoResetEvent(false);
static void Main(string[] args)
{
Thread thread1 = new Thread(Worker);
Thread thread2 = new Thread(Worker);
thread1.Start();
thread2.Start();
// 唤醒第一个等待线程
autoResetEvent.Set();
// 唤醒第二个等待线程
Thread.Sleep(1000);
autoResetEvent.Set();
// 等待线程执行完毕
thread1.Join();
thread2.Join();
}
static void Worker()
{
Console.WriteLine("Worker started");
autoResetEvent.WaitOne();
Console.WriteLine("Worker finished");
}
}
ManualResetEvent 使用示例
我们同样创建了两个工作线程,但这次使用ManualResetEvent 来同步它们的执行。在主线程中,我们设置了事件为终止状态,这将唤醒所有等待线程。由于 ManualResetEvent 保持终止状态,每个线程只会被唤醒一次,然后继续执行直到结束。
using System;
using System.Threading;
class Program
{
static ManualResetEvent manualResetEvent = newManualResetEvent(true);
static void Main(string[] args)
{
Thread thread1 = new Thread(Worker);
Thread thread2 = new Thread(Worker);
thread1.Start();
thread2.Start();
// 唤醒所有等待线程
manualResetEvent.Set();
// 等待线程执行完毕
thread1.Join();
thread2.Join();
}
static void Worker()
{
Console.WriteLine("Worker started");
manualResetEvent.WaitOne();
Console.WriteLine("Worker finished");
}
}
阻塞多个线程并同时激活
如果需要阻塞多个线程并同时激活多个线程,建议使用 ManualResetEvent。原因是 ManualResetEvent 允许多个等待线程被唤醒,而 AutoResetEvent 只允许一个等待线程被唤醒。
下面是一个使用 ManualResetEvent 的示例代码:
using System;
using System.Threading;
class Program
{
static ManualResetEvent manualResetEvent = new ManualResetEvent(false);
static void Main(string[] args)
{
Thread[] threads = new Thread[5];
for (int i = 0; i < threads.Length; i++)
{
threads[i] = new Thread(Worker);
threads[i].Start();
}
// 阻塞所有线程
Console.WriteLine("Blocking all threads...");
manualResetEvent.WaitOne();
// 激活所有线程
Console.WriteLine("Activating all threads...");
manualResetEvent.Set();
// 等待线程执行完毕
foreach (Thread thread in threads)
{
thread.Join();
}
}
static void Worker()
{
Console.WriteLine("Worker started");
manualResetEvent.WaitOne();
Console.WriteLine("Worker finished");
}
}
在示例中创建了 5 个工作线程,并使用 ManualResetEvent 来阻塞和激活这些线程。执行的流程为:
- 主线程将 ManualResetEvent 设置为非终止状态,阻塞所有的工作线程;
- 主线程打印消息并将 ManualResetEvent 设置为终止状态,激活所有的工作线程;
- 等待所有线程执行完毕。