0

複数のスレッドを使用して同じファイルの異なる部分に同時に書き込むときに、書き込み中の正しいファイル位置を取得するのに問題があります。

ファイルへの 1 つのグローバル ファイル記述子があります。私の書き込み関数では、最初にミューテックスをロックしてlseek(global_fd, 0, SEEK_CUR)から、現在のファイル位置を取得します。次に、 を使用して 31 個のゼロ バイト (31 はエントリ サイズ) を書き込みwrite()、後で使用するためのスペースを確保します。次に、ミューテックスのロックを解除します。

関数の後半で、ローカルfd変数を同じファイルに宣言し、それを開きます。私は今、lseekそのローカルでfdを実行して、以前に学んだ私のスペースが予約されている位置に到達します。最後にwrite()、エントリ用にそこに 31 データ バイトを入力し、ローカルを閉じfdます。

問題は、まれに、エントリが予期された場所に書き込まれないことです (データが壊れているわけではありません。別のエントリと交換されているか、2 つのエントリが同じ場所に書き込まれているようです)。私が説明した「関数の書き込み」が実行されている複数のスレッドがあります。

pwrite()それ以来、それを使用して特定のオフセットに書き込むことができることを学びました。これはより効率的であり、 lseek(). ただし、最初に調べたいのは、元のアルゴリズムの何が問題なのですか? 予想される書き込み場所と、データが実際にファイルに格納される場所との間の不一致を引き起こす可能性のあるバッファリングの種類はありますか?

関連するコード スニペットを以下に示します。これが問題となる理由は、2 番目のデータ ファイルに、書き込み中のエントリが保存される場所を記録するためです。書き込み前に基づくその場所lseek()が正確でない場合、データが適切に一致しません。これは時々発生することです (再現するのは困難です。おそらく 100,000 回の書き込みに 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; 
}

完全を期すために、もう1つ注意してください。問題を引き起こしている可能性のある同様の、しかしより単純なルーチンがあります。これは buffered outputを使用しますが、各書き込みの最後に を(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 と同じ場所を指している場合)、標準ライブラリをバイパスするためにFILE *使用すると、ファイル内のどこにいるかについて標準ライブラリが混乱するという問題が発生する可能性があります。write()

于 2012-07-02T01:10:56.883 に答える