20191126:这篇博客内容部分有误,将会纠正,敬请期待。

一、问题还原

在多进程的环境下,父子进程同时去写一个文件,例如父进程每次写入aaaaa,子进程每次写入bbbbb,问题是会不会出现写操作被打断的现象,比如出现aabbbaaabb这样交替的情况?

二、结论

1:使用write系统调用的情况下,不会出现内容交叉的情况。
2:使用fwriteANSIC标准C语言函数,会出现内容交叉的情况。

三、实验过程

实验环境:

操作系统: RedHat Linux 7.0

实验过程:

1:打开一个文件,fork一个子进程,父子进程同时写文件,父进程写入a,子进程写入b

2:分别用writefwrite去观察现象。

实验现象

write:不会出现数据交叉的情况,而且父子进程交替执行写入。

1:测试代码如下:

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

int main(int argc,char * argv[])
{
    struct timeval start,end;
        int times = argc > 1 ? atoi(argv[1]):10000;  //通过参数传入需要写入的字节数
        int stat;
        int fd;
        int childpid;
        int i;


        for(i=0 ;i<1; i++){
                if(childpid = fork())
                        break;
        }

        if(childpid == -1){
                perror("failed to fork\n");
                return 1;
        }

        fd = open("tmp.dat",O_WRONLY|O_CREAT|O_APPEND,0666);

        if(fd < 0){
                perror("failed to open\n");
                return 1;
        }

        gettimeofday(&start,NULL);          //测试下时间
        if(childpid > 0){
                char *buf = (char*)malloc(times);
                for(int i = 0;i < times;++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);
        gettimeofday(&end,NULL);

        int timeuse = 1000000 * (end.tv_sec - start.tv_sec) + end.tv_usec - start.tv_usec;
        printf("UseTime: MicroSeconds:%d us and  Seconds:%d s\n",timeuse,end.tv_sec-start.tv_sec);
        return 0;
}

2:编译运行

$  gcc file.c -std=c99 -o file
$ ./file 100

java 两个进程同时查询 两个进程写同一个文件_java 两个进程同时查询

3:结果

java 两个进程同时查询 两个进程写同一个文件_java 两个进程同时查询_02

可以发现首先没有出现交叉的情况,并且父子进程是交替写入的,即一行a,一行b

fwrite:在写入的字节数为500的时候就会出现交叉的情况(当然,500并不是最准确的数字,只是我测试500的时候已经出现了)。

1:测试代码如下:

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

int main(int argc,char * argv[])
{
        struct timeval start,end;
        int times = argc > 1 ? atoi(argv[1]):10000;
        int stat;
        int fd;
        int childpid;
        int i;


        for(i=0 ;i<1; i++){
                if(childpid = fork())
                        break;
        }

        if(childpid == -1){
                perror("failed to fork\n");
                return 1;
        }

        FILE *fp = NULL;
        fp = fopen("tmpfp.dat","ab");
        if(fp == NULL) {
            system("touch tmpfp.dat");
        }

        gettimeofday(&start,NULL); 
        if(childpid > 0){
                char *buf = (char*)malloc(times);
                for(int i = 0;i < times;++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);

        gettimeofday(&end,NULL); 
        int timeuse = 1000000 * (end.tv_sec - start.tv_sec) + end.tv_usec - start.tv_usec;
        printf("UseTime: MicroSeconds:%d us and Seconds:%d s\n",timeuse,end.tv_sec-start.tv_sec);
        return 0;
}

2:编译运行

$  gcc fileFP.c -std=c99 -o Filefp
$ ./Filefp 500

java 两个进程同时查询 两个进程写同一个文件_#include_03

3:结果

用一个sed的表达式判断下是否出现了交叉现象,因为我们是以写入的,所以每行的开头不是a就是b,拿出所有a开头的行,看里面是否有包含b的。

$ sed -n '/^a.*$/p' tmpfp.dat | grep b

// '/^a.*$/p'  表示以a开头
// grep b   表示过滤出包含b的行

java 两个进程同时查询 两个进程写同一个文件_i++_04

可以看到,已经出现了一行a中混入了b,因此fwrite在多进程的情况下操作同一个fd是会出现问题的。

四、反思

1:为什么write不会出现问题但是fwrite却出现了问题?

答:writeLinux操作系统的系统调用,fwrite是ANSIC标准的C语言库函数,fwrite在用户态是有缓冲区的。因此需要锁机制来保证并发环境下的安全访问。

2:如果两个进程同时write一个socket会怎样?

答:就像队列一样,一个进程写完另一个进程才能写,数据上不会有问题。
http://stackoverflow.com/questions/1981372/are-parallel-calls-to-send-recv-on-the-same-socket-valid

欢迎评论交流~

参考资料:

http://bbs.chinaunix.net/thread-804742-1-1.html

http://www.chinaunix.net/old_jh/23/829712.html

https://www.nowcoder.com/questionTerminal/869cae279aa84d8b8e9e50cf1084830b