有一个程序(Ubuntu 12.04 LTS,一个单核处理器):
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
int main(){
mode_t mode = S_IRUSR | S_IWUSR;
int i = 0, fd, pid;
unsigned char pi1 = 0x33, pi2 = 0x34;
if((fd = open("res", O_WRONLY | O_CREAT | O_TRUNC, mode)) < 0){
perror("open error");
exit(1);
}
if((pid = fork()) < 0){
perror("fork error");
exit(1);
}
if(pid == 0) {
if(write(fd, &pi2, 1) != 1){
perror("write error");
exit(1);
}
}else{
if(write(fd, &pi1, 1) != 1){
perror("write error");
exit(1);
}
}
close(fd);
return 0;
}
想法是打开文件写,然后去fork。两个流程的总记录位置。奇怪的是,如果你运行程序,它输出到文件“res”不是恒定的:我已经激怒然后34然后4然后3.问题是为什么这样的结论? (毕竟,如果该职位是共享的,那么结论必须是34或43。)。
我怀疑,当他找到一个写作位置时,该过程在函数写入中被中断。
答案 0 :(得分:3)
当您使用fork()生成多个进程时,无法判断它们将以何种顺序执行。由操作系统调度程序决定。
因此,让多个进程写入同一个文件会导致灾难。
关于为什么有时为什么有两个数字中的一个被省略的问题:写入首先写入数据然后递增文件指针。我认为线程控制可能会在那个时刻发生变化,以便第二个线程在文件位置更新之前写入。所以它会覆盖其他进程刚写的数据。
答案 1 :(得分:2)
我多次运行你的程序,结果是“34”或“43”。 所以我写了一个shell脚本
#!/bin/bash
for i in {1..500}
do
./your_program
for line in $(cat res)
do
echo "$line"
done
done
,并运行您的程序500次。我们可以看到,它有时会变为'3'或'4'(在500中变为20次)。 我们怎么解释这个? 答案是: 当我们fork()子进程时,子进程共享相同的文件描述和文件状态结构(具有当前文件偏移量)。 通常,进程首先获得offset = 0,然后写入第一个字节,offset = 1;另一个进程获得offset = 1,它将写入第二个字节。 但有时,如果父进程从文件状态结构获得offset = 0,并且子进程同时获得offset = 0 ,则进程写入第一个字节,另一个进程覆盖第一个字节。结果将是“3”或“4”(取决于父级是先写还是小写)。因为它们都写了文件的第一个字节。
答案 2 :(得分:1)
标准说
{PIPE_BUF}字节或更少字节的写请求不得与来自在同一管道上写入的其他进程的数据交错。
链接 - http://pubs.opengroup.org/onlinepubs/009696699/functions/write.html
写入的原子性只能在写入管道的情况下保证小于等于PIPE_BUF并且没有为常规文件指定,我们不能假设。
因此,在这种情况下,竞争条件正在发生,并导致某些运行的数据不正确。 (在我的系统中,它也发生在几千次运行之后)。
我认为您应该考虑使用互斥锁/信号量/任何其他锁定原语来解决此问题。
答案 3 :(得分:0)
你确定你需要fork()吗? fork()使用不同的内存空间(文件描述符等)创建不同的进程。也许pthreads适合你?如果使用pthreads,您将为所有进程共享相同的fd。但无论如何,你应该考虑在项目中使用互斥锁。
答案 4 :(得分:0)
这就是我认为正在发生的事情。
open
该文件和fork
3
并退出SIGHUP
的默认操作是终止该过程,以便孩子无声地死亡测试此功能的一种简单方法是添加睡眠:
sleep(5); /* Somewhere in the child, right before you write. */
您会看到子进程瞬间死亡:永远不会执行写入。
测试此方法的另一种方法是在分叉之前忽略SIGHUP
, :
sigignore(SIGHUP); /* Define _XOPEN_SOURCE 500 before including signal.h. */
/* You can also use signal(SIGHUP, SIG_IGN); */
您将看到该过程现在将两个数字写入文件。
覆盖假设不太可能。在fork
之后,两个进程在系统范围的表中共享指向同一文件描述符的链接,该表还包含文件偏移量。