【问题标题】:Are fclose(), fprintf(), ftell() thread_safe only in terms of each function itself?fclose()、fprintf()、ftell() 是否仅就每个函数本身而言是线程安全的?
【发布时间】: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() 调用,它会导致问题。)

标签: c linux


【解决方案1】:

这些函数中的每一个都会锁定与FILE* 关联的互斥锁。所以这些函数对于特定的FILE* 对象是“原子的”。但是一旦FILE*对象被关闭,就无法使用了。因此,如果FILE* 被关闭并且另一个线程尝试使用该关闭的FILE*,那么您将由于尝试写入已关闭的文件而失败。

请注意,这不包括在不与其他线程同步的情况下更改 f 变量时可能遇到的任何数据竞争。 (从我们看到的代码sn-p来看,并不清楚那里是否有比赛,但我猜可能有)。

【讨论】:

  • 我的印象是写入关闭的文件流会返回错误,但没有意识到“f”可能指向无效的东西。如果我像在原版中编辑的那样使用 freopen() 会怎样发帖?
  • 如果freopen() 出于某种原因返回错误,该方案仍然可能存在问题。否则,我认为可能没问题。但我真的不是 100% 确定。
  • 谢谢。我看了一下 glibc 的 freopen() 实现,它不会释放 FILE 数据,所以它一直持有文件锁,不像 fclose() 释放锁然后释放 FILE 结构。但我想这可能与安德鲁在另一个答案中所说的那样依赖于实现。
【解决方案2】:

使用 fclose 关闭流后,FILE 指针的值是不确定的。这意味着使用它会导致未定义的行为。

7.21.3 文件

  1. ... 指向 FILE 对象的指针的值为 关联文件关闭后不确定...

由于 fprintf 调用可能在 fclose() 和 open() 之间由其他线程发生,当指针 f 的值不确定时,行为或您的代码是未定义的。

要定义代码,fprintf 调用和任何其他使用指针的调用也应该被互斥锁锁定。

【讨论】:

  • 啊,不确定的值解释了我看到的崩溃。如果我像在原始帖子中编辑的那样使用 freopen() 会怎样?
  • @wei 如果我像在原始帖子中编辑的那样使用 freopen() 会怎样? 这可能会更糟,具体取决于实现。我在standards 中看不到任何保证,它要求freopen() 始终保持内部一致的FILE * 对象。即使它是互斥的,隐含的fclose() 也可以在调用fprintf() 的过程中从任何线程下使对象无效。它可能有效,但即使有效,这样做充其量也是依赖于实现,最坏的情况是未定义。
  • @wei 由于freopen首先关闭了与流关联的文件,在打开它之前,行为仍然是未定义的。
  • @AndrewHenle & 2501 谢谢,我想我必须在 fprintf/fputs() 之前使用 rdwr 锁
  • @wei 您应该能够使用flockfile()funlockfile() 并在它们之间包装整个log() 函数,而不是单独的互斥锁或其他锁。请参阅pubs.opengroup.org/onlinepubs/9699919799/functions/… 以及有关说明和示例:gnu.org/software/libc/manual/html_node/Streams-and-Threads.html 请注意,如果您在 Linux 或 Solaris 上运行,您可以将流设置为绕过 FILE * 对象的内部锁定,从而使您的整个 log() 调用只有一个锁定/解锁周期的原子。
猜你喜欢
  • 2014-04-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-20
  • 1970-01-01
  • 2018-06-04
  • 2020-06-13
相关资源
最近更新 更多