Python 调用系统DLL(1) 信号量Semaphore

理论上来说调用系统DLL不区分语言,所以可以在不同语言中使用信号量进行通信和控制。

信号量简介

信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。如果不太明白信号量的具体效用和机制可自行百度。

测试代码

test1.py

from ctypes import *
# DLL函数所需的全局变量
SEMAPHORE_ALL_ACCESS = 0x001f0003
INFINITE = 0xFFFFFFFF
# 创建信号量
semaphore1 = windll.kernel32.CreateSemaphoreA(0, 0, 1, 'mySemaphore1'.encode())
semaphore2 = windll.kernel32.CreateSemaphoreA(0, 0, 1, 'mySemaphore2'.encode())
print('test1等待一个信号量1')
# 请求一个信号量,如果没有则阻塞自己
windll.kernel32.WaitForSingleObject(semaphore1, INFINITE)
print('test1被唤醒!')
print('test1释放一个信号量2')
# 释放信号量
windll.kernel32.ReleaseSemaphore(semaphore2, 1, 0)

test2.py

from ctypes import *
# DLL函数所需的全局变量
SEMAPHORE_ALL_ACCESS = 0x001f0003
INFINITE = 0xFFFFFFFF
# 打开系统中已有的信号量
semaphore1 = windll.kernel32.OpenSemaphoreA(SEMAPHORE_ALL_ACCESS, 1, 'mySemaphore1'.encode())
semaphore2 = windll.kernel32.OpenSemaphoreA(SEMAPHORE_ALL_ACCESS, 1, 'mySemaphore2'.encode())
print('test2释放一个信号量1')
# 释放信号量
windll.kernel32.ReleaseSemaphore(semaphore1, 1, 0)
print('test2等待一个信号量2')
# 请求一个信号量,如果没有则阻塞自己
windll.kernel32.WaitForSingleObject(semaphore2, INFINITE)
print('test2被唤醒!')

执行结果

test1.py

test2.py

test1等待一个信号量1

test2释放一个信号量1

test1被唤醒!

test2等待一个信号量2

test1释放一个信号量2

test2被唤醒!

所用函数的API手册

win32 api手册链接

CreateSemaphore

CreateSemaphore函数创建一个命名或未命名的信号量对象。

手柄CreateSemaphore(

LPSECURITY_ATTRIBUTES 【lpSemaphoreAttributes】, //指向安全属性的指针
LONG 【lInitialCount】, //初始计数
LONG 【lMaximumCount】, //最大计数
LPCTSTR 【lpName】 //指向信号量对象名称的指针
);

参数

【lpSemaphoreAttributes】

指向SECURITY_ATTRIBUTES结构的指针,用于确定返回的句柄是否可由子进程继承。如果【lpSemaphoreAttributes】为NULL,则无法继承该句柄。

Windows NT:结构的lpSecurityDescriptor成员指定新信号量的安全描述符。如果【lpSemaphoreAttributes】为NULL,则信号量将获取默认安全描述符。

Windows 95:结构的lpSecurityDescriptor成员将被忽略。

【lInitialCount】

指定信号量对象的初始计数。该值必须大于或等于零且小于等于【lMaximumCount】.信号量的状态当其计数大于零时发出信号,而当信号量为零时,信号量的状态为非信号。等待功能释放等待信号量的线程时,计数减少1。通过调用ReleaseSemaphore功能,增加指定的数量。

【lMaximumCount】

指定信号量对象的最大计数。该值必须大于零。

【lpName】

指向一个以null结尾的字符串,指定信号量对象的名称。该名称仅限于MAX_PATH个字符,并且可以包含除反斜杠路径分隔符(\)之外的任何字符。名称比较区分大小写。

如果【lpName】匹配现有的命名信号量对象的名称,则此函数将请求SEMAPHORE_ALL_ACCESS访问现有对象。在这种情况下,【lInitialCount】和【lMaximumCount】参数被忽略,因为它们已经被创建过程设置了。如果【lpSemaphoreAttributes】参数不为NULL,则确定句柄是否可以继承,但其安全描述符成员将被忽略。

