20191126:这篇博客内容部分有误,将会纠正,敬请期待。
一、问题还原
在多进程的环境下,父子进程同时去写一个文件,例如父进程每次写入aaaaa
,子进程每次写入bbbbb
,问题是会不会出现写操作被打断的现象,比如出现aabbbaaabb
这样交替的情况?
二、结论
1:使用
write
系统调用的情况下,不会出现内容交叉的情况。
2:使用fwrite
ANSIC标准C语言函数,会出现内容交叉的情况。
三、实验过程
实验环境:
操作系统: RedHat Linux 7.0
实验过程:
1:打开一个文件,fork
一个子进程,父子进程同时写文件,父进程写入a
,子进程写入b
。
2:分别用write
和fwrite
去观察现象。
实验现象
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
3:结果
可以发现首先没有出现交叉的情况,并且父子进程是交替写入的,即一行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
3:结果
用一个sed
的表达式判断下是否出现了交叉现象,因为我们是以行
写入的,所以每行的开头不是a
就是b
,拿出所有a
开头的行,看里面是否有包含b
的。
$ sed -n '/^a.*$/p' tmpfp.dat | grep b
// '/^a.*$/p' 表示以a开头
// grep b 表示过滤出包含b的行
可以看到,已经出现了一行a
中混入了b
,因此fwrite
在多进程的情况下操作同一个fd是会出现问题的。
四、反思
1:为什么write
不会出现问题但是fwrite
却出现了问题?
答:
write
是Linux
操作系统的系统调用,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