AutoCAD 3DMAX C语言 Pro/E UG JAVA编程 PHP编程 Maya动画 Matlab应用 Android
Photoshop Word Excel flash VB编程 VC编程 Coreldraw SolidWorks A Designer Unity3D
 首页 > C++

C++ Builder 协调线程

51自学网 http://www.51zixue.net
7.2 协调线程
当编写线程执行时运行的代码时,必须考虑到可能同步执行的其他线程的行为。特别要注意避免两个线程试图同时使用相同的全局对象或变量。另外,一个线程中代码可能会依赖其他线程所执行任务的结果。

7.2.1避免同时访问
为避免在访问全局对象或变量时与其他线程发生冲突,可能需要暂停其他线程的执行,直到该线程代码完成操作。需注意不要暂停其他不需停止的线程的执行。那样做会使效率严重降低,也无法获得使用多个线程的大多数优点。

1.锁定对象
一些对象内置了锁定能力以防止其他线程使用该对象的实例。例如,画布对象(TCanvas及其派生类)有一种Lock方法可防止其他线程访问画布,直到调用Unlock方法。

VCL还包含一种线程安全的列表对象TThreadList。调用TThreadList::LockList返回列表对象,同时阻止其他线程使用列表直到调用UnlockList方法。调用TCanvas::Lock或TThreadList::LockList可以安全地嵌套。锁定直到最后一个锁定调用匹配到同一线程中相应的解锁调用时才会被释放。

2.使用重要区段
若对象没有提供内置的锁定,可使用重要区段。重要区段像门一样,每次只允许一个线程进入。要使用重要区段,需创建TCriticalSection的全局实例。TCriticalSection有两个方法,Acquire(阻止其他线程执行该区段)及Release(取消对其他线程的阻止)。

每个重要区段都与需要保护的全局内存关联。每个要访问这个全局内存的线程首先要调用Acquire方法以确保其他线程不再使用它。当线程结束时,调用Release方法以便其他线程能通过调用Acquire访问全局内存。

警告重要区段只在每个线程都使用它们来访问关联的全局内存时才有用。忽略重要区段并且不调用Acquire而访问全局内存,会引起同时访问的问题。

例如,应用程序有一个全局重要区段变量pLockXY,可阻止访问全局变量X和Y。任何使用X或Y的线程必须调用重要区段,如下所示:


3.使用多重读、独占写的同步器
当使用重要区段来保护全局内存时,每次只有一个线程可使用该内存。这种保护可能超出了需要,特别是有一个经常读但很少写的对象或变量时更是如此。多个线程同时读相同内存但没有线程写内存是没有危险的。当有一些经常被读但很少有线程向其写入的全局内存时,可用TMultiReadExclusiveWriteSynchronizer保护它。这个对象与重要区段一样,但它允许多个线程同时读,只要没有线程写即可。线程必须有独占访问权才能写通过TMultiReadExclusiveWriteSynchronizer保护的内存。

要使用“多重读独占写”的同步器,需创建TMultiReadExclusiveWriteSynchronizer的一个全局实例,它与要保护的全局内存关联。每个需要读内存的线程首先要调用BeginRead方法。BeginRead确保当前无其他线程写内存。线程完成对保护内存读操作时,调用EndRead方法。任何线程要写保护内存必须先调用BeginWrite方法。BeginWrite确保当前无其他线程读或写内存。线程完成对保护内存写操作时,调
用EndWrite方法,以便等待读内存的线程可以开始操作。

警告 与重要区段一样,“多重读独占写”的同步器只在每个线程都使用它们来访问关联的全局内存时才有用。忽略同步器并且不调用BeginRead或BeginWrite而访问内存会引起同时访问的问题。

4.共享内存的其他技术
当使用VCL对象时,使用主VCL线程来执行代码。使用主VCL线程可确保对象不会间接地访问同时被其他线程中的VCL对象使用的内存。参见7.1.2节的“使用主VCL线程”,可获得关于主VCL线程的更多信息。若全局变量不需要被多个线程共享,可使用线程局部变量来代替它。通过使用线程局部变量,线程可以不需要等待或暂停其他线程。参见7.1.2节的“使用线程局部变量”,可获得关于线程局部变量的更多信息。

7.2.2 等待其他线程
若线程必须等待另一线程完成某项任务,可让线程临时中断执行。然后,或者等待另一线程完全执行结束,或者等待另一线程通知完成了该项任务。

1.等待线程执行结束
要等待另一线程执行结束,使用它的WaitFor方法。WaitFor直到那个线程终止才返回,终止的方式要么完成了其Execute方法,要么由于一个异常。例如,下列代码在访问列表中对象前等待,直到另一线程填满该列表:


上例中,列表对象只在WaitFor方法指出该列表被填满时才能被访问。返回值由被等待的线程的Execute方法指定。然而,因为调用WaitFor方法的线程需要知道另一线程的执行结果,无法以代码调用Execute方法,Execute方法也无法返回任何值。所以,Execute方法改为设置ReturnValue属性。ReturnValue通过被其他线程调用的WaitFor方法返回。返回值是一个整数。由应用程序确定其含意。

2.等待任务完成
有时,只需等待线程完成一些操作而不是等待线程执行结束。为此,可使用一个事件对象。事件对象(TEvent)应具有全局范围以便它们能够为所有线程可见。当一个线程完成一个被其他线程依赖的操作时,它调用TEvent::SetEvent。SetEvent发出一个信号,以便任何其他线程可检查并得知操作完成。要关掉信号,使用ResetEvent方法。
例如,当必须等待若干线程完成其执行而不是单个线程时。因为不知道哪个线程最后完成,也就不能对某个线程使用WaitFor方法。这时可通过设置事件以在线程结束时累加计数值并在最后一个线程结束时发出信号以指示所有线程结束。
下列代码显示所有需完成的线程结尾处的OnTerminate事件处理程序。CounterGuard是一个全局的重要区段对象,用以防止多个线程同时使用计数器。Counter是一个统计完成的线程数的全局变量。


主线程初始化Counter变量,启动任务线程,并调用WaitFor方法等待所有线程完成的信号。WaitFor等待一个由信号指定的时间,并返回表7-2中的某个值:


下列代码显示主线程如何启动任务线程,并当所有的线程完成时继续运行:


注意 若你不想在指定时间过后停止等待,可传递给WaitFor方法一个INFINITE参数值。使用INFINITE时必须小心,若预期信号永远不会被收到,则线程将被挂起。

 

 

 
上一篇:C++ Builder 执行线程对象  下一篇:有关遗传算法