Perl 中的 IPC::Semaphore 信号量的操作
什么是信号量?
信号量(semaphore)的数据结构为一个值和一个指针,指针指向等待该信号量的下一个进程。信号量的值与相应资源的使用情况有关。当它的值大于0时,表示当前可用资源的数量;当它的值小于0时,其绝对值表示等待使用该资源的进程个数。
注意,信号量的值仅能由PV操作来改变。
一般来说,信号量S>=0时,S表示可用资源的数量。执行一次P操作意味着请求分配一个单位资源,因此S的值减1;当S<0时,表示已经没有可用资源,请求者必须等待别的进程释放该类资源,它才能运行下去。而执行一个V操作意味着释放一个单位资源,因此S的值加1;若S<0,表示有某些进程正在等待该资源,因此要唤醒一个等待状态的进程,使之运行下去。
PV操作的含义
PV操作由P操作原语和V操作原语组成(原语是不可中断的过程),这些操作会对对信号量进行相关的操作
具体定义如下:
假设有信号量 S. 然后我们分别来讲 P 和 V 操作.
P(S):
将信号量S的值减1,即S=S-1;
如果S>=0,则该进程继续执行;否则该进程置为等待状态,排入等待队列。
V(S):
将信号量S的值加1,即S=S+1;
如果S>0,则该进程继续执行;否则释放队列中第一个等待信号量的进程。
PV操作的意义:我们用信号量及PV操作来实现进程的同步和互斥。PV操作属于进程的低级通信。 互斥时,进程的用户实现互斥需要成对出现, 默认互斥信号量的初值一般为1。
我们来看看 Perl 模块有关信号量的模块.
创建信号量
这个地方, 我们需要引入二个模块, IPC::SysV 是专门用来引入这种信号量操作的常量, 由 IPC::Semaphore 来操作信号量, 如果我们还要多进程共享,可能还需要引入 IPC::SysV::ftok 这个模块来对本地文件进行操作.
useIPC::SysV qw(IPC_PRIVATE S_IRUSR S_IWUSR IPC_CREAT); useIPC::Semaphore; $sem= IPC::Semaphore->new(IPC_PRIVATE, 10, S_IRUSR | S_IWUSR | IPC_CREAT);
这个地方第一个参数为信号量的名字, 第二个参数为信号量上最多的资源的数量, 所以这个地方这样设置就可以有 10 个信号量.
注意: 多个进程做同步之类时, 我们需要给第一个参数指定为一个文件.通常使用 IPC::SysV::ftok 来替换 IPC_PRIVATE.这样其它进程才能得到这个信号量的编号, 在多个进程中才能相互关联起来.
IPC::Semaphore 模块的基本操作
常用的几个信号量操作的方法
$sem->setall( (0) x 10);
设置上面 10 个信号量都为 0.当然我们也可以单个信号量来设置, 如下
$sem->setval(0, 0);
设置第 0 信号量的值为 0, 因为一共这个信号量才 1 个, 所以从零开始. 这样的话, 如果使用 P/V 操作的话,最开始操作的时候谁也无法取得资源.
@sem= $sem->getall;
取得所有信号量的状态.
进行信号量的 PV 操作主要是周 op 方法
$sem->op(0, -1, SEM_UNDO);
这样会操作这 10 个信号中的第零个信号量减 1.这个就是所谓的 PV 的操作.这个最后一个参数是 semop 中的 SEM_UNDO 操作, 这个会在进程退出时自动还原所有操作.这个地方建议
直接使用 IPC::SysV 来给 SEM_UNDO 和 IPC_NOWAIT 这几个常用的参数都导出来. 在这的就可以实现非阻塞返回值.
我们现在来看个日本人写的一个例子, 来做详细分析:
#!/usr/bin/env perl usestrict; usewarnings; use5.010; useIPC::Semaphore; useIPC::SysV qw/ IPC_PRIVATE IPC_CREAT S_IWUSR SEM_UNDO /; useParallel::ForkManager; useTime::HiRes (); my$process= 10; my$pm = Parallel::ForkManager->new($process); # 创建一个信号量, 最多可以有 1 的资源 # 第一个参数为信号量的名字, 第二个参数为信号量上最多的资源的数量 # 公用信号量: 实现进程间的互斥, 初值=1或资源的数目 # 私用信号量: 实现进程间的同步, 初值=0或某个整数 my$sem= IPC::Semaphore->new(IPC_PRIVATE, 1, IPC_CREAT | S_IWUSR); # 设置第 0 信号量的值为 0, 因为一共这个信号量才 1 个, 所以从零开始. 这样最开始操作的时候谁也无法取得. $sem->setval(0, 0); for(1..$process) { if($pm->start) { Time::HiRes::sleep(0.2); # fork 的延时 next; } # 操作第 0 个信号量, 进行减少 # 这使用了 semop 的 SEM_UNDO 操作, 这个会在进程退出时自动还原所有操作. # 这还可以使用 IPC_NOWAIT 就可以实现非阻塞返回值 # 这个 P/V 操作因为减少时小于等于 0 , 所以这个时候进程并不会工作. $sem->op(0, -1, SEM_UNDO); # 子进程开始处理的时间 say "[$$] ", Time::HiRes::time(); # 子进程退出, 这时会通过 SEM_UNDO 来解锁 $pm->finish; } # 设置第 0 个信号量的默认值为进程数量 $sem->setval(0, $process); $pm->wait_all_children; # 删除信号量 $sem->remove;
所有的程序细节, 我都写了注释, 这个地方, 我们是用来做同步, 让所有的进程都 Fork 完了, 然后都等到父进程 setval 给信号量设置值以后, 这些子进程才开始执行.我可以见到如下的输出, 这个输出可以见到, 从时间上来看, 子进程都是一起执行的.
[30007] 1423541569.65873 [30010] 1423541569.65877 [30017] 1423541569.65879 [30009] 1423541569.65993 [30016] 1423541569.66185 [30011] 1423541569.66185 [30015] 1423541569.66452 [30012] 1423541569.66453 [30014] 1423541569.66596 [30013] 1423541569.66729
这时, 假设我们不使用信号量来控制看看.代码实现如下
useParallel::ForkManager; useTime::HiRes ( ); my$process= 10; my$pm= Parallel::ForkManager->new($process); for(1..$process) { if($pm->start) { Time::HiRes::sleep(0.2); next; } say "[$$] ", Time::HiRes::time( ); $pm->finish; } $pm->wait_all_children;
这时输出会变成, 每过 0.2 秒有一个子进程执行, 并不能同步一起来执行.
[17905] 1423552414.9412 [17906] 1423552415.14185 [17907] 1423552415.34264 [17910] 1423552415.5436 [17911] 1423552415.74442 [17912] 1423552415.94519 [17913] 1423552416.14596 [17914] 1423552416.3467 [17915] 1423552416.54749 [17916] 1423552416.74825
以上的例子可能实际中并不会有大的作用, 但通过这个我们可以很好的了解 IPC::Semaphore 这个模块和操作系统的信号量到底是怎么回事, 怎么样工作的.
建议继续学习:
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:扶 凯 来源: 扶凯
- 标签: 信号量
- 发布时间:2015-02-14 14:13:52
- [44] 界面设计速成
- [42] Oracle MTS模式下 进程地址与会话信
- [41] 如何拿下简短的域名
- [41] 图书馆的世界纪录
- [41] android 开发入门
- [39] IOS安全–浅谈关于IOS加固的几种方法
- [39] 视觉调整-设计师 vs. 逻辑
- [37] 【社会化设计】自我(self)部分――欢迎区
- [37] 程序员技术练级攻略
- [35] 读书笔记-壹百度:百度十年千倍的29条法则