C - 使用多个线程时使用lseek()获得的write()位置不准确

时间:2012-07-02 00:38:06

标签: c file-io pthreads

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

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

稍后在函数中,我将一个本地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);

2 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分:1)

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