WIN32下DELPHI中的多线程【同步2】(五)

本文介绍了在Windows环境下,使用Delphi进行多线程编程时如何利用信号量内核对象进行线程同步。讨论了信号量的原理、创建和使用方法,以及与其他同步机制的区别,强调了避免无谓消耗CPU资源的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程同步2
      上一文中曾经介绍了线程同步的一些方法,其实完成同步还有很多很多的办法,这里最后介绍一种方式--信号量内核对象。并借此来回顾线程同步。
     在谈论信号量之前,我想先谈论另外一种方式,一种你最好不要使用的方式。假设你有一个公共内存区域,你不希望一个线程在完成一个操作之前另外一个线程对他进行另外的操作。抛开前面所有的知识,我们可以使用这样一种办法,一种所有人都会想到的办法。
      程序中设置一个布尔类型的公共变量FLAG,此公共变量唯一的最用是决定线程是否是否可以操作公共内存区域。如果是TRUE则允许操作,如果是FALSE则禁止操作。在线程将要执行对共享内存的操作时,反复判断此变量,类似一个死循环,直到FLAG变为TRUE。思路很简单,实现起来也比前面介绍的那些方法更容易,在某种意义上说,它也是有效的。但文章前面曾经说过,最好不用使用这种方式,为什么?回顾线程的工作状态,我们基本可以这样划分,
1、处于可调度状态(挂起),此状态下的线程正在等待CPU分配时间片给它来执行自己的操作
2、等待状态,此时的线程我们可以称它处在不可调度状态,CPU绝不会在等待事件未发生之前分配时间片给它,例如一个线程正在等待某件事情的发生,就比如前边说的等待事件内核对象的状态变为已通知
3、CPU已分配时间片给线程,它正在执行自己的操作。
      假如我们使用事件内核对象来完成一些线程的同步,那么前面曾经说过,当等待函数检测到事件内核对象的状态为未通知状态时,此线程将处于等待状态,此时线程不会使用CPU,而如果使用前面介绍的那种反复判断变量的方法,那么此线程将占用CPU资源,这很重要,我始终认为,对于一个合格的程序员而言,绝对不要无谓的浪费客户的CPU资源。
      虽然我说上面那种循环判断公共状态位的办法不可取,但它却反映了线程同步的思想,即使我们调用那些用于同步的API函数,事实上,同步的思想也是如此,只是实现的方法不同而已。

信号量
      信号量内核对象用于对资源进行计数。它们与所有内核对象一样,包含一个使用数量,但是它们也包含另外两个带符号的32位值,一个是最大资源数量,一个是当前资源数量。最大资源数量用于标识信标能够控制的资源的最大数量,而当前资源数量则用于标识当前可以使用的资源的数量。
   信号量的使用规则如下:
   • 如果当前资源的数量大于0,则发出信标信号。
   • 如果当前资源数量是0,则不发出信标信号。
   • 系统决不允许当前资源的数量为负值。
   • 当前资源数量决不能大于最大资源数量。
创建一个信号量内核对象
HANDLE CreateSemaphore(
    LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // pointer to security attributes
    LONG lInitialCount, // initial count
    LONG lMaximumCount, // maximum count
    LPCTSTR lpName  // pointer to semaphore-object name 
   );
       和大多数创建内核对象的函数一样,它的第一个参数用来接受安全信息,通常我们用NULL来表示默认,最后一个参数为创建这个信号量的名字,此名字可以使得我们在其他的进程中使用此信号量,lInitialCount参数代表创建信号两时允许资源访问的个数,lMaximumCount用来指定最大资源数,不要让lInitialCount大于lMaximumCount。
      使用Create***创建内核对象时,要注意一个问题,例如,如果已经有一个进程A创建了一个名为'wudi_1982'的信号量内核对象,当另外一个进程B也试图创建名字为'wudi_1982'的内核对象的时候,系统首先要查看是否已经存在一个名字为'wudi_1982'的内核对象。由于确实存在一个带有该名字的对象,因此内核要检查对象的类型。如果类型相同(例如都是信号量内核对象),此时系统会执行一次安全检查,以确定调用者是否拥有对该对象的完整的访问权。如果拥有这种访问权,系统就在进程B的句柄表中找出一个空项目,并对该项目进行初始化,使该项目指向现有的内核对象。如果该对象类型不匹配,或者调用者被拒绝访问,那么Create****将运行失败(返回NULL)。

打开一个现有的信号量
HANDLE OpenSemaphore(
    DWORD dwDesiredAccess, // access flag
    BOOL bInheritHandle, // inherit flag
    LPCTSTR lpName  // pointer to semaphore-object name 
   );
      参数dwDesiredAccess代表了访问权限,bInheritHandle参数表明子进程是否可继承,最后一个参数lpName 用于指明内核对象的名字。不能为该参数传递NULL,必须传递以0结尾的地址。这些函数要搜索内核对象的单个名空间,以便找出匹配的空间。如果不存在带有指定名字的内核对象,该函数返回NULL,GetLastError返回2(ERROR_FILE_NOT_FOUND)。但是,如果存在带有指定名字的内核对象,并且它是相同类型的对象,那么系统就要查看是否允许执行所需的访问(通过dwDesiredAccess参数进行访问)。如果拥有该访问权,调用进程的句柄表就被更新,对象的使用计数被递增。如果为bInheritHandle,参数传递TRUE,那么返回的句柄将是可继承的。调用Create*函数与调用Open*函数之间的主要差别是,如果对象并不存在,那么Create*函数将创建该对象,而Open*函数则运行失败。

通过调用ReleaseSemaphore函数,线程就能够对信标的当前资源数量进行递增
BOOL ReleaseSemaphore(
    HANDLE hSemaphore, // handle of the semaphore object 
    LONG lReleaseCount, // amount to add to current count 
    LPLONG lpPreviousCount  // address of previous count
   );
      参数hSemaphore代表了要操作内核对象的句柄,lReleaseCount表明该函数此值添加给信标的当前资源数量,通常我们用1。lpPreviousCount返回当前资源数量的原始值,大多数的时候我们并不关心这个数值,所以一般赋值为NULL。

一个例子:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值