2007年05月26日
flock(2)はスレッドも排他するか?
以前人づてに、flock(2)がプロセス間排他はするけどスレッド間排他はしないという話を聞いた。
これが本当だと、今まで自分が書いたマルチスレッドアプリケーションのファイルを扱う部分の根幹が揺らいでしまう。特にファイルが壊れたりするような現象は見られなかったので今まで放置してきたが、非常に心配になってきたので調べてみた。
実験環境と検証用コードは以下の通り。
dylan% uname -a Linux dylan 2.6.17-10-generic #2 SMP Tue Dec 5 22:28:26 UTC 2006 i686 GNU/Linux dylan% gcc --version gcc (GCC) 4.1.2 20060928 (prerelease) (Ubuntu 4.1.1-13ubuntu5) Copyright (C) 2006 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#include <sys/file.h>
#include <pthread.h>
#include <stdio.h>
#include <assert.h>
static int locked = 0;
static pthread_mutex_t locked_mutex = PTHREAD_MUTEX_INITIALIZER;
void*
func1(void* arg)
{
int r;
char ch = (char)arg;
while (1) {
FILE *f = fopen("test.txt", "a+");
assert(f != NULL);
int fd = fileno(f);
assert(f >= 0);
r = flock(fd, LOCK_EX);
assert(r == 0);
pthread_mutex_lock(&locked_mutex);
assert(locked == 0);
locked = 1;
pthread_mutex_unlock(&locked_mutex);
fwrite(&ch, 1, 1, f);
fwrite(&ch, 1, 1, f);
printf("%d\n", pthread_self());
pthread_mutex_lock(&locked_mutex);
locked = 0;
pthread_mutex_unlock(&locked_mutex);
sched_yield();
r = flock(fd, LOCK_UN);
assert(r == 0);
fflush(f);
fclose(f);
}
return NULL;
}
#define THREAD_NUM 200
int
main(void)
{
pthread_t ths[THREAD_NUM];
int i;
for (i = 0; i < THREAD_NUM; i++) {
char ch = 'a' + i;
pthread_create(&ths[i], NULL, func1, (void*)ch);
}
for (i = 0; i < THREAD_NUM; i++)
pthread_join(ths[i], NULL);
return 0;
}
スレッドは200個で、それぞれのスレッドはファイルに追記を行う。ただし毎回flock(2)で保護している。
flock(LOCK_EX)の直後とflock(LOCK_UN)の直前でlockedという変数を変更している。もしflok(2)がスレッドを排他するのであれば、lockedを1にする直前でlockedは必ず0になっているはずである。
さらに、locked = 0にした後でsched_yield(2)を呼び出す事で、明示的に他のスレッドにCPUを明け渡すようにした。こうすることで競合がおきやすくなるはずだ。
これを実行してみた。
dylan% gcc -Wall flock.c -lpthread; rm test.txt; ./a.out
CPUが2つ有るマシンで暫くの間動かしてみたが、assertで落ちる事は無かった。この実験の結果としては、スレッド間で排他できているように思える。
一応、カーネルのコードもささっと読んでみた。file lockの所持者はstruct file_lockのfl_ownerで管理されており、これはfl_owner_tという型で、これはinclude/linux/fs.hで次のように定義されている。
/* * The POSIX file lock owner is determined by * the "struct files_struct" in the thread group * (or NULL for no owner - BSD locks). * * Lockd stuffs a "host" pointer into this. */ typedef struct files_struct *fl_owner_t;
コメントから見る限りスレッドグループについてきちんと考えられてそう。もう一つきになるstruct file_lockのfl_pidについても、current->tgidが代入されているので大丈夫そう。
ただ、「プログラムが間違ってるよ」「カーネル読み間違ってるよ」「たまたま落ちなかっただけでもっと激しくテストしたら落ちるよ」「昔のカーネルだとそうじゃないよ」とか有り得るかもしれないので注意。
- by
- at 06:02

comments
排他できてるかどうか確認するだけなら、排他できてれば deadlock するような code の方がいいんでは。
しかし排他出来ていないのであれば、このコードで一瞬で止まるはずなのでこれでいいかなーと。証明は得意なjun0さんに任せたっ