【发布时间】:2016-04-11 21:08:56
【问题描述】:
Glibc 说 fclose()/fopen()/fprintf()/ftell() 是线程安全的。但是当一个线程正在写入或读取文件而另一个线程正在关闭文件时会发生什么?
假设我有一个看起来像这样的函数
FILE * f; //f is opened when program starts
int log(char * str)
{
fprintf(f, "%s", str);
if (ftell(f) > SIZE_LIMIT) {
pthread_mutex_lock(&mutex);
if (ftell(f) > SIZE_LIMIT) {
fclose(f);
rename(OLD_PATH, NEW_PATH);
f = open(OLD_PATH, "a");
}
pthread_mutex_unlock(&mutex);
}
}
这个函数被多个线程用来写入文件。是否安全,即没有崩溃?请注意,函数返回错误很好,我的实验表明程序会间歇性崩溃。
编辑: 1. 正如@2501 所指出的,“在关联文件关闭后,指向 FILE 对象的指针的值是不确定的”,这解释了间歇性崩溃。 如果我使用 freopen 重写代码会怎样?
pthread_mutex_lock(&mutex);
if (ftell(f) > SIZE_LIMIT) {
rename(OLD_PATH, NEW_PATH);
f = freopen(OLD_PATH, "a", f);
}
pthread_mutex_unlock(&mutex);
【问题讨论】:
-
关注安全的代码应该检查各种 I/O 函数的结果。 (也许为了简化,它们被忽略了。)
-
即使使用线程安全函数,您也不能在另一个线程正在或可能正在使用资源时释放资源。
-
如果担心性能,可以使用
fputs( str, fp );,而不是fprintf( fp, "%s", str );。 -
@AndrewHenle 谢谢,性能差异是由于 fprintf() 中的格式化处理开销造成的吗?
-
@wei 是因为 fprintf() 中的格式化处理开销导致的性能差异吗? 是的。根据实施情况,它可能很重要。例如,GLIBC implementation 的前 1200 行左右都是在
fprintf()处理中使用的宏(GLIBC 使用vfprintf()来实现fprintf())(还要注意在那个实现中,只传递一个简单的字符串会绕过所有这些处理 - 不要使用它,因为如果有人将带有%的字符串传递给您的log()调用,它会导致问题。)