[apue] 使用文件记录锁无法实现父子进程交互执行同步
父子进程间交互执行是指用一种同步原语,实现父进程和子进程在某一时刻只有一个进程执行,之后由另外一个进程执行,用一段代码举例如下:
- SYNC_INIT();
- int i=, counter=;
- pid_t pid = fork ();
- if (pid < )
- err_sys ("fork error");
- else if (pid > )
- {
- // parent
- for (i=; i<NLOOPS; i+=)
- {
- counter = update ((long *)area);
- if (counter != i)
- err_quit ("parent: expected %d, got %d", i, counter);
- else
- printf ("parent increase to %d based %d\n", i+, counter);
- SYNC_TELL(pid, );
- SYNC_WAIT();
- }
- printf ("parent exit\n");
- }
- else
- {
- for (i=; i<NLOOPS+; i+=)
- {
- SYNC_WAIT();
- counter = update ((long *)area);
- if (counter != i)
- err_quit ("child: expected %d, got %d", i, counter);
- else
- printf ("child increase to %d based %d\n", i+, counter);
- SYNC_TELL(getppid (), );
- }
- printf ("child exit\n");
- }
其中area是指向共享内存的一个地址,update用来增加area指向的内容(为long),在fork之后,父子进程交替更新此值。
它们使用了一些抽象的同步原语,例如SYNC_INIT用于初始化同步设施、SYNC_WAIT等待另外进程的信号、SYNC_TELL向另外进程发送信号。
下面是成功同步后的输出(假设NLOOPS为100):
- create shared-memory 3801126 with size 4 ok
- attach shared-memory at 0xb7733000
- parent increase to 1 based 0
- child increase to 2 based 1
- parent increase to 3 based 2
- child increase to 4 based 3
- parent increase to 5 based 4
- child increase to 6 based 5
- parent increase to 7 based 6
- child increase to 8 based 7
- parent increase to 9 based 8
- child increase to 10 based 9
- parent increase to 11 based 10
- child increase to 12 based 11
- parent increase to 13 based 12
- child increase to 14 based 13
- parent increase to 15 based 14
- child increase to 16 based 15
- parent increase to 17 based 16
- child increase to 18 based 17
- parent increase to 19 based 18
- child increase to 20 based 19
- parent increase to 21 based 20
- child increase to 22 based 21
- parent increase to 23 based 22
- child increase to 24 based 23
- parent increase to 25 based 24
- child increase to 26 based 25
- parent increase to 27 based 26
- child increase to 28 based 27
- parent increase to 29 based 28
- child increase to 30 based 29
- parent increase to 31 based 30
- child increase to 32 based 31
- parent increase to 33 based 32
- child increase to 34 based 33
- parent increase to 35 based 34
- child increase to 36 based 35
- parent increase to 37 based 36
- child increase to 38 based 37
- parent increase to 39 based 38
- child increase to 40 based 39
- parent increase to 41 based 40
- child increase to 42 based 41
- parent increase to 43 based 42
- child increase to 44 based 43
- parent increase to 45 based 44
- child increase to 46 based 45
- parent increase to 47 based 46
- child increase to 48 based 47
- parent increase to 49 based 48
- child increase to 50 based 49
- parent increase to 51 based 50
- child increase to 52 based 51
- parent increase to 53 based 52
- child increase to 54 based 53
- parent increase to 55 based 54
- child increase to 56 based 55
- parent increase to 57 based 56
- child increase to 58 based 57
- parent increase to 59 based 58
- child increase to 60 based 59
- parent increase to 61 based 60
- child increase to 62 based 61
- parent increase to 63 based 62
- child increase to 64 based 63
- parent increase to 65 based 64
- child increase to 66 based 65
- parent increase to 67 based 66
- child increase to 68 based 67
- parent increase to 69 based 68
- child increase to 70 based 69
- parent increase to 71 based 70
- child increase to 72 based 71
- parent increase to 73 based 72
- child increase to 74 based 73
- parent increase to 75 based 74
- child increase to 76 based 75
- parent increase to 77 based 76
- child increase to 78 based 77
- parent increase to 79 based 78
- child increase to 80 based 79
- parent increase to 81 based 80
- child increase to 82 based 81
- parent increase to 83 based 82
- child increase to 84 based 83
- parent increase to 85 based 84
- child increase to 86 based 85
- parent increase to 87 based 86
- child increase to 88 based 87
- parent increase to 89 based 88
- child increase to 90 based 89
- parent increase to 91 based 90
- child increase to 92 based 91
- parent increase to 93 based 92
- child increase to 94 based 93
- parent increase to 95 based 94
- child increase to 96 based 95
- parent increase to 97 based 96
- child increase to 98 based 97
- parent increase to 99 based 98
- child increase to 100 based 99
- child exit
- parent exit
- remove that shared-memory
这套同步原语可以有多种实现方案,简单如管道、xsi信号量,甚至直接使用信号。下面是一些例子:
1. 使用管道
- #ifdef USE_PIPE_SYNC
- // pp is the pipe that parent notify(write) child wait(read)
- // pc is the pipe that child notify(write) parent wait(read)
- static int pp[], pc[];
- void SYNC_INIT (void)
- {
- if (pipe (pp) < || pipe(pc) < )
- err_sys ("pipe error");
- }
- void SYNC_TELL (pid_t pid, int child)
- {
- // close unused read end to avoid poll receive events
- // note, we can NOT do it in SYNC_INIT,
- // as at that moment, we have not fork yet !
- if (child) {
- close (pp[]);
- close (pc[]);
- pp[] = pc[] = -;
- } else {
- close (pc[]);
- close (pp[]);
- pc[] = pp[] = -;
- }
- if (write (child ? pp[] : pc[], child ? "p" : "c", ) != )
- err_sys ("write error");
- }
- void SYNC_WAIT (int child /* unused */)
- {
- int n = , m = ;
- struct pollfd fds[] = {{ }};
- // if fd==-1, just be a place taker !
- //if (pp[0] != -1)
- {
- fds[n].fd = pp[];
- fds[n].events = POLLIN;
- n++;
- }
- //if (pc[0] != -1)
- {
- fds[n].fd = pc[];
- fds[n].events = POLLIN;
- n++;
- }
- int ret = poll (fds, n, -);
- if (ret == -)
- err_sys ("poll error");
- else if (ret > ) {
- char c = ;
- //printf ("poll %d from %d\n", ret, n);
- for (m=; m<n; ++m) {
- //printf ("poll fd %d event 0x%08x\n", fds[m].fd, fds[m].revents);
- if (fds[m].revents & POLLIN) {
- if (fds[m].fd == pp[]) {
- if (read (pp[], &c, ) != )
- err_sys ("read parent pipe error");
- if (c != 'p')
- err_quit ("wait parent pipe but got incorrect data %c", c);
- }
- else {
- if (read (pc[], &c, ) != )
- err_sys ("read child pipe error");
- if (c != 'c')
- err_quit ("wait child pipe but got incorrect data %c", c);
- }
- }
- }
- }
- else
- printf ("poll return 0\n");
- }
- #endif
管道的话,TELL时就是向管道写一个字节,WAIT的时候就是阻塞在对应的端读取一个字节。
注意这里WAIT没有直接使用child参数,而是使用poll同时检测两个读端,看哪个有数据就返回哪个。其实直接读对应的端更直接一些。
2.使用xsi信号量
- #ifdef USE_SEM_SYNC
- union semun
- {
- int val; //<= value for SETVAL
- struct semid_ds *buf; // <= buffer for IPC_STAT & IPC_SET
- unsigned short int *array;// <= array for GETALL & SETALL
- struct seminfo *__buf; // <= buffer for IPC_INFO
- };
- static int semid = -;
- void SYNC_INIT ()
- {
- int mode = ; // 0;
- int flag = IPC_CREAT;
- #ifdef USE_EXCL
- flag |= IPC_EXCL;
- #endif
- semid = semget (IPC_PRIVATE, , flag | mode);
- if (semid < )
- err_sys ("semget for SYNC failed");
- printf ("create semaphore %d for SYNC ok\n", semid);
- union semun sem;
- //sem.val = 1;
- //int ret = semctl (semid, 0, SETVAL, sem);
- //if (ret < 0)
- // err_sys ("semctl to set val failed");
- short arr[] = { };
- sem.array = arr;
- int ret = semctl (semid, , SETALL, sem);
- if (ret < )
- err_sys ("semctl to set all val failed");
- printf ("reset all semaphores ok\n");
- }
- void SYNC_TELL (pid_t pid, int child)
- {
- struct sembuf sb;
- sb.sem_op = ;
- sb.sem_num = child ? : ;
- sb.sem_flg = ; // IPC_NOWAIT, SEM_UNDO
- int ret = semop (semid, &sb, );
- if (ret < )
- printf ("semop to release resource failed, ret %d, errno %d\n", ret, errno);
- else
- printf ("release %d resource %d\n", sb.sem_op, ret);
- }
- void SYNC_WAIT (int child)
- {
- struct sembuf sb;
- sb.sem_op = -;
- sb.sem_num = child ? : ;
- sb.sem_flg = ; // IPC_NOWAIT, SEM_UNDO
- int ret = semop (semid, &sb, );
- if (ret < )
- printf ("semop to require resource failed, ret %d, errno %d\n", ret, errno);
- else
- printf ("require %d resource %d\n", sb.sem_op, ret);
- }
- #endif
xsi信号量的话,在TELL时是向对应的信号量执行V操作,释放一个资源;在WAIT时是向对应的信号量执行P操作,申请一个资源,如果申请不到,就阻塞在那里。
3.使用信号
- #ifdef USE_SIGNAL_SYNC
- static volatile sig_atomic_t sigflag;
- static sigset_t newmask, oldmask, zeromask;
- static void sig_usr (int signo)
- {
- sigflag = ;
- printf ("SIGUSR1/2 called\n");
- }
- void SYNC_INIT ()
- {
- if (apue_signal (SIGUSR1, sig_usr) == SIG_ERR)
- err_sys ("signal (SIGUSR1) error");
- if (apue_signal (SIGUSR2, sig_usr) == SIG_ERR)
- err_sys ("signal (SIGUSR2) error");
- sigemptyset (&zeromask);
- sigemptyset (&newmask);
- sigaddset (&newmask, SIGUSR1);
- sigaddset (&newmask, SIGUSR2);
- if (sigprocmask (SIG_BLOCK, &newmask, &oldmask) < )
- err_sys ("SIG_BLOCK error");
- }
- void SYNC_TELL (pid_t pid, int child)
- {
- kill (pid, child ? SIGUSR1 : SIGUSR2);
- }
- void SYNC_WAIT (int child /* unused */)
- {
- while (sigflag == )
- sigsuspend (&zeromask);
- sigflag = ;
- if (sigprocmask (SIG_SETMASK, &oldmask, NULL) < )
- err_sys ("SIG_SETMASK error");
- }
- #endif
直接使用signal的话,这里分别使用了SIGUSR1和SIGUSR2表示父子进程,TELL操作就是激发一个信号给对方;WAIT操作就是sigsuspend在某个特定信号上,直到有信号发生才返回。
注意TELL时需要指定发送信号的进程号,所以多了一个pid参数,这个参数在之前据说的两种方法中并没有使用。这也是signal不好的一点。
然后,apue 15章最后一道习题中,要求使用文件记录锁来实现上述交互执行时,发现这是不可能完成的任务!
假设我们以加锁文件或文件中一个字节来实现WAIT,使用解锁来实现TELL,那么会发现文件记录锁有以下缺点,导致它不能胜任这个工作:
1. 文件记录锁是基于文件+进程的,当fork后产生子进程时,之前加的锁自动释放;
2. 文件记录锁对于重复施加锁于一个文件或文件中某个特定字节时,它的表现就和之前没有加锁一样,直接成功返回,不会产生阻塞效果;
对于 问题1,直接的影响就是父进程加好锁之后fork,子进程启动后却没有任何初始锁,导致父子进程同步困难。
虽然这个可以通过在子进程中重新初始化来部分的解决,但是这种问题因为有进程竞争存在,问题不严密从而不完美的;
对于 问题2,就直接导致其中一个进程在它的任务循环中,TELL另外一个进程后,再WAIT本进程的同步原语时(内部通过加锁实现),
另一个进程即使没有解锁相应的文件或字节,WAIT也直接成功返回(因为本进程已经持有该锁),从而造成其中一个进程执行多次,另一个进程没有办法插进去执行的情况(虽然两个进程也不能同时执行)。
所以结论是,对于交互执行的同步场景,管道、semaphore、signal都适用,而file lock不适用。
[apue] 使用文件记录锁无法实现父子进程交互执行同步的更多相关文章
- [11]APUE:(文件)记录锁
[a] 概念 建议锁:在遵循相同记录锁规则的进程间生效,通常用于保证某个程序自身多个进程间的数据一致性 强制锁:意在保证所有进程间的数据一致性,但不一定有效:如不能应对先 unlink 后建立同名副本 ...
- linux 文件记录锁详解
一: linux记录锁更恰当的称呼应该是范围锁,它是对文件某个范围的锁定. 关于记录锁的功能就是fcntl提供的第五个功能,具体使用如下: int fcntl(int fd, int cmd, str ...
- Linux进程同步之记录锁(fcntl)
记录锁相当于线程同步中读写锁的一种扩展类型,可以用来对有亲缘或无亲缘关系的进程进行文件读与写的同步,通过fcntl函数来执行上锁操作.尽管读写锁也可以通过在共享内存区来进行进程的同步,但是fcntl记 ...
- inux c编程:记录锁
记录锁的功能:当一个进程正在读或修改文件的某个部分是,它可以阻止其他进程修改同一文件区.对于这个功能阐述我认为有三点要解释的: 记录锁不仅仅可以用来同步不同进程对同一文件的操作,还可以通过对同一文件加 ...
- [apue] linux 文件访问权限那些事儿
前言 说到 linux 上的文件权限,其实我们在说两个实体,一是文件,二是进程.一个进程能不能访问一个文件,其实由三部分内容决定: 文件的所有者.所在的组: 文件对所有者.组用户.其它用户设置的权限访 ...
- LINUX编程学习笔记(十四) 创建进程与 父子进程内存空间
1什么是进程:进程是一个执行中的程序 执行的程序: 代码->资源->CPU 进程有很多数据维护:进程状态/进程属性 所有进程属性采用的一个树形结构体维护 ps -a//所有进程 ps - ...
- ZT 父子进程共享文件描述符
转贴自倒霉熊的博客 [linux学习笔记-2]父子进程共享文件描述符 (2009-03-02 23:03:17) 转载▼ 标签: 学习 linux 子进程 文件描述符 杂谈 分类: 学习 #inclu ...
- Linux--谈父子进程执行过程
fork函数用于从已存在进程中创建一个新进程,新进程成为子进程,原进程成为父进程.这两个进程分别返回他们各自的返回值, 其中父进程的返回值是子进程的进程号,子进程则返回0,因此返回值大于0标识父进程, ...
- shell的父子进程
2017年1月11日, 星期三 shell的父子进程 启动/执行方式: 当前shell: #!/bin/bash 必须行首 ...
随机推荐
- Redis学习总结(五)--Redis集群创建
在之前我们讲到了主从,但是对于大数据量的场景下我们就需要用到集群了,让我们来了解下集群吧. 为什么需要集群 单机内存太小 redis最高可以达到10万/s 请求,如果超过该频率呢? 数据分布方式 数据 ...
- Oracle数据库之五 限定查询和排序显示
五.限定查询和排序显示 5.1.限定查询 5.1.1 认识限定查询 例如:如果一张表中有 100w 条数据,一旦执行了 " SELECT * FROM 表 " 语句之后,则将在屏幕 ...
- 设计模式(C#)——03建造者模式
推荐阅读: 我的CSDN 我的博客园 QQ群:704621321 当一个复杂对象由一些子对象构成,并且子对象的变化会导致复杂对象的修改.这时我们需要提供一种"封装机制&qu ...
- Gym - 101252H
题意略. 思路:二分.注意当利率高且m比较小的时候,每个月的偿还可能会大于本金,所以我们二分的右边界应该要设为2 * 本金. 详见代码: #include<bits/stdc++.h> # ...
- Python基础 2-2 列表的实际应用场景
引言 本章主要介绍列表在实际应用中的使用场景,多维列表(嵌套列表) 如果你需要在列表保存每个人员的一些基本信息,使用列表嵌套来保存这种信息是个不错的主意. 多维列表 列表可以根据实际情况嵌套使用,比如 ...
- 朋友聚会,下馆子要到哪家饭馆?——单样本T检验帮你找到答案
聚会时,五花八门的饭馆让人眼花缭乱,应该到哪家店吃呢?除了美味的食物,良好的服务态度也是好饭馆的必备品质,如何判断一家饭馆的服务态度如何?此时可以用单样本T检验来找答案~ 让顾客对A饭馆的服务态度 ...
- [Error] - Windows卸载程序时,提示错误2503
1. 打开“任务管理器” 2. 切换到“详细信息”标签页,找到explorer.exe文件,并结束它. 3. 点击“任务管理器”上的文件->运行新任务,输入explorer.ext,勾选“以系统 ...
- ACdream1726-A Math game+(DFS+二分)+(DFS+前缀和)
传送门 官方题解:http://acdream.info/topic?tid=4246 参考:https://www.cnblogs.com/nowandforever/p/4492428.html ...
- JDBC编程之预编译SQL与防注入
在JDBC编程中,常用Statement.PreparedStatement 和 CallableStatement三种方式来执行查询语句,其中 Statement 用于通用查询, PreparedS ...
- webstorm的live templates快速编辑功能,让你的css JS代码书写速度飞起来
前言: Emmet的前身是大名鼎鼎的Zen coding,如果你从事Web前端开发的话,对该插件一定不会陌生.它使用仿CSS选择器的语法来生成代码, 大大提高了HTML/CSS代码编写的速度,比如下面 ...