收藏 分享(赏)

delphi中多线程同步的一些方法.doc

上传人:gnk289057 文档编号:6213126 上传时间:2019-04-02 格式:DOC 页数:11 大小:115.50KB
下载 相关 举报
delphi中多线程同步的一些方法.doc_第1页
第1页 / 共11页
delphi中多线程同步的一些方法.doc_第2页
第2页 / 共11页
delphi中多线程同步的一些方法.doc_第3页
第3页 / 共11页
delphi中多线程同步的一些方法.doc_第4页
第4页 / 共11页
delphi中多线程同步的一些方法.doc_第5页
第5页 / 共11页
点击查看更多>>
资源描述

1、delphi 中多线程同步的一些方法 转当有多个线程的时候,经常需要去同步这些线程以访问同一个数据或资源。例如,假设有一个程序,其中一个线程用于把文件读到内存,而另一个线程用于统计文件中的字符数。当然,在把整个文件调入内存之前,统计它的计数是没有意义的。但是,由于每个操作都有自己的线程,操作系统会把两个线程当作是互不相干的任务分别执行,这样就可能在没有把整个文件装入内存时统计字数。为解决此问题,你必须使两个线程同步工作。 存在一些线程同步地址的问题,Win32 提供了许多线程同步的方式。在本节你将看到使用临界区、 互斥、信号量和事件来解决线程同步的问题。 1. 临界区临界区是一种最直接的线程同

