Linuxマルチスレッドでのfwriteとwriteの使用に関する詳細な説明

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;
}

おすすめ

転載: blog.csdn.net/qq_40989769/article/details/110927256