.NET 中的进程/线程同步技术
第1章 线程控制
1.1 线程控制
using System;
using System.Threading;
public class Worker
{
// This method will be called when the thread is started.
public void DoWork()
{
while (!_shouldStop)
{
Console.WriteLine("worker thread: working...");
}
Console.WriteLine("worker thread: terminating gracefully.");
}
public void RequestStop()
{
_shouldStop = true;
}
// Volatile is used as hint to the compiler that this data
// member will be accessed by multiple threads.
private volatile bool _shouldStop;//通过_shouldStop来标识子线程是否退出
}
public class WorkerThreadExample
{
static void Main()
{
// Create the thread object. This does not start the thread.
Worker workerObject = new Worker();
Thread workerThread = new Thread(workerObject.DoWork);
// Start the worker thread.
workerThread.Start();//启动线程
Console.WriteLine("main thread: Starting worker thread...");
// Loop until worker thread activates.
while (!workerThread.IsAlive);//.IsAlive标示某线程对象是否已激活;被句作用为让当前Main线程一直等到workerThread激活后才继续往下执行。
// Put the main thread to sleep for 1 millisecond to
// allow the worker thread to do some work:
Thread.Sleep(1);//当前Main线程sleep 1毫秒
// Request that the worker thread stop itself:
workerObject.RequestStop();//设置_shouldStop标识,让子线程自己结束
// workerThread.Abort();Abort 从另一个线程中终止某个线程。这将强行终止受影响的线程,//即使该线程尚未完成其任务,并且未提供清理资源的机会。
// Use the Join method to block the current thread
// until the object's thread terminates.
workerThread.Join();//阻塞当前Main进程,知道指定的workerThread进程停止为止
Console.WriteLine("main thread: Worker thread has terminated.");
}
}
1.2 线程池
第1章 进程/线程同步技术
1.1 Lock锁
lock 关键字可以用来确保代码块完成运行,而不会被其他线程中断。这是通过在代码块运行期间为给定对象获取互斥锁来实现的。
lock 语句以关键字 lock 开头,它有一个作为参数的对象,在该参数的后面还有一个一次只能由一个线程执行的代码块。例如:
public class TestThreading
{
private System.Object lockThis = new System.Object();
public void Function()
{
lock (lockThis)
{
// Access thread-sensitive resources.
}
}
}
QueueUserWorkItem方法
using System;
using System.Threading;
public class Example {
public static void Main() {
// Queue the task.
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));
Console.WriteLine("Main thread does some work, then sleeps.");
// If you comment out the Sleep, the main thread exits before
// the thread pool task runs. The thread pool uses background
// threads, which do not keep the application running. (This
// is a simple example of a race condition.)
Thread.Sleep(1000);
Console.WriteLine("Main thread exits.");
}
// This thread procedure performs the task.
static void ThreadProc(Object stateInfo) {
// No state object was passed to QueueUserWorkItem, so
// stateInfo is null.
Console.WriteLine("Hello from the thread pool.");
}
}
RegisterWaitForSingleObject 方法
using System;
using System.Threading;
// TaskInfo holds state information for a task that will be
// executed by a ThreadPool thread.
public class TaskInfo {
// State information for the task. These members
// can be implemented as read-only properties, read/write
// properties with validation, and so on, as required.
public string Boilerplate;
public int Value;
// Public constructor provides an easy way to supply all
// the information needed for the task.
public TaskInfo(string text, int number) {
Boilerplate = text;
Value = number;
}
}
public class Example {
public static void Main() {
// Create an object containing the information needed
// for the task.
TaskInfo ti = new TaskInfo("This report displays the number {0}.", 42);
// Queue the task and data.
if (ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc), ti)) {
Console.WriteLine("Main thread does some work, then sleeps.");
// If you comment out the Sleep, the main thread exits before
// the ThreadPool task has a chance to run. ThreadPool uses
// background threads, which do not keep the application
// running. (This is a simple example of a race condition.)
Thread.Sleep(1000);
Console.WriteLine("Main thread exits.");
}
else {
Console.WriteLine("Unable to queue ThreadPool request.");
}
}
// The thread procedure performs the independent task, in this case
// formatting and printing a very simple report.
//
static void ThreadProc(Object stateInfo) {
TaskInfo ti = (TaskInfo) stateInfo;
Console.WriteLine(ti.Boilerplate, ti.Value);
}
}
1.2 Monitor
与 lock 关键字类似,监视器防止多个线程同时执行代码块。Enter 方法允许一个且仅一个线程继续执行后面的语句;其他所有线程都将被阻止,直到执行语句的线程调用 Exit。这与使用 lock 关键字一样。事实上,lock 关键字就是用 Monitor 类来实现的。例如:
lock (x)
{
DoSomething();
}
等效于:
System.Object obj = (System.Object)x;
System.Threading.Monitor.Enter(obj);
try
{
DoSomething();
}
finally
{
System.Threading.Monitor.Exit(obj);
}
多于一个线程Enter相同的对象,那么就在同步对象上形成了“就绪队列”,而当多于一个线程同时Wait相同的对象,就在同步对象上形成了“等待队列”,每个Pluse释放等待队列头上的单个线程,使它们可以进入就绪队列重新得到锁,要注意,Pluse只是使等待队列中的线程进入就绪队列,如果Pluse的调用线程没有退出它的锁语句(Exit或Wait),那么进入就绪队列仍然没有办法拥有锁。
Pluse是一个单向通讯,以异步方式进行,不返回任何值来指示这个脉冲是否被接收到了,如果发出脉冲时没有线程在等待队列中,则脉冲会被忽略
提供了PluseAll方法,用于在一刹那之间释放整个等待队列里的线程。
Wait(Object)—释放对象锁后,当前线程进入等待队列,直到pulse将其唤醒,进入就绪队列;
Wait(Object, Int32)/ Wait(Object, TimeSpan)/ Wait(Object, Int32, Boolean),Boolean指定是否在等待前推出上下文同步域然后重新获取该同步域/ Wait(Object, TimeSpan, Boolean)—释放对象锁,直到重新获得锁;此时线程进入等待队列,直到被PulseAll叫醒或被Pulse叫醒(如果此时线程在等待列队对头);若在指定的等待时间内还未被叫醒,在等待列队中的线程就自动移到就绪队列;如果在指定的时间过期之前重新获取该锁,则为 true;如果在指定的时间过期之后重新获取该锁,则为 false。此方法只有在重新获取该锁后才会返回。
注意:如果为 millisecondsTimeout 参数指定了 Infinite,则除非锁的持有者调用 Pulse 或 PulseAll,否则此方法会无限期阻止。如果 millisecondsTimeout 等于零,则调用 Wait 的线程释放锁,然后立即进入就绪队列以便重新获取锁。
调用方执行一次 Wait,与已为指定对象调用 Enter 的次数无关。从概念上说,Wait 方法存储调用方对对象调用 Enter 的次数,并按完全释放锁定对象所需要的次数调用 Exit。然后调用方在等待重新获取对象期间被阻止。当调用方重新获取锁时,系统按还原调用方的已保存 Enter 计数所需要的次数调用 Enter。调用 Wait 仅释放指定对象的锁;如果调用方是其他对象的锁的所有者,则不释放这些锁。
1.3 同步事件和等待句柄
使用锁或监视器对于防止同时执行区分线程的代码块很有用,但是这些构造不允许一个线程向另一个线程传达事件。
这需要“同步事件”,它是有两个状态(终止和非终止)的对象,可以用来激活和挂起线程。让线程等待非终止的同步事件可以将线程挂起,将事件状态更改为终止可以将线程激活。如果线程试图等待已经终止的事件,则线程将继续执行,而不会延迟。
同步事件有两种:AutoResetEvent 和 ManualResetEvent。它们之间唯一的不同在于,无论何时,
--只要 AutoResetEvent 激活线程,它的状态将自动从终止变为非终止。
--相反,ManualResetEvent 允许它的终止状态激活任意多个线程,只有当它的 Reset 方法被调用时才还原到非终止状态。
可以通过调用一种等待方法,如 WaitOne、WaitAny 或 WaitAll,让线程等待事件。WaitHandle..::.WaitOne()()() 使线程一直等待,直到单个事件变为终止状态;WaitHandle..::.WaitAny()()() 阻止线程,直到一个或多个指示的事件变为终止状态;WaitHandle..::.WaitAll()()() 阻止线程,直到所有指示的事件都变为终止状态。当调用事件的 Set 方法时,事件将变为终止状态。
在下面的示例中,创建了一个线程,并由 Main 函数启动该线程。新线程使用 WaitOne 方法等待一个事件。在该事件被执行 Main 函数的主线程终止之前,该线程一直处于挂起状态。一旦该事件终止,辅助线程将返回。在本示例中,因为事件只用于一个线程的激活,所以使用 AutoResetEvent 或 ManualResetEvent 类都可以。
using System;
using System.Threading;
class ThreadingExample
{
static AutoResetEvent autoEvent;
static void DoWork()
{
Console.WriteLine(" worker thread started, now waiting on event...");
autoEvent.WaitOne();
Console.WriteLine(" worker thread reactivated, now exiting...");
}
static void Main()
{
autoEvent = new AutoResetEvent(false);
Console.WriteLine("main thread starting worker thread...");
Thread t = new Thread(DoWork);
t.Start();
Console.WriteLine("main thread sleeping for 1 second...");
Thread.Sleep(1000);
Console.WriteLine("main thread signaling worker thread...");
autoEvent.Set();
}
}
AutoResetEvent 类
AutoResetEvent 允许线程通过发信号互相通信。通常,此通信涉及线程需要独占访问的资源。
线程通过调用 AutoResetEvent 上的 WaitOne 来等待信号。如果 AutoResetEvent 处于非终止状态,则该线程阻塞,并等待当前控制资源的线程通过调用 Set 发出资源可用的信号。
调用 Set 向 AutoResetEvent 发信号以释放等待线程。AutoResetEvent 将保持终止状态,直到一个正在等待的线程被释放,然后自动返回非终止状态。如果没有任何线程在等待,则状态将无限期地保持为终止状态。
可以通过将一个布尔值传递给构造函数来控制 AutoResetEvent 的初始状态,如果初始状态为终止状态,则为 true;否则为 false。
AutoResetEvent 也可以同 staticWaitAll 和 WaitAny 方法一起使用。
有关线程同步机制的更多信息,请参见概念文档中的 AutoResetEvent。
- set()将Event设为中止状态
--reset() 将Event设为非中止状态
ManualResetEvent 类
ManualResetEvent 允许线程通过发信号互相通信。通常,此通信涉及一个线程在其他线程进行之前必须完成的任务。
当一个线程开始一个活动(此活动必须完成后,其他线程才能开始)时,它调用 Reset 以将 ManualResetEvent 置于非终止状态。此线程可被视为控制 ManualResetEvent。调用 ManualResetEvent 上的 WaitOne 的线程将阻止,并等待信号。当控制线程完成活动时,它调用 Set 以发出等待线程可以继续进行的信号。并释放所有等待线程。
一旦它被终止,ManualResetEvent 将保持终止状态,直到它被手动重置。即对 WaitOne 的调用将立即返回。
可以通过将布尔值传递给构造函数来控制 ManualResetEvent 的初始状态,如果初始状态处于终止状态,为 true;否则为 false。
ManualResetEvent 也可以同 staticWaitAll 和 WaitAny 方法一起使用。
有关线程同步机制的更多信息,请参见概念文档中的 ManualResetEvent。
using System;
using System.Threading;
class CalculateTest
{
static void Main()
{
Calculate calc = new Calculate();
Console.WriteLine("Result = {0}.",
calc.Result(234).ToString());
Console.WriteLine("Result = {0}.",
calc.Result(55).ToString());
}
}
class Calculate
{
double baseNumber, firstTerm, secondTerm, thirdTerm;
AutoResetEvent[] autoEvents;
ManualResetEvent manualEvent;
// Generate random numbers to simulate the actual calculations.
Random randomGenerator;
public Calculate()
{
autoEvents = new AutoResetEvent[]
{
new AutoResetEvent(false),
new AutoResetEvent(false),
new AutoResetEvent(false)
};
manualEvent = new ManualResetEvent(false);
}
void CalculateBase(object stateInfo)
{
baseNumber = randomGenerator.NextDouble();
// Signal that baseNumber is ready.
manualEvent.Set();
}
// The following CalculateX methods all perform the same
// series of steps as commented in CalculateFirstTerm.
void CalculateFirstTerm(object stateInfo)
{
// Perform a precalculation.
double preCalc = randomGenerator.NextDouble();
// Wait for baseNumber to be calculated.
manualEvent.WaitOne();
// Calculate the first term from preCalc and baseNumber.
firstTerm = preCalc * baseNumber *
randomGenerator.NextDouble();
public class TestThreading
{
private System.Object lockThis = new System.Object();
public void Function()
{
lock (lockThis)
{
// Access thread-sensitive resources.
}
}
}
注意:如果为 millisecondsTimeout 参数指定了 Infinite,则除非锁的持有者调用 Pulse 或 PulseAll,否则此方法会无限期阻止。如果 millisecondsTimeout 等于零,则调用 Wait 的线程释放锁,然后立即进入就绪队列以便重新获取锁。