flockのサンプルコード

マルチプロセスな処理をするのに必須な排他制御ですが、flockのサンプルが見つからなかったので書いてみました。

まず排他制御しないとまずい例は以下の様なコードでしょうか。(サンプルなのでエラー処理をしていません。)
ファイルを読み、記述されている数字をインクリメントして再度ファイルに書くという処理を2つのプロセスから10回ずつ行っています。合計で20回のインクリメントが走るので最後の値は20を期待してるのですがそうはなりません。

#include "stdio.h"
#include "string.h"
#include "stdlib.h"
#include "unistd.h"

int main(int argc, char* argv) {
	char* filename = "test.txt";
	FILE* fh;
	char rbuf[16], wbuf[16];	

	// ファイルの初期化
	if (fh = fopen(filename, "w")) {
		fclose(fh);
	}

	// 親プロセスと子プロセスで同時に以下のコードを走らせる
	pid_t id = fork();	
	for (int i = 0; i < 10; i++) {
		pid_t cur = getpid();
		if (fh = fopen(filename, "r+")) {
			fread(rbuf, sizeof(char), sizeof(rbuf), fh);
			fseek(fh, 0L, SEEK_SET);
			sprintf(wbuf, "%d", atoi(rbuf)+1);
			fwrite(wbuf, sizeof(char), strlen(wbuf), fh);
			printf("%d:%s\n", cur, wbuf);
			fclose(fh);			
		}
	}
}

実行毎に結果は違いますが、例えば以下の様な出力が得られます。

$ g++ flock_test.cpp -o flock_test && ./flock_test
# 出力例1 プロセスAが書き込む前に、プロセスBが読み込んでいる
80293:1
80292:1
80293:2
80293:3
80293:4
80292:5
80292:6
80293:7
80292:7
80293:8
80292:8
80293:9
80292:9
80293:10
80292:10
80293:11
80292:11
80293:12
80292:12
80292:13

# 出力例2プロセスAの書込中に、プロセスBも書込んでいる
$ ./flock_test
80322:1
80322:2
80323:2
80323:3
80323:4
80322:3
80323:4
80322:5
80323:6
80322:7
80322:8
80322:9
80322:10
80322:11
80322:12
80323:8
80323:83    # ここで値が壊れている
80323:84
80323:85
80323:86

特に出力例2ではプロセスAが「13」と書こうとして1を書いたタイミングでプロセスBが8書き、その後プロセスAが3を書き込み、見事に値が壊れています。このように複数のプロセスから同一ファイルへアクセスする為には排他制御が必須で、linux系のOSではflockがよく使われます。

先ほどのコードをflockを使って排他制御してみたコードが以下になります。(やはりエラー処理はしていません) 処理自体は簡単で、flockにロックを取得したいファイルのファイルディスクリプタを指定してロックを取得します。ファイルディスクリプタはfilenoでファイルハンドルから取得できます。

#include "stdio.h"
#include "string.h"
#include "stdlib.h"
#include "unistd.h"
#include "sys/file.h"

int main(int argc, char* argv[]) {
	char* filename = "test.txt";
	FILE* fh;
	char rbuf[16], wbuf[16];	

	// ファイルの初期化
	if (fh = fopen(filename, "w")) {
		fclose(fh);
	}

	// 親プロセスと子プロセスで同時に以下のコードを走らせる
	pid_t id = fork();	
	for (int i = 0; i < 10; i++) {
		pid_t cur = getpid();
		if (fh = fopen(filename, "r+")) {
			int fd = fileno(fh);
			if (flock(fd, LOCK_EX) == 0) {  // ロックを取得
				fread(rbuf, sizeof(char), sizeof(rbuf), fh);
				fseek(fh, 0L, SEEK_SET);
				sprintf(wbuf, "%d", atoi(rbuf)+1);
				fwrite(wbuf, sizeof(char), strlen(wbuf), fh);
				printf("%d:%s\n", cur, wbuf);
			}
			fclose(fh);
			flock(fd, LOCK_UN);			    // ロックを解放
		}
	}
}

これだと期待した出力が得られますね。

$ g++ flock_test.cpp -o flock_test && ./flock_test
80475:1
80475:2
80475:3
80475:4
80475:5
80475:6
80475:7
80475:8
80475:9
80475:10
80476:11
80476:12
80476:13
80476:14
80476:15
80476:16
80476:17
80476:18
80476:19
80476:20

なおflock時に指定するフラグは以下の通り。

#define   LOCK_SH   1    /* shared lock */
#define   LOCK_EX   2    /* exclusive lock */
#define   LOCK_NB   4    /* don't block when locking */
#define   LOCK_UN   8    /* unlock */

後flockはタイムアウトの機構が無いのが色々と面倒です。これだったらデータベースのトランザクション使った方が幾らか楽な気がしましたが、どうなんでしょうか。

Leave a Reply

Your email address will not be published. Required fields are marked *