Linuxでのファイル操作の場合、Cライブラリのファイルストリーム操作を使用することを好む人もいれば、Linuxのネイティブシステムコールを使用することを好む人もいます。一般的に、Cライブラリ自体がファイルキャッシュ処理を行うため、Cライブラリのファイル操作はより効率的です。今日、私は主にマルチスレッドでのfwriteとwriteを研究しています。各スレッドは、同じFILE *またはfdに書き込み、結果が期待される動作であるかどうかを確認します。
最初のケース:Cライブラリのfwriteを使用すると、スレッドの実装は次のようになります。
2番目のケース:システムコールwriteを使用すると、スレッドの実装は次のようになります。
メインスレッドの実装を見てみましょう。
その中で、LOOPSは1000000と定義されています。つまり、スレッド1〜3は、それぞれ「aaaaaa \ n」、「bbbbbb \ n」、「cccccc \ n」を100万回書き込みます。ファイルの書き込み操作が「スレッドセーフ」の場合、ファイルの最終行数は300万行になり、各行は「aaaaaa」、「bbbbbb」、「cccccc」のいずれかになります。
[記事のメリット] C / C ++ Linuxサーバーアーキテクトの学習資料とグループ812855908(C / C ++、Linux、golangテクノロジー、Nginx、ZeroMQ、MySQL、Redis、fastdfs、MongoDB、ZK、ストリーミングメディア、CDN、P2P、K8S、 Docker、TCP / IP、coroutine、DPDK、ffmpegなど)
次に、テスト結果を見てください。
1.マクロUSE_CLIBが定義されています。つまり、Cライブラリのfwriteが使用されています。結果は次のとおりです。
2.赤いUSE_CLIBをコメントアウトします。つまり、システムコールwriteを直接使用すると、結果は次のようになります。
上記のテスト結果から、Cライブラリのfwriteであろうとシステムコールの書き込みであろうと、出力が混合されないこと、つまり、複数のスレッドの出力が混合されないことを保証できます。システムコールの書き込みが使用され、最終的なファイルの行番号が間違っている予想どおり、合計300万行よりもはるかに小さいです。また、書き込みシステムコールが「スレッドセーフ」ではないことも証明します。マルチスレッドでは、出力は相互にカバーします。Cライブラリのfwriteは、スレッドセーフな関数です。
なぜこのような結果なのですか?まず、fwriteの実装を見てみましょう。
fwriteの内部では、ロックを使用して操作のシリアル化を保証し、それによってスレッドセーフを実現します。
そして、書き込みの実現については、次の図を参照してください。
書き込む前に、file_pos_readを使用してオフセットを取得します。マルチコアとマルチスレッドの場合、2つのコアが同時にカーネル状態になり、ファイルの現在のオフセットを同時に取得する可能性がある場合、値は等しくなければなりません。したがって、2つのスレッドは同じオフセットにデータを書き込みます。結局、ファイルの実際のサイズは予想されるサイズではありません。
最終要約:
Cライブラリのfwriteはスレッドセーフな関数であり、システムコールの書き込みには、オフセットがオーバーラップせず、予想される同時書き込みを実現するために、追加の書き込み用に追加のフラグビットO_APPENDが必要です。以下を変更できます。コードをテストし、独自の環境でテストします。
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//#define USE_CLIB
#define TEST_FILE "./tmp.txt"
#define LOOPS (1000000)
#ifdef USE_CLIB
struct thr_data {
FILE *fp;
const char *data;
};
static void * write_data(void *data)
{
struct thr_data *d;
size_t len;
int i;
d = data;
len = strlen(d->data);
for (i = 0; i < LOOPS; ++i) {
fwrite(d->data, len, 1, d->fp);
}
return NULL;
}
#else
struct thr_data {
int fd;
const char *data;
};
static void *write_data(void *data)
{
struct thr_data *d;
int i;
size_t len;
d = data;
len = strlen(d->data);
for (i = 0; i < LOOPS; ++i) {
write(d->fd, d->data, len);
}
return NULL;
}
#endif
int main(void)
{
pthread_t t1, t2, t3;
struct thr_data d1, d2, d3;
#ifdef USE_CLIB
FILE *fp = fopen(TEST_FILE, "w");
d1.fp = d2.fp = d3.fp = fp;
#else
//int fd = open(TEST_FILE, O_WRONLY|O_TRUNC);
int fd = open(TEST_FILE, O_WRONLY|O_TRUNC|O_APPEND);
d1.fd = d2.fd = d3.fd = fd;
#endif
d1.data = "aaaaaa\n";
d2.data = "bbbbbb\n";
d3.data = "cccccc\n";
pthread_create(&t1, NULL, write_data, &d1);
pthread_create(&t2, NULL, write_data, &d2);
pthread_create(&t3, NULL, write_data, &d3);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
#ifdef USE_CLIB
fclose(fp);
#else
close(fd);
#endif
return 0;
}