在写apue第10章的习题时遇到一道习题,编写一段程序测试10-24中父进程和子进程的同步函数,要求大概是让父子进程轮流向文件写计数器值,并打印是谁写的,简单描述下就是程序先向文件写0,然后父子进程轮流写1,2,3,…,同时在后面打印是谁写的,也就是确定确实是父子进程轮流写入文件的.
第一个是思路的问题,我们需要注意的是子进程将会copy父进程的几乎所有东西,所以想要用static global 变量的可以歇歇了,但是在我们这道题中有个讨巧的方式,我就是这样做的(笑),因为是轮流写,所以我每次调用函数向文件里写的时候将counter的值加二,再修改下子进程中初始的counter值就OK了.实际上靠谱的解决办法是每次调用写文件的函数时,先读文件中最后一次写入的计数器值,然后根据该计数器的值决定要写的计数器值.
第二个小地方是子进程拷贝了父进程的文件描述符,但是他们俩共享文件表项,这也就意味着他们共享相同的文件偏移量,所以我们在父进程中写入计数值及身份信息时,子进程的文件偏移量同时也发生了变化,所以我们不用考虑父子进程写入文件位置的问题.
我们在写入文件的时候需要注意,我们如果使用标准I/O库的函数fprintf,则必须每次写完就要调用fflush(fp),因为标准I/O库的函数是带缓冲的,同时我们打开的是文件,所以是全缓冲的,如果不写完就冲洗文件流的话,我们的结果可能就不是我们想要的了(经过测试,确实会发生问题)
第四个就是潜在的一个死锁问题,我在编写代码之前设置了计数器counter的上限是100,但是在运行时却发现程序始终无法退出,但是打开写入文件却一切正常(发现最后一次是parent write),于是我修改了counter的上限为101,问题居然就消失了,打开写入文件发现最后一次是child write,所以很显然我们可以察觉到问题肯定是出在父子进程的同步之上了,然后我看了下代码果然是有问题的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29if((pid = fork()) < 0)
err_sys("fork error");
else if(pid == 0)
{
++counter;
while(counter < NUM)
{
TELL_WAIT();
WAIT_PARENT();
writeFile("child");
TELL_PARENT(getppid());
}
/* avoid problem parent waits for child indefinitely */
/*TELL_WAIT();
WAIT_PARENT();
TELL_PARENT(getppid());*/
exit(0);
}
else
{
while(counter < NUM)
{
TELL_WAIT();
writeFile("parent");
TELL_CHILD(pid);
WAIT_CHILD();
}
exit(0);
}
贴上局部的代码,我们可以看到每次父进程写完了之后都会调用
WAIT_CHILD()等待子进程结束,这就是问题出现的地方,如果子进程已经退出,而父进程却在一直等待子进程的信号,父进程就永远无法停止.要解决这个办法,肯定不能用修改counter上限的方式(笑),我们看到每次父进程写完都会等待子进程,也就是子进程必须在父进程之后结束,所以我们可以在子进程退出循环之后再调用WAIT_PARENT函数,等待父进程结束(只要等待一次就OK了,大家可以想想问什么),解决这个问题的代码我放在上面贴的代码的注释部分了,去掉注释就OK了.
结语:完整的代码我放在我的github上了:codeofapue,想说的就是信号这一块确实相当的复杂,涉及到很多的问题,需要仔细周全的考虑,另外在1000次测试中没问题的代码,可能第1001次就会出现问题,所以我们就需要更加的小心,避免潜在的问题.