多进程读写文件操作探讨

文章转自本人公众号:机械猿,本人之前在四川某汽轮机从事结构强度设计,目前在阿里巴巴淘宝事业部担任高级开发工程师,有机械工程同行想转行IT,或者有想入职BAT的可以找我内推~

我们都知道多进程执行printf时因为行缓存的原因会导致输出混乱,那么多进程使用文件I/O操作同时写一个文件是否会出现这种问题?用标准I/O库呢?我们做两个实验,实验一:父进程中使用open打开一个dat文件,然后父子进程各自调用write函数写这个dat文件,查看文件是否出现交叉;实验二:父进程使用标准I/O库函数打开一个dat文件,使用流方式操作,父子进程各自调用fwrite函数进行流式读写,查看结果文件是否出现交叉。

话不多说,在Mac上实地跑一把,先看实验一的结果(源代码附在文章末尾):

实验

多进程读写文件操作探讨_第1张图片

父进程写100字节’a’,子进程写100字节’b’,各自十行,可以看到按顺序输出没有交叉。写入500行试试呢?

可以看到也没有交叉现象。

父子进程各自写100字节,来看看实验二的结果:

多进程读写文件操作探讨_第2张图片

顺序输出没有交叉现象。那么写入500字节呢:

多进程读写文件操作探讨_第3张图片

可以看到父进程写入’a’的过程中被打断,被子进程写入了’b’。

为什么会出现这种不同?下面分析具体一下。

文件I/O

类似read、write这些不带缓冲的标准I/O都是内核的系统调用,当打开一个现有文件或者创建一个新文件时,内核向应用进程返回一个文件描述符fd。UNIX支持在不同进程中共享打开的文件,下图显示了一个进程对应的文件描述符表之间的关系,该进程打开了两个文件。

多进程读写文件操作探讨_第4张图片

如果是两个进程各自打开同一文件,那么文件表跟进程表关系如下:

多进程读写文件操作探讨_第5张图片

写操作包含两个逻辑操作:先定位到文件尾端,然后写。当我们采用O_APPEND选项打开一个文件时,这两个逻辑操作是原子执行的,也就是不能被进程切换中断打断。

标准I/O库

下面看一下标准I/O的操作流程。与read、write不同,标准I/O提供了缓冲,目的就是为了尽可能减少文件I/O的调用次数,问题也就出在这里。

使用fopen可以打开或重新创建一个新流,流的缓冲分为全缓冲、行缓冲和无缓冲三种模式,可以使用setbuf函数进行设置。当执行进程调度时,如果没有调用fflush进行冲洗守护进程没有及时刷新时,缓冲区会被使用流的另一进程执行读写,导致输出错乱。

标准I/O库还有一个问题就是读写效率不高,可以使用映射函数mmap代替功能。

实验一代码:

int main(intargc,char * argv[])
{
        int cnt = argc > 1 ?atoi(argv[1]):1000; 
        int stat;
        int fd;
        int childpid;
 
        if((childpid = fork()) == -1){
                perror("failed tofork\n");
                return 1;
        }
 
        fd =open("test1.dat",O_WRONLY|O_CREAT|O_APPEND,0777);    //这里使用O_APPEND方式
 
        if(fd < 0){
                perror("failed toopen\n");
                return 1;
        }
 
        if(childpid > 0){
                char *buf = (char*)malloc(cnt);
                for(int i = 0;i < cnt;++i) {
                    buf[i] = 'a';
                }
                strcat(buf,"\n");
                for(i=0; i<10; i++){
                        usleep(1000);
                       write(fd,buf,strlen(buf));
                }
                wait(&stat);
        }else{
                char *buf =(char*)malloc(times);
                for(int i = 0;i < times;++i){
                    buf[i] = 'b';
                }
                strcat(buf,"\n");
                for(i=0; i<10; i++){
                        usleep(1000);
                       write(fd,buf,strlen(buf));
                }
        }
        close(fd);
 
        return 0;
}

实验二代码:

int main(intargc,char * argv[])
{
        int cnt = argc > 1 ?atoi(argv[1]):10000;
        int stat;
        int fd;
        int childpid;
        int i;
 
 
        if((childpid = fork()) == -1){
                perror("failed tofork\n");
                return 1;
        }
 
        FILE *fp = NULL;
        fp =fopen("test2.dat","ab");
        if(fp == NULL) {
                     perror("failed tofopen\n");
                     return 1;
 
        }
 
        if(childpid > 0){
                char *buf = (char*)malloc(cnt);
                for(int i = 0;i < cnt;++i) {
                    buf[i] = 'a';
                }
                strcat(buf,"\n");
 
                for(i=0; i<10; i++){
                        usleep(1000);
                       fwrite(buf,strlen(buf),1,fp);
                }
                wait(&stat);
        }else{
                char *buf =(char*)malloc(times);
                for(int i = 0;i < times;++i){
                    buf[i] = 'b';
                }
                strcat(buf,"\n");
                for(i=0; i<10; i++){
                        usleep(1000);
                       fwrite(buf,strlen(buf),1,fp);
                }
        }
        fclose(fp);
 
        return 0;
}

最后厚着脸皮放一下自己的公众号:机械猿,有机械工程同行想转行IT,或者有想入职BAT的可以找我内推~

多进程读写文件操作探讨_第6张图片

你可能感兴趣的:(多进程读写文件操作探讨)