0

当使用多个线程同时写入同一文件的不同部分时,我在获取正确的文件位置时遇到问题。

我有一个文件的全局文件描述符。在我的写作功能中,我首先锁定一个互斥锁,然后lseek(global_fd, 0, SEEK_CUR)获取当前文件位置。接下来我使用 写入 31 个零字节(31 是我的条目大小)write(),实际上是为以后保留空间。然后我解锁互斥锁。

稍后在函数中,我fd为同一个文件声明了一个局部变量,然后打开它。我现在lseek对该本地进行一次操作,fd以到达我之前学到的位置,我的空间是保留的。最后,我write()在那里输入了 31 个数据字节,并关闭了本地fd.

问题似乎很少,条目没有写入预期的位置(它不是损坏的数据 - 似乎它与不同的条目交换,或者两个条目被写入相同的位置)。有多个线程在运行我描述的“写入功能”。

从那以后我了解到pwrite()可以用来写入特定的偏移量,这样效率更高,并且消除了lseek(). 但是,我首先要弄清楚:我原来的算法有什么问题?是否有任何类型的缓冲可能导致预期写入位置与数据实际最终存储在文件中的位置之间的差异?

相关代码片段如下。这是一个问题的原因是,在第二个数据文件中,我记录了我正在编写的条目将被存储的位置。如果根据lseek()写入之前的位置不准确,我的数据不正确匹配——这有时会发生(很难重现——它可能发生在 100k 写入中的 1 次)。谢谢!

db_entry_add(...)
{
   char dbrecord[DB_ENTRY_SIZE];
   int retval;

   pthread_mutex_lock(&db_mutex);

   /* determine the EOF index, at which we will add the log entry */
   off_t ndb_offset = lseek(cfg.curr_fd, 0, SEEK_CUR);
   if (ndb_offset == -1)
   {
      fprintf(stderr, "Unable to determine ndb offset: %s\n", strerror_s(errno, ebuf, sizeof(ebuf)));
      pthread_mutex_unlock(&db_mutex);
      return 0;
   }

   /* reserve entry-size bytes at the location, at which we will
      later add the log entry */
   memset(dbrecord, 0, sizeof(dbrecord));

   /* note: db_write() is a write() loop */ 
   if (db_write(cfg.curr_fd, (char *) &dbrecord, DB_ENTRY_SIZE) < 0)
   {
      fprintf(stderr, "db_entry_add2db - db_write failed!");
      close(curr_fd);
      pthread_mutex_unlock(&db_mutex);

      return 0;
   }

   pthread_mutex_unlock(&db_mutex);

   /* in another data file, we now record that the entry we're going to write 
      will be at the specified location. if it's not (which is the problem,
      on rare occasion), our data will be inconsistent */ 
   advertise_entry_location(ndb_offset);
   ...

   /* open the data file */
   int write_fd = open(path, O_CREAT|O_LARGEFILE|O_WRONLY, 0644);
   if (write_fd < 0)
   {
      fprintf(stderr, "%s: Unable to open file %s: %s\n", __func__, cfg.curr_silo_db_path, strerror_s(errno, ebuf, sizeof(ebuf)));
      return 0;
   }

   pthread_mutex_lock(&db_mutex);

   /* seek to our reserved write location */
   if (lseek(write_fd, ndb_offset, SEEK_SET) == -1)
   {
      fprintf(stderr, "%s: lseek failed: %s\n", __func__, strerror_s(errno, ebuf, sizeof(ebuf)));
      close(write_fd);
      return 0;
   }

   pthread_mutex_unlock(&db_mutex);

   /* write the entry */
   /* note: db_write_with_mutex is a write() loop wrapped with db_mutex lock and unlock */ 
   if (db_write_with_mutex(write_fd, (char *) &dbrecord, DB_ENTRY_SIZE) < 0)
   {
      fprintf(stderr, "db_entry_add2db - db_write failed!");         
      close(write_fd);

      return 0;
   }

   /* close the data file */
   close(write_fd);

   return 1; 
}

为了完整起见,还要注意。我有一个类似但更简单的例程,也可能导致问题。这个使用缓冲输出(FILE*, fopen, fwrite),但fflush()在每次写入结束时执行。它写入与先前例程不同的文件,但可能导致相同的症状。

pthread_mutex_lock(&data_mutex);

/* determine the offset at which the data will be written. this has to be accurate,    
otherwise it could be causing the problem */ 
offset = ftell(current_fp);

fwrite(data);
fflush(current_fp);

pthread_mutex_unlock(&data_mutex);
4

2 回答 2

2

似乎有几个地方可能会出错。我将进行以下更改:(1) 保持一致并按照 bdonlan 的建议使用相同的 I/O 库,(2) 使 lseek() 并写入由互斥锁保护的原子操作,以便只有一个线程一次可以执行添加到两个文件的操作。SEEK_CUR 根据文件偏移指针的当前位置进行搜索,所以您不希望 SEEK_END 搜索到文件末尾以便追加到那里吗?然后,如果您正在修改文件的特定部分,您将使用 SEEK_SET 重新定位到您要写入的位置。您可能希望在互斥锁保护部分中执行此操作,以便仅允许单个线程进行文件定位和文件更新。

于 2012-07-02T01:37:41.543 回答
1

如果您同时使用“更简单的例程”,这确实可能是个问题。如果这些是单独的文件描述符,则无法确保它们始终都指向文件末尾(除非您使用附加模式,但是我不确定附加模式的 ftell 语义是什么)。如果它们是相同的 fd(即,您有一个原始 fd 和一个指向同一个地方的 fd),那么当您使用绕过它FILE *时,您可能会遇到标准库混淆您在文件中的位置的问题。write()

于 2012-07-02T01:10:56.883 回答