多进程下的文件描述符

风流意气都作罢 提交于 2020-01-07 07:52:30

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

多进程下的文件描述符

我们都知道fork之后, 子进程继承了父进程的文件描述符. 但是这是什么意思? 如果父子进程或多个子进程同时操作这个文件描述符会如何?

奇怪的表现

脚本的基本逻辑是父进程读取要处理的文件是计算行数, 然后根据配置的子进程个数fork子进程,子进程处理某一范围的数据,比如子进程1处理1到10行, 子进程2处理11到20. 因为对这个文件的处理是只读操作, 所以没有进行文件拆分,而是直接使用继承的文件描述符, 各子进程移动到自己处理的范围进行处理.

但是在测试的时候, 发现有些子进程总是无法读到数据, strace 调试发现read(2)返回0. 但是增加fseek之后,又正常了, 见当时代码注释:

    //开发时发现如果没有这个fseek, 会导致$proc_idx=0的那个子进程fgets读不到数据,strace显示read返回0
    //原因不明
    fseek($order_file, ftell($order_file));
    while(($line_no <= $stop) && $line = fgets($order_file, 4096))
    {
        ....
    }

可以看到这个fseek到ftell完全是没有用的,但加了这句这后就就正常了. 真是匪夷所思.

开发时, 一开始是正常的, 但是加上了通过socket调用后端服务之后, 就异常了, 去掉此socket则不需要fseek也可以正常读到数据.

我一度还以为是PHP的BUG,于是更新了PHP的版本, 问题还是如此.

某一时刻忽然灵光一闪, read不到数据, 可以是文件偏移量不对.... 或者当时灵光是另一种闪法也未可知, 总是我于是去查看fork, 得到以下信息:

man 2 fork
...
       *  The child inherits copies of the parent's set of open file descriptors.  Each file descriptor in  the  child
          refers  to  the same open file description (see open(2)) as the corresponding file descriptor in the parent.
          This means that the two descriptors share open file status flags, current file offset, and signal-driven I/O
          attributes (see the description of F_SETOWN and F_SETSIG in fcntl(2)).
...

于是再查open

man 2 open
       A call to open() creates a new open file description, an entry in the system-wide table of  open  files.   This
       entry  records  the  file  offset and the file status flags (modifiable via the fcntl(2) F_SETFL operation).  A
       file descriptor is a reference to one of these entries; 

请注意这里有两个词file descriptorsfile description. 回到一开始我们所说的"我们都知道...文件描述符...", file descriptorsfile description都可翻译为"文件描述符", 但实际上两个是不同的. file descriptors 可以认为是数组的下标 file descriptors才是正常的数据, 这个数据包括了file offset, fork之后子进程继承的是file descriptors(下标), 但下标指向的是 同一个file description, 所以大家都在read这个file descriptors, 大家都修改了同一个fiel offset, 会相互影响.

test code

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

void child(int idx, int fd);

int main(int argc, char const* argv[])
{
    int i;
    int pid;
    int fd = open(argv[1], O_RDONLY);

    for (i = 0; i < 3; i++)
    {
        pid = fork();
        if(0 == pid)
        {
            //child 
            child(i, fd);
            exit(0);
        }
    }
    return 0;
}

void child(int idx, int fd)
{
    FILE* fp            = 0;
    char filename[1024] = {0};
    char s[1024]        = {0};
    snprintf(filename, sizeof(filename), "test_%d.txt", idx);

    fp = fopen(filename, "wb");
    while(read(fd, s, idx * 10 + 1))
    {
        fwrite(s, sizeof(char), idx*10 + 1, fp);
    }
}

按我原来的理解, 如果继承的"文件描述符"是互不影响的, 那么三个子进程都能复制到同一份完整的文件,但事实上几乎每个子进程都读到错乱的文件.

解决方法

各子进程自行打开同一文件, 各自处理自己的范围

        elseif($pid == 0)
        {
            //child
            process_file_range($task_id, $result_files['order_input_file'], $i, $child_proc_range[$i]["start"], $child_proc_range[$i]["stop"]);
            exit(0);
        }
=>
        elseif($pid == 0)
        {
            //child
            $result_files['order_input_file'] = fopen($order_file, 'r');
            process_file_range($task_id, $result_files['order_input_file'], $i, $child_proc_range[$i]["start"], $child_proc_range[$i]["stop"]);
            exit(0);
        }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!