2、步方式。所谓临界区,就是一次只能由一个线程来执行的一段代码。如果把初始化数组的代码放在临界区内,另一个线程在第一个线程处理完之前是不会被执行的。 在使用临界区之前,必须使用 InitializeCriticalSection()过程来初始化它。 其声明如下: procedure InitializeCriticalSection(varlpCriticalSection参数是一个 TRTLCriticalSection 类型的记录,并且是变参。至于TRTLCriticalSection 是如何定义的,这并不重要,因为很少需要查看这个记录中的具体内容。只需要在 lpCriticalSection

3、 中传递未初始化的记录,InitializeCriticalSection()过程就会填充这个记录。 注意 Microsoft 故意隐瞒了 TRTLCriticalSection 的细节。因为,其内容在不同的硬件平台上是不同的。在基于 Intel 的平台上, TRTLCriticalSection 包含一个计数器、一个指示当前线程句柄的域和一个系统事件的句柄。在 Alpha 平台上,计数器被替换为一种 Alpha-CPU 数据结构,称为 spinlock。在记录被填充后,我们就可以开始创建临界区了。这时我们需要用EnterCriticalSection()和 LeaveCriticalSect

4、ion()来封装代码块。这两个过程的声明如下:procedure EnterCriticalSection(var lpCriticalSection:TRRLCriticalSection);stdcall; procedure LeaveCriticalSection(var 正如你所想的,参数 lpCriticalSection就是由 InitializeCriticalSection()填充的记录。 当你不需要 TRTLCriticalSection 记录时,应当调用 DeleteCriticalSection()过程,下面是它的声明: procedure DeleteCritical

5、Section(var2. 互斥互斥非常类似于临界区,除了两个关键的区别:首先,互斥可用于跨进程的线程同步。其次,互斥能被赋予一个字符串名字,并且通过引用此名字创建现有互斥对象的附加句柄。 提示临界区与事件对象(比如互斥对象) 的最大的区别是在性能上。临界区在没有线程冲突时,要用 1 0 1 5 个时间片,而事件对象由于涉及到系统内核要用 400600 个时间片。 可以调用函数 CreateMutex ( )来创建一个互斥量。下面是函数的声明: functionlpMutexAttributes 参数为一个指向 TSecurityAttributtes 记录的指针。此参数通常设为 0,表示默认

6、的安全属性。bInitalOwner 参数表示创建互斥对象的线程是否要成为此互斥对象的拥有者。当此参数为 False 时, 表示互斥对象没有拥有者。 lpName 参数指定互斥对象的名称。设为 nil 表示无命名,如果参数不是设为nil,函数会搜索是否有同名的互斥对象存在。如果有,函数就会返回同名互斥对象的句柄。否则,就新创建一个互斥对象并返回其句柄。 当使用完互斥对象时,应当调用 CloseHandle()来关闭它。 在程序中使用 WaitForSingleObject()来防止其他线程进入同步区域的代码。此函数声明如下: function这个函数可以使当前线程在 dwMillisecond

7、s 指定的时间内睡眠,直到hHandle 参数指定的对象进入发信号状态为止。一个互斥对象不再被线程拥有时,它就进入发信号状态。当一个进程要终止时,它就进入发信号状态。dwMilliseconds 参数可以设为 0,这意味着只检查 hHandle 参数指定的对象是否处于发信号状态,而后立即返回。dwMilliseconds 参数设为 INFINITE,表示如果信号不出现将一直等下去。 这个函数的返回值如下 WaitFor SingleObject()函数使用的返回值 返回值 含义 WAIT_ABANDONED 指定的对象是互斥对象,并且拥有这个互斥对象的线程在没有释放此对象之前就已终止。此时就称

8、互斥对象被抛弃。这种情况下,这个互斥对象归当前线程所有,并把它设为非发信号状态 WAIT_OBJECT_0 指定的对象处于发信号状态 WAIT_TIMEOUT 等待的时间已过,对象仍然是非发信号状态再次声明,当一个互斥对象不再被一个线程所拥有,它就处于发信号状态。此时首先调用WaitForSingleObject()函数的线程就成为该互斥对象的拥有者,此互斥对象设为不发信号状态。当线程调用 ReleaseMutex()函数并传递一个互斥对象的句柄作为参数时,这种拥有关系就被解除,互斥对象重新进入发信号状态。 注意除 WaitForSingleObject()函数外,你还可以使用WaitForM

9、ultipleObject()和 MsgWaitForMultipleObject()函数,它们可以等待几个对象变为发信号状态。这两个函数的详细情况请看 Win32 API 联机文档。3. 信号量另一种使线程同步的技术是使用信号量对象。它是在互斥的基础上建立的,但信号量增加了资源计数的功能,预定数目的线程允许同时进入要同步的代码。可以用 CreateSemaphore()来创建一个信号量对象,其声明如下: function和 CreateMutex()函数一样, CreateSemaphore()的第一个参数也是一个指向 TSecurityAttribute s 记录的指针,此参数的缺省值可以

10、设为 nil。 lInitialCount 参数用来指定一个信号量的初始计数值,这个值必须在 0 和lMaximumCount 之间。此参数大于 0,就表示信号量处于发信号状态。当调用 WaitForSingleObject()函数(或其他函数)时,此计数值就减 1。当调用ReleaseSemaphore()时,此计数值加 1。 参数 lMaximumCount 指定计数值的最大值。如果这个信号量代表某种资源,那么这个值代表可用资源总数。 参数 lpName 用于给出信号量对象的名称,它类似于 CreateMutex()函数的lpName 参数。 关于线程同步: Synchronize()是在

11、一个隐蔽的窗口里运行,如果在这里你的任务很繁忙,你的主窗口会阻塞掉;Synchronize() 只是将该线程的代码放到主线程中运行,并非线程同步。 临界区是一个进程里的所有线程同步的最好办法,他不是系统级的,只是进程级的,也就是说他可能利用进程内的一些标志来保证该进程内的线程同步,据 Richter 说是一个记数循环;临界区只能在同一进程内使用;临界区只能无限期等待,不过 2k 增加了 TryEnterCriticalSection 函数实现 0 时间等待。 互斥则是保证多进程间的线程同步,他是利用系统内核对象来保证同步的。由于系统内核对象可以是有名字的,因此多个进程间可以利用这个有名字的内核

12、对象保证系统资源的线程安全性。互斥量是 Win32 内核对象,由操作系统负责管理;互斥量可以使用 WaitForSingleObject 实现无限等待,0 时间等待和任意时间等待。1. 临界区临界区是一种最直接的线程同步方式。所谓临界区,就是一次只能由一个线程来执行的一段代码。如果把初始化数组的代码放在临界区内,另一个线程在第一个线程处理完之前是不会被执行的。在使用临界区之前,必须使用InitializeCriticalSection()过程来初始化它。 在第一个线程调用了 EnterCriticalSection()之后,所有别的线程就不能再进入代码块。下一个线程要等第一个线程调用 Leav

13、eCriticalSection()后才能被唤醒。 2. 互斥互斥非常类似于临界区,除了两个关键的区别:首先,互斥可用于跨进程的线程同步。其次,互斥能被赋予一个字符串名字,并且通过引用此名字创建现有互斥对象的附加句柄。 提示:临界区与事件对象 (比如互斥对象)的最大的区别是在性能上。临界区在没有线程冲突时,要用 10 15 个时间片,而事件对象由于涉及到系统内核要用 400600 个时间片。 当一个互斥对象不再被一个线程所拥有 ,它就处于发信号状态。此时首先调用WaitForSingleObject()函数的线程就成为该互斥对象的拥有者,此互斥对象设为不发信号状态。当线程调用 ReleaseM

14、utex()函数并传递一个互斥对象的句柄作为参数时,这种拥有关系就被解除,互斥对象重新进入发信号状态。 可以调用函数 CreateMutex()来创建一个互斥量。当使用完互斥对象时,应当调用 CloseHandle()来关闭它。 3. 信号量另一种使线程同步的技术是使用信号量对象。它是在互斥的基础上建立的,但信号量增加了资源计数的功能,预定数目的线程允许同时进入要同步的代码。可以用 CreateSemaphore()来创建一个信号量对象, 因为只允许一个线程进入要同步的代码,所以信号量的最大计数值(lMaximumCount)要设为 1。ReleaseSemaphore()函数将使信号量对象的

15、计数加 1; 记住,最后一定要调用 CloseHandle()函数来释放由 CreateSemaphore()创建的信号量对象的句柄。 WaitForSingleObject 函数的返值: WAIT_ABANDONED 指定的对象是互斥对象,并且拥有这个互斥对象的线程在没有释放此对象之前就已终止。此时就称互斥对象被抛弃。这种情况下,这个互斥对象归当前线程所有,并把它设为非发信号状态; WAIT_OBJECT_0 指定的对象处于发信号状态; WAIT_TIMEOUT 等待的时间已过,对象仍然是非发信号状态; VCL 支持三种技术来达到这个目的: (2) 使用 critical 区 如果对象没有提

16、高内置的锁定功能,需要使用 critical 区,Critical 区在同一个时间只也许一个线程进入。为了使用 Critical 区,产生一个 TCriticalSection全局的实例。TcriticalSection 有两个方法,Acquire(阻止其他线程执行该区域)和Release(取消阻止 ) 每个 Critical 区是与你想要保护的全局内存相关联。每个访问全局内存的线程必须首先使用 Acquire 来保证没有其他线程使用它。完成以后,线程调用Release 方法,让其他线程也可以通过调用 Acquire 来使用这块全局内存。 警告:Critical 区只有在所有的线程都使用它来访

17、问全局内存,如果有线程直接调用内存,而不通过 Acquire,会造成同时访问的问题。例如:LockXY 是一个全局的 Critical 区变量。任何一个访问全局 X, Y 的变量的线程,在访问前,都必须使用Acquire LockXY.Acquire; lock out other threads tryY := sin(X); finallyLockXY.Release; end 临界区主要是为实现线程之间同步的,但是使用的时候注意,一定要在用此临界对象同步的线程之外建立该对象(一般在主线程中建立临界对象) 。 线程同步使用临界区,进程同步使用互斥对象。 Delphi 中封装了临界对象。对象

18、名为 TCriticalSection,使用的时候只要在主线程当中建立这个临界对象(注意一定要在需要同步的线程之外建立这个对象) 。具体同步的时候使用 Lock 和 Unlock 即可。 而进程间同步建立互斥对象,则只需要建立一个互斥对象 CreateMutex. 需要同步的时候只需要 WaitForSingleObject(mutexhandle, INFINITE) unlock的时候只需要 ReleaseMutex(mutexhandle);即可。 有很多方法, 信号灯, 临界区, 互斥对象, 此外, windows 下还可以用全局原子,共享内存等等. 在 windows 体系中, 读写

19、一个 8 位整数时原子的, 你可以依靠这一点完成互斥的方法. 对于能够产生全局名称的方法能够可以在进程间同步上(如互斥对象), 也可以用在线程间同步上;不能够产生全局名称的方法(如临界区)只能用在线程间同步上.CreateEvent 的用法HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes, / SD BOOL bManualReset, / reset type BOOL bInitialState, / initial state LPCTSTR lpName / object name ); 该函数创建一个 Event

20、同步对象,并返回该对象的 Handle lpEventAttributes 一般为 NULL bManualReset 创建的 Event 是自动复位还是人工复位 ,如果true,人工复位 , 一旦该 Event 被设置为有信号,则它一直会等到 ResetEvent()API 被调用时才会恢复 为无信号. 如果为 false,Event 被设置为有信号,则当有一个 wait 到它的Thread 时, 该 Event 就会自动复位,变成无信号. bInitialState 初始状态,true,有信号,false 无信号 lpName Event 对象名 一个 Event 被创建以后,可以用 Op

21、enEvent()API 来获得它的 Handle,用CloseHandle() 来关闭它,用 SetEvent()或 PulseEvent()来设置它使其有信号, 用ResetEvent() 来使其无信号,用 WaitForSingleObject()或 WaitForMultipleObjects()来等待 其变为有信号. PulseEvent()是一个比较有意思的使用方法,正如这个 API 的名字,它使一个Event 对象的状态发生一次脉冲变化,从无信号变成有信号再变成无信号,而整个操作是原子的. 对自动复位的 Event 对象,它仅释放第一个等到该事件的 thread(如果有),而对于 人工复位的 Event 对象,它释放所有等待的 thread.

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 网络科技 > Delphi/Perl

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报