如果【lpName】为NULL,则会创建没有名称的信号量对象。

如果【lpName】匹配现有事件,互斥体或文件映射对象的名称,则该函数将失败,并且GetLastError函数返回ERROR_INVALID_HANDLE。这是因为事件,互斥体,信号量和文件映射对象共享相同的名称空间。

返回值

如果函数成功,则返回值是信号量对象的句柄。如果在函数调用之前存在命名的信号量对象,则GetLastError函数返回ERROR_ALREADY_EXISTS。否则,GetLastError返回零。

如果函数失败,返回值为NULL。要获取扩展错误信息,请调用GetLastError.

备注

由CreateSemaphore返回的句柄对新的信号量对象进行了SEMAPHORE_ALL_ACCESS访问,可用于需要对信号量对象处理的任何函数。

调用进程的任何线程都可以在调用其中一个等待功能时指定信号量对象句柄。当指定对象的状态发出信号时,单个对象等待函数返回。可以指示多对象等待功能在任何一个或所有指定对象发出信号时返回。当wait函数返回时,等待的线程被释放以继续执行。

信号量对象的状态在其计数大于零时发出信号,当其计数等于零时,信号量对象的状态为非信号。【lInitialCount】参数指定初始计数。由于信号量的信号状态,每次等待线程被释放时,信号量的计数减少一个。使用ReleaseSemaphore函数将信号量的计数增加一定量。计数永远不能小于零或大于【lMaximumCount】参数中指定的值。

多个进程可以具有相同信号量对象的句柄,从而使对象能够进行进程间同步。以下对象共享机制可用:

*如果CreateSemaphore参数CreateSemaphore启用继承,则CreateProcess函数创建的子进程可以继承信号量对象的句柄。

*进程可以在调用DuplicateHandle函数时指定信号量对象句柄,以创建另一个进程可以使用的重复句柄。

*进程可以在调用OpenSemaphore或CreateSemaphore函数时指定信号量对象的名称。

使用CloseHandle功能关闭句柄。当过程终止时,系统自动关闭句柄。信号量对象在其最后一个句柄被关闭时被销毁。

OpenSemaphore

OpenSemaphore函数返回一个现有的命名信号量对象的句柄。

手柄OpenSemaphore(

DWORD 【dwDesiredAccess】, //访问标志
BOOL 【bInheritHandle】, //继承标志
LPCTSTR 【lpName】 //指向信号量对象名称的指针
);

参数

【dwDesiredAccess】

指定对信号量对象的请求访问。对于支持对象安全性的系统,如果指定对象的安全描述符不允许调用进程的请求访问,则该函数将失败。

此参数可以是以下值的任意组合:

访问 描述
SEMAPHORE_ALL_ACCESS 指定信号量对象的所有可能的访问标志。
SEMAPHORE_MODIFY_STATE 使用ReleaseSemaphore函数中的信号量句柄来修改信号量的计数。
SYNCHRONIZE 仅Windows NT:启用在任何等待功能中使用信号量句柄等待信号量状态的信号。

【bInheritHandle】

指定返回的句柄是否可继承。如果为TRUE,则CreateProcess函数创建的进程可以继承该句柄;否则,句柄不能被继承。

【lpName】

指向以空字符结尾的字符串,命名要打开的信号量。名称比较区分大小写。

返回值

如果函数成功,则返回值是信号量对象的句柄。

如果函数失败,返回值为NULL。要获取扩展错误信息,请调用GetLastError.

备注

OpenSemaphore功能允许多个进程打开相同信号量对象的句柄。只有某些进程已经使用CreateSemaphore函数创建了信号量,该函数才会成功。调用进程可以使用任何需要信号量对象的句柄的函数(如等待功能)的返回句柄,但必须遵守【dwDesiredAccess】参数中指定的访问限制。

可以使用DuplicateHandle功能复制句柄。使用CloseHandle功能关闭句柄。当过程终止时,系统自动关闭句柄。信号量对象在其最后一个句柄被关闭时被销毁。

ReleaseSemaphore

ReleaseSemaphore函数将指定信号量对象的计数增加指定量。

BOOL ReleaseSemaphore(

HANDLE 【// handle of the semaphore object】, //信号量对象的句柄
LONG 【lReleaseCount】, //量加到当前计数
LPLONG 【lpPreviousCount】 //以前的计数地址
);

参数

【// handle of the semaphore object】

标识信号量对象。CreateSemaphore或OpenSemaphore函数返回此句柄。

Windows NT:此句柄必须具有SEMAPHORE_MODIFY_STATE访问权限。有关详细信息,请参阅进程间同步对象.

【lReleaseCount】

指定要增加信号量对象当前计数的量。该值必须大于零。如果指定的数量会导致信号量的计数超过创建信号量时指定的最大计数,则计数不会更改,函数返回FALSE。

【lpPreviousCount】

指向32位变量以接收信号量的先前计数。如果不需要先前的计数,则此参数可以为NULL。

返回值

如果函数成功,返回值不为零。

如果函数失败,返回值为零。要获取扩展错误信息,请调用GetLastError.

备注

信号量对象的状态在其计数大于零时发出信号,并且当其计数等于零时,信号量对象的状态为非信号。调用CreateSemaphore函数的过程指定信号量的初始计数。由于信号量的信号状态,每次等待线程被释放时,信号量的计数减少一个。

通常,应用程序使用信号量来限制使用资源的线程数。在线程使用资源之前,它会在调用等待功能之一时指定信号量句柄。当wait函数返回时,它将信号量的计数减1。线程完成使用资源后,调用ReleaseSemaphore将信号量的计数增加1。

ReleaseSemaphore的另一个用途是在应用程序的初始化过程中。应用程序可以创建初始计数为零的信号量。这将信号量的状态设置为非信号状态,并阻止所有线程访问受保护的资源。当应用程序完成初始化时,它使用ReleaseSemaphore将计数增加到其最大值,以允许正常访问受保护的资源。

WaitForMultipleObjects

当发生以下其中一种情况时,WaitForMultipleObjects函数返回:

*任何一个或所有指定的对象都处于信号状态。

*经过了超时间隔。

DWORD WaitForMultipleObjects(

DWORD 【NCOUNT】, //对象句柄数组中的句柄数
CONST HANDLE *【lpHandles】, //指向object-handle数组的指针
BOOL 【bWaitAll】, // wait flag
DWORD 【dwMilliseconds】 //超时间隔(以毫秒为单位)
);

参数

【NCOUNT】

指定【lpHandles】指向的数组中的对象句柄数。对象句柄的最大数量为MAXIMUM_WAIT_OBJECTS。

【lpHandles】

指向一组对象句柄。有关可以指定句柄的对象类型的列表,请参阅以下备注部分。该数组可以包含不同类型对象的句柄。

Windows NT:手柄必须具有SYNCHRONIZE访问权限。有关详细信息,请参阅访问掩码和访问权限.

【bWaitAll】

指定等待类型。如果为TRUE,则当状态为【lpHandles】数组中的所有对象发出信号时,该函数将返回。如果为FALSE,则当设置为任何一个对象的状态被发出信号时,该函数返回。在后一种情况下,返回值表示状态导致函数返回的对象。

【dwMilliseconds】

指定超时间隔(以毫秒为单位)。即使没有满足【bWaitAll】参数指定的条件,该函数返回间隔。如果【dwMilliseconds】为零,函数将测试指定对象的状态并立即返回。如果【dwMilliseconds】为INFINITE,则该函数的超时间隔从不会过去。

返回值

如果函数成功,则返回值表示导致函数返回的事件。

如果函数失败,返回值为WAIT_FAILED。要获取扩展错误信息,请调用GetLastError.

成功时的返回值是以下值之一:

值 含义
WAIT_OBJECT_0到(WAIT_OBJECT_0 + 【NCOUNT】 - 1) 如果【bWaitAll】为TRUE,则返回值表示指示了所有指定对象的状态。
如果【bWaitAll】为FALSE,返回值减WAIT_OBJECT_0表示满足等待的对象的【lpHandles】数组索引。如果在通话过程中发出了多个对象信号,这是所有信号对象的索引值最小的信号对象的数组索引。

WAIT_ABANDONED_0至(WAIT_ABANDONED_0 + 【NCOUNT】 - 1) 如果【bWaitAll】为TRUE,则返回值表示所有指定对象的状态都发出信号,至少有一个对象是放弃的互斥对象。
如果【bWaitAll】为FALSE,则返回值减WAIT_ABANDONED_0表示满足等待的已放弃互斥体对象的【lpHandles】数组索引。

WAIT_TIMEOUT 超时间隔和【bWaitAll】参数指定的条件不满足。

备注

WaitForMultipleObjects功能确定是否满足等待条件。如果没有满足标准,则调用线程进入有效的等待状态,在等待满足条件的同时消耗很少的处理器时间。

当【bWaitAll】为TRUE时,只有当所有对象的状态都被设置为发信号时,该函数的等待操作才会完成。在所有对象的状态都被设置为发出信号之前,该函数不会修改指定对象的状态。例如,可以用信号通知互斥体,但是直到其他对象的状态也被设置为发信号时线程才会获得所有权。在此期间,其他线程可能会获得互斥体的所有权,从而将其状态设置为非指定状态。

在返回之前,wait函数修改某些类型的同步对象的状态。修改仅针对信号状态导致功能返回的对象或对象。例如,信号量对象的计数减1。

WaitForMultipleObjects函数可以指定【lpHandles】数组中的任何以下对象类型的句柄:

目的 描述
更改通知en FindFirstChangeNotification函数返回句柄。当在指定的目录或目录树中发生指定类型的更改时,发出更改通知对象的状态。
控制台输入 当指定CONIN $值时,该句柄由CreateFile函数返回,或通过GetStdHandle函数返回。当控制台的输入缓冲区中有未读的输入时,对象的状态发出信号,当输入缓冲区为空时,该状态为非信号。
事件 CreateEvent或OpenEvent函数返回句柄。事件对象的状态被明确设置为由SetEvent或PulseEvent功能发出信号。手动复位事件对象的状态必须显式重置为ResetEvent功能的非指定状态。对于自动重置事件对象,等待功能在返回之前将对象的状态重置为非指定状态。事件对象也用于重叠操作,其中状态由系统设置。
互斥 CreateMutex或OpenMutex函数返回句柄。当互斥体对象的状态不被任何线程所拥有时发出。等待函数请求调用线程的互斥体的所有权,在授予所有权时将互斥体的状态更改为非指定状态。
处理 CreateProcess或OpenProcess函数返回句柄。进程对象的状态在进程终止时发出信号。
信号 CreateSemaphore或OpenSemaphore函数返回句柄。信号量对象维持零和某个最大值之间的计数。当它的计数大于零时,它的状态被发出信号,当它的计数为零时,它的信号是非信号的。如果发出当前状态,则等待功能将计数减1。
线 CreateProcess,CreateThread或CreateRemoteThread函数返回句柄。当线程终止时,会发出线程对象的状态。
计时器 CreateWaitableTimer或OpenWaitableTimer函数返回句柄。通过调用SetWaitableTimer函数激活定时器。有效定时器的状态在达到适当的时间时发出信号。您可以通过调用CancelWaitableTimer功能来停用定时器。有效定时器的状态在达到适当的时间时发出信号。您可以通过调用CancelWaitableTimer功能来停用定时器。

在某些情况下,您可以在【lpHandles】中将文件的句柄(称为管道)或通信设备指定为同步对象。但是,不鼓励用于此目的。

使用等待功能和DDE时必须要小心。如果一个线程创建任何窗口,它必须处理消息。DDE向系统中的所有窗口发送消息。如果您的线程使用等待功能,没有超时间隔,系统将会死锁。因此,如果您有一个创建窗口的线程,请使用MsgWaitForMultipleObjects或MsgWaitForMultipleObjectsEx,而不是WaitForMultipleObjects.