以下内容为本人的著作,如需要转载,请声明原文链接 微信公众号「englyf」https://mp.weixin.qq.com/s/lrERW0P7Q6zvklv4JfUO2A
在linux的多进程(或者多线程,这里以进程为例)开发里经常有进程间的通信部分,常见的技术手段有信号量、消息队列、共享内存等,而共享内存和信号量就像衬衫和外套一样搭配才算完整。
信号量的使用可以使得对资源的访问具有排它性,单一时刻只允许同一个进程访问,而其它的进程统统排队等候或者取消行程打道回府。
对资源的访问权既然要有排它性,那么访问权的获得就必然有竞争关系。竞争关系,又会使得结果是有顺序的,包括有序和无序。无序就是,竞争是公平的,对资源的访问权获取是随机的。而有序则是,对竞争的结果有刻意的安排,出现固定的顺序,比如数据生产消费模型里,数据一般是安排先在生产端输出,然后才轮到消费端访问。
好了,扯得太长太阳都快出来了。
信号量的使用库有System V库和POXIS库两种,这里仅简单介绍System V库和相关API,太详细会让人睡着的。
| 函数原型 | 备注 |
|---|---|
| int semget(key_t key, int nsems, int semflg) | 获取或者创建一个信号量集的标识符,一个信号量集可以包含有多个信号量,nsems代表信号量数量,key可以通过ftok获取(也可以直接使用IPC_PRIVATE,但是仅能用于父子进程间通信),semflg代表信号量集的属性 |
| int semctl(int semid, int semnum, int cmd, union semun arg) | 设置或者读取信号量集的某个信号量的信息,semid代表semget返回值,semnum代表信号量的序号,类型union semun在某些系统中不一定存在(如有需要可以自定义) |
| int semop(int semid, struct sembuf *sops, unsigned nsops) | 执行PV操作,P是对资源的占用,V是对资源的释放,类型struct sembuf包含了操作的具体内容,nsops代表操作信号量的个数(一般仅用1) |
struct sembuf {
short sem_num; //指定信号量,信号量在信号量集中的序号,从0开始
short sem_op; //小于0,就是执行P操作,对信号量减去sem_op的绝对值;大于0,就是执行V操作,对信号量加上sem_op的绝对值;等于0,等待信号量值归0
short sem_flg; //0,IPC_NOWAIT,SEM_UNDO(方便于调用进程崩溃时对信号量值的自动恢复,防止对资源的无用挤占)
}
下面介绍一下信号量的两种使用方式。
信号量最简单的使用方式就是无序的竞争方式。比如在获取资源时,只使用一个信号量,各个进程公平竞争上岗。预设其中一个特定进程启动后,初始化信号量的值为1(调用semctl实现)。然后当所有进程其中的一个需要抢占资源时,P操作对信号量值减1,信号量值归0,调用进程抢占资源成功,资源使用完成后,V操作对信号量值加1,信号量值变为1,释放资源。
当信号量值归0后,其它进程如果需要抢占资源,对信号量执行P操作会导致调用进程挂起并等待,这是调用进程堵塞了。如果执行P操作时,semop的sem_flg用了IPC_NOWAIT,则直接返回-1,通过errno可以获取到错误代码EAGAIN。
PV操作就是通过semop函数对信号量的值检查再加减操作。
老是觉得话太多还不如几行代码来得直接明了。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/sem.h>
void P(int sid)
{
struct sembuf sem_p;
sem_p.sem_num = 0;
sem_p.sem_op = -1;
sem_p.sem_flg = 0;
if (semop(sid, &sem_p, 1) == -1) {
perror("p fail");
exit(-1);
}
}
void V(int sid)
{
struct sembuf sem_v;
sem_v.sem_num = 0;
sem_v.sem_op = 1;
sem_v.sem_flg = 0;
if (semop(sid, &sem_v, 1) == -1) {
perror("v fail");
exit(-1);
}
}
int main(int argc, char *argv[])
{
int fd = open("semtest", O_RDWR | O_CREAT, 0666);
if (fd == -1) {
perror("open");
exit(-1);
}
key_t key = ftok("semtest", 'a');
if (key == -1) {
perror("ftok");
exit(-1);
}
int sid = semget(key, 1, IPC_CREAT | 0666);
if (sid == -1) {
perror("semget");
exit(-1);
}
if (semctl(sid, 0, SETVAL, 1) == -1) {
perror("semctl");
exit(-1);
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(-1);
} else if (pid == 0) {
// child process
while (1) {
P(sid);
printf("child get\n");
sleep(1);
printf("child release\n");
V(sid);
}
} else {
// parent process
printf("parent pid %d child pid %d\n", getpid(), pid);
while (1) {
P(sid);
printf("parent get\n");
sleep(1);
printf("parent release\n");
V(sid);
}
}
return 0;
}
然后看看结果输出,第一次可能是这样子的
parent pid 13156 child pid 13157
child get
child release
parent get
parent release
child get
child release
parent get
parent release
child get
child release
...
第二次可能就是这样子了
parent pid 12873 child pid 12874
parent get
parent release
child get
child release
parent get
parent release
child get
child release
parent get
parent release
...
很明显这就是信号量的无序竞争结果,就像永远猜不到下一个出现的会是如花姐姐还是白雪公主。
其实,进程间对资源的使用方式常常是有刻意顺序的,比如数据的生产消费模型使用场景。我们去茶楼喝茶,都是要先下好单等厨房的师傅们弄好端出来,我们才下筷吃起来,这里边就有既定的顺序啦。
那么怎么实现信号量的有序操作呢?如果仅仅使用一个信号量,对于各个进程来说,同一个信号量的值,你知我知大家知,大伙处在同一起跑线上,明显一个信号量是不够了。那么可以尝试使用多个信号量,毕竟人多力量大,大力出奇迹?(玩笑,给个评价____)
假设有两个进程(A和B)竞争使用同一个资源,使用资源的顺序要求先是A,然后B,如此循环。每个进程各分配一个代表的信号量(semA/semB)。由于信号量的值默认是0的,那么可以在最优先的进程(A)中对信号量(semA)的值初始化为1,其它信号量(semB)初始化为0,而在其它进程中不需要再对信号量的值作初始化了。
当进程(A)需要抢占资源时,P操作信号量(semA),信号量(semA)的值归0,抢占资源成功。进程(A)使用完需要释放资源时,V操作信号量(semB),信号量(semB)的值变为1,释放完成。在进程(A)中,资源释放后,这时如果再次尝试抢占资源,则P操作信号量(semA),检查信号量(semA)的值,发现已为0,抢占资源失败,进程(A)挂起等待资源。
在进程(A)释放资源后,如果进程(B)尝试抢占资源,P操作信号量(semB),信号量(semB)的值归0,抢占资源成功。进程(B)使用完需要释放资源时,V操作信号量(semA),信号量(semA)的值变为1,释放完成。如果进程(A)未曾抢占资源并且释放,这时进程(B)尝试抢占资源,P操作信号量(semB),检查信号量(semB)的值,发现已为0,抢占资源失败,进程(B)挂起等待资源。
这样就实现了资源总是先给到进程(A),待进程(A)释放资源后,进程(B)才有资格获取到。
下面是代码,look一look
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/sem.h>
#include <string.h>
#include <errno.h>
void P(int sid, int index)
{
struct sembuf sem_p;
sem_p.sem_num = index;
sem_p.sem_op = -1;
sem_p.sem_flg = 0;
if (semop(sid, &sem_p, 1) == -1) {
printf("%d p fail: %s", index, strerror(errno));
exit(-1);
}
}
void V(int sid, int index)
{
struct sembuf sem_v;
sem_v.sem_num = index;
sem_v.sem_op = 1;
sem_v.sem_flg = 0;
if (semop(sid, &sem_v, 1) == -1) {
printf("%d v fail: %s", index, strerror(errno));
exit(-1);
}
}
int main(int argc, char *argv[])
{
int fd = open("semtest", O_RDWR | O_CREAT, 0666);
if (fd == -1) {
perror("open");
exit(-1);
}
key_t key = ftok("semtest", 'a');
if (key == -1) {
perror("ftok");
exit(-1);
}
int sid = semget(key, 2, IPC_CREAT | 0666);
if (sid == -1) {
perror("semget 2");
exit(-1);
}
if (semctl(sid, 0, SETVAL, 1) == -1) {
perror("semctl 0");
exit(-1);
}
if (semctl(sid, 1, SETVAL, 0) == -1) {
perror("semctl 1");
exit(-1);
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(-1);
} else if (pid == 0) {
// child
while (1) {
P(sid, 1);
printf("child get\n");
sleep(1);
printf("child release\n");
V(sid, 0);
}
} else {
// parent
printf("parent pid %d child pid %d\n", getpid(), pid);
while (1) {
P(sid, 0);
printf("parent get\n");
sleep(1);
printf("parent release\n");
V(sid, 1);
}
}
return 0;
}
编译执行,看看输出。
parent pid 271 child pid 272
parent get
parent release
child get
child release
parent get
parent release
child get
child release
parent get
parent release
...
无论执行多少遍这程序,发现parent永远是最先抢占资源的。不信的话,还可以在parent的while循环之前加个延时,再看看输出结果(治好你的小鸡咕噜。。。)。你会发现parent这只小兔子无论故意睡多久的懒觉,还是会第一个冲出屏幕(不是终点线)。
// parent
printf("parent pid %d child pid %d\n", getpid(), pid);
sleep(10);
while (1) {
P(sid, 0);
printf("parent get\n");
sleep(1);
printf("parent release\n");
V(sid, 1);
}
如果把上面信号量初始化的代码改一改(会不会单车变摩托?想多了。。。)
改成:子进程的代表信号量值初始化为1,父进程的代表信号量初始化为0。
if (semctl(sid, 0, SETVAL, 0) == -1) {
perror("semctl 0");
exit(-1);
}
if (semctl(sid, 1, SETVAL, 1) == -1) {
perror("semctl 1");
exit(-1);
}
编译后再执行程序看看输出,发现最先抢占资源的变成永远是child了
parent pid 298 child pid 299
child get
child release
parent get
parent release
child get
child release
parent get
parent release
child get
child release
...
目录一、inout在设计文件中的使用方法1.1、inout的第一种使用方法1.2、inout实现的第二种使用方法1.3、inout使用总结 二、inout在仿真测试中的使用方法一、inout在设计文件中的使用方法在FPGA的设计过程中,有时候会遇到双向信号(既能作为输出,也能作为输入的信号叫双向信号)。比如,IIC总线中的SDA信号就是一个双向信号,QSPIFlash的四线操作的时候四根信号线均为双向信号。在Verilog中用关键字inout定义双向信号,这里总结一下双向信号的处理方法。1.1、inout的第一种使用方法 实际上,双向信号的本质是由一个三态门组成的,三态门可以输出高电平,低电
我想知道使用MRIruby(2.0.0)和一些全局变量来创建竞争条件是否容易,但事实证明这并不容易。看起来它应该在某个时候失败,但它没有,我已经运行了10分钟。这是我一直在努力实现的代码:definc(*)a=$xa+=1a*=3000a/=3000$x=aendTHREADS=10COUNT=5000loopdo$x=1THREADS.times.mapdoThread.new{COUNT.times(&method(:inc))}end.each(&:join)breakputs"woohoo!"if$x!=THREADS*COUNT+1endputs$x为什么我无法生成(或检
我想从gtk3中的Widget发出自定义信号。在GTK2中,有一个名为signal_new的函数来创建一个新信号。您可以在此处查看示例:https://github.com/ruby-gnome2/ruby-gnome2/blob/ec373f87e672dbeeaa157f9148d18b34713bb90e/glib2/sample/type-register.rb在GTK3中,这个功能似乎不再可用。那么在ruby的GTK3中创建自定义信号的新方法是什么? 最佳答案 GTK3更改为使用define_signal方法而不是si
我想知道如何连接到带参数的信号(使用Rubyblock)。我知道如何连接到一个不带参数的:myCheckbox.connect(SIGNAL:clicked){doStuff}但是,这不起作用:myCheckbox.connect(SIGNAL:toggle){doStuff}它不起作用,因为切换槽采用参数voidQAbstractButton::toggled(boolchecked)。我怎样才能让它与参数一起工作?谢谢。 最佳答案 对您的问题的简短回答是,您必须使用slots方法声明要连接的插槽的方法签名:classMainGU
我有在服务器上运行的代码,在服务器硬关闭之前,发送了一个信号SIGTERM让我的代码知道它需要清理。我想在发生这种情况时运行代码并将信号发送回同一个程序,以便任何其他需要清理的代码都可以这样做。我不想捕获信号或改变信号行为,我只需要在我的程序的其余部分解释SIGTERM之前运行一些东西。目前我可以做类似的事情Signal.trap('TERM')doputs"Gracefulshutdown"exitend但如果同一个应用中的多段代码试图做同样的事情,它就不起作用了。例如:Signal.trap('TERM')doputs"Gracefulshutdown"exitendSignal.
Heroku可能会出于各种原因向您的应用程序发送SIGTERM,因此我创建了一个处理程序来处理一些清理工作,以防发生这种情况。一些谷歌搜索没有给出任何关于如何在RSpec中测试它的答案或示例。这是基本代码:Signal.trap('TERM')docleanupenddefcleanupputs"doingsomecleanupstuff"...exitend当程序收到SIGTERM时,测试调用此清理方法的最佳方法是什么? 最佳答案 使用Process.kill'TERM',0将信号发送到RSpec并测试调用处理程序。确实,如果信号
也许你已经看到了这个......2012-03-07T15:36:25+00:00heroku[web.1]:StoppingprocesswithSIGTERM2012-03-07T15:36:36+00:00heroku[web.1]:StoppingprocesswithSIGKILL2012-03-07T15:36:36+00:00heroku[web.1]:ErrorR12(Exittimeout)->Processfailedtoexitwithin10secondsofSIGTERM2012-03-07T15:36:38+00:00heroku[web.1]:Proces
来自Process.kill的文档:Sendsthegivensignaltothespecifiedprocessid(s)ifpidispositive.IfpidiszerosignalissenttoallprocesseswhosegroupIDisequaltothegroupIDoftheprocess.signalmaybeanintegersignalnumberoraPOSIXsignalname(eitherwithorwithoutaSIGprefix).Ifsignalisnegative(orstartswithaminussign),killsproces
我有一组对象@users,每个对象都有其id属性。@users=[#,#]我还有一个有序的ids数组。ids=[2,1]¿是否有一种神奇的方法可以使用该ID列表对集合进行排序?如果可能,不再次调用数据库。谢谢!!! 最佳答案 其实你不需要排序,建立一个中间的索引散列,它是O(n):users_by_id=Hash[@users.map{|u|[u.id,u]}]users_by_id.values_at(*ids)如果您仍想尝试排序方法,Schwartziantransform就足够了:@users.sort_by{|u|ids.i
互联网这头“猪”真的掉下来了流量红利已经一去不复返了!3年前业界其实已经发出各种密集信号,在当时无论是BAT还是一些经济学家在3年前都已经预测过,互联网的流量模式已经衰竭,并且它将一去不复返。曾经处于互联网大潮的我们这一代人有喜有有悲也有感慨。还在4-5年前不少程序员会发觉在一个地方工作一年再跳一家公司,工资翻倍是至少的。其实这不是能力的表现这只不过是因此我们赶上了互联网流利红利、风投资本红利的“风口”而己。“赶上风口就连老母猪都能上树"用于形容当时的情形一点不为过。可是这个“风”这次是真的过去了,因此这头“猪”掉了下来,而且这次摔了还挺狠,直接给摔成了肉饼。业务模式、生态、环境的变革是时代的