文章转自本人公众号:机械猿,本人之前在四川某汽轮机从事结构强度设计,目前在阿里巴巴淘宝事业部担任高级开发工程师,有机械工程同行想转行IT,或者有想入职BAT的可以找我内推~
我们都知道多进程执行printf时因为行缓存的原因会导致输出混乱,那么多进程使用文件I/O操作同时写一个文件是否会出现这种问题?用标准I/O库呢?我们做两个实验,实验一:父进程中使用open打开一个dat文件,然后父子进程各自调用write函数写这个dat文件,查看文件是否出现交叉;实验二:父进程使用标准I/O库函数打开一个dat文件,使用流方式操作,父子进程各自调用fwrite函数进行流式读写,查看结果文件是否出现交叉。
话不多说,在Mac上实地跑一把,先看实验一的结果(源代码附在文章末尾):
实验
父进程写100字节’a’,子进程写100字节’b’,各自十行,可以看到按顺序输出没有交叉。写入500行试试呢?
可以看到也没有交叉现象。
父子进程各自写100字节,来看看实验二的结果:
顺序输出没有交叉现象。那么写入500字节呢:
可以看到父进程写入’a’的过程中被打断,被子进程写入了’b’。
为什么会出现这种不同?下面分析具体一下。
文件I/O
类似read、write这些不带缓冲的标准I/O都是内核的系统调用,当打开一个现有文件或者创建一个新文件时,内核向应用进程返回一个文件描述符fd。UNIX支持在不同进程中共享打开的文件,下图显示了一个进程对应的文件描述符表之间的关系,该进程打开了两个文件。
如果是两个进程各自打开同一文件,那么文件表跟进程表关系如下:
写操作包含两个逻辑操作:先定位到文件尾端,然后写。当我们采用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的可以找我内推~