最近碰到关于多进程写文件的问题,问题的描述如下:有一个MPI程序,会以n个进程运行程序,然后在每个进程结束的时候向timer.txt文件写入数据。
开始的时候,用的是普通的写文件操作,显然不行,若是各个进程结束的时间相差较大,不会有错误,若是执行时间相差很小,大约是几是个毫秒,就会导致出错,其实这种做法本身就存在问题。
后来,参考unix环境高级编程提到的方法,又试用了其中三种方法,最后还是出错。由于之前未接触多进程编程,所以对错误的原因不是很确定,不知道是否为自己用法有问题,特写出来大家讨论下。
方法一:用open(time.txt, O_APPEND | O_WRONLY ),具体代码如下:
int w_timer_handle;
char temp[1000];
int len;
int w_len;
int offset;
sprintf(temp, "process %d time_total %lf comm_time %lf/n",llrank, total_time_last[llrank], comm_time[llrank] );
len = strlen(temp);
w_timer_handle = open( ftimer, O_WRONLY | O_APPEND );
w_len = write( w_timer_handle, temp, len );
fsync( w_timer_handle );
if( w_len != len )
printf("write to timer file error. /n");
printf("The length is: %d , and the write length is: %d /n", len, w_len );
close( w_timer_handle );
用该方法仍然无法避免丢失某些数据,就是有些进程并未将数据写出。
方法二:用PWRITE()原子操作
int w_timer_handle;
char temp[1000];
int len;
int w_len;
int offset;
offset = llrank * 60;
printf("%d offset: %d /n", llrank, offset );
sprintf(temp, "process %d time_total %lf comm_time %lf/n",llrank, total_time_last[llrank], comm_time[llrank] );
len = strlen(temp);
w_timer_handle = open( ftimer, O_WRONLY | O_APPEND );
w_len = write( w_timer_handle, temp, len );
//w_len = pwrite( w_timer_handle, temp, len, offset);
fsync( w_timer_handle );
if( w_len != len )
printf("write to timer file error. /n");
printf("The length is: %d , and the write length is: %d /n", len, w_len );
close( w_timer_handle );
方法三: 用记录锁的方法,该方法使的丢失数据最少,但是也没有达到要求,理论上觉得该方法应该没有问题的。
struct flock timer_flock;
//int fd;
//fd = open( ftimer, O_APPEND | O_WRONLY );
timer_flock.l_type = F_WRLCK;
timer_flock.l_start = 0;
timer_flock.l_whence = SEEK_SET;
timer_flock.l_len = 0;
while ( fcntl( fd, F_SETLK, &timer_flock ) < 0 )
{
printf("process %d Can't set_lock /n", llrank );
sleep( llrank+1 );
}
w_len = write( fd, temp, len );
fsync( fd );
if( w_len != len )
printf("write to timer file error. /n");
close( fd );
说明,开始得时候,把fd作为局部变量,发现仍然存在以前的问题,又改为全局变量还是不ok;后来分析下觉得其实跟全局变量或者局部变量没有关系。
方法四:利用标准输入输出,但是fseek进行重定位,这个方法理论上觉得完全可行,但不知道什么原因,总是输出一些乱码,也就是有效数据给写成乱码的形式,不知道是什么原因。
char temp[1000];
int len;
int w_len;
int offset;
sprintf(temp, "process %d time_total %lf comm_time %lf",llrank, total_time_last[llrank], comm_time[llrank] );
len = strlen( temp );
for( i = len; i< 59; i++ )
sprintf( temp+i, " ");
sprintf( temp+59, "/n" );
pf_timer = fopen( ftimer, "a+" );
rewind( pf_timer );
offset = 60 * llrank;
fseek( pf_timer, offset, SEEK_SET );
err = fprintf( pf_timer, "%s", temp );
fflush( pf_timer );
if( err < 0 )
printf("write to timer error /n");
fclose( pf_timer );
最后,虽然采用另一种方法,就是只让一个进程进行文件写操作,从其他进程收集将要输出的数据,然后该进程一直等待,当所有进程都获得数据后,再有0号进程,进行写操作。
我对前面的几个方法进行了简单分析,不知道是否正确。例如O_APPEND虽然是原子操作,但是由于MPI程序中几个进程独立运行,而理论上O_APPEND之所以有效,是因为进程A B在被调度时同时完成fseek与open,但是若几个进程在不同的节点上(相当于几台不同机器,用网络连接起来,但是共用一个磁盘系统),不受统一调度控制,所以仍然无法保证写冲突,另外几个方法,我觉得原因可能也是如此。
请有兴趣的人共同分析。