4

有一个程序(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;
}

这个想法是打开文件进行写入,然后再分叉。两个过程的总和记录的位置。奇怪的是,如果你运行这个程序,它输出到一个文件的“res”不是恒定的:我激怒了然后是34 然后是4 然后是3。问题是为什么会有这样的结论?(毕竟,如果位置是共享的,那么结论必须是 34 或 43。)。

在我的怀疑中,当他找到一个可以写的位置时,这个过程在函数 write 中被打断了。

4

5 回答 5

3

当您使用 fork() 生成多个进程时,无法确定它们将按什么顺序执行。由操作系统调度程序决定。

因此,让多个进程写入同一个文件是灾难的根源。

关于为什么有时会省略两个数字之一的问题: write 首先写入数据,然后递增文件指针。我认为线程控制可能恰好在那个时刻发生变化,以便第二个线程在文件位置更新之前写入。所以它会覆盖其他进程刚刚写入的数据。

于 2012-09-06T13:04:49.373 回答
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”(取决于父母先写还是子写)。因为它们都写入文件的第一个字节。

分叉和偏移,看这个

于 2012-09-06T15:34:06.023 回答
1

这就是我认为正在发生的事情。

  • open文件和你fork
  • 父母先跑(如果孩子先跑,可能会发生类似的事情)
    • 父级写入3并退出
    • 父进程是终端的控制进程,因此内核向前台组的所有成员发送 SIGHUP
  • 的默认动作SIGHUP是终止进程,让孩子默默地死去

一个简单的测试方法是添加睡眠:

sleep(5); /* Somewhere in the child, right before you write. */

您会看到子进程立即死亡:写入从未执行

另一种测试方法是在 fork 之前SIGHUP忽略, :

sigignore(SIGHUP);  /* Define _XOPEN_SOURCE 500 before including signal.h. */
/* You can also use signal(SIGHUP, SIG_IGN); */

您将看到该过程现在将两个数字写入文件。


覆盖假设是不可能的。之后fork,两个进程共享一个指向系统范围表中相同文件描述符的链接,该表还包含文件偏移量

于 2012-09-06T13:58:31.330 回答
1

标准说

{PIPE_BUF} 字节或更少的写入请求不应与来自其他进程在同一管道上进行写入的数据交错。

链接 - http://pubs.opengroup.org/onlinepubs/009696699/functions/write.html

写入的原子性仅在写入管道的时间小于等于 PIPE_BUF 的情况下得到保证,并且没有为常规文件指定,我们不能假设。

所以在这种情况下,竞争条件正在发生,这会导致某些运行的数据不正确。(在我的系统中,它也在几千次运行之后发生)。

我认为您应该考虑使用互斥锁/信号量/任何其他锁定原语来解决这个问题。

于 2012-09-06T19:31:28.637 回答
0

你确定你需要 fork() 吗?fork() 创建具有不同内存空间(文件描述符等)的不同进程。也许 pthreads 会适合你?如果使用 pthreads,您将为所有进程共享相同的 fd。但无论如何,你真的应该考虑在你的项目中使用互斥锁。

于 2012-09-06T13:04:55.797 回答