デッドロック

2013/01/16

ファイルとレコードのロックの使用

UNIX のロック機能を使用すると、デッドロックを検出および防止できます。デッドロックが発生する可能性があるのは、システムがレコードロックインタフェースを休眠させようとするときだけです。このとき、2 つのプロセスがデッドロック状態であるかどうかを判断する検索が行われます。潜在的なデッドロックが検出されると、ロックインタフェースは失敗し、デッドロックを示す値が errno に設定されます。F_SETLK を使用してロックを設定するプロセスは、ロックがすぐに取得できなくても、それを待たないので、デッドロックは発生しません。

事務処理に必須の「ファイル・ロッキング」

F_RDLCK リードロック read lock (シェアードロック shared lock)
F_WRLCK ライトロック write lock (排他ロック exclusive lock)
F_UNLCK アンロック (上記ふたつのロックを解除)

F_WRLCK は、ファイルの特定の領域を、ひとつのプロセスだけがロック可能にするためのもので、これは排他制御そのものですから、「排他ロック」(exclusive lock)と呼ばれることがあります。前記の creat(), link(), open() を使った方式に比べて、ファイルの更新に必要な一部分だけをロックすることができるため、更新の場所が違う限り、複数のプロセスが同時に同じファイルにアクセスすることが可能で、データ処理の効率が良くなります。F_WRLCK は、対象とするファイルに、書き込みのためのパーミションを必要とします。

F_RDLCK の方は、通常、ファイルの読み取りに使用され、ファイルの更新を禁止します。この場合は、既に他のプロセスが F_RDLCK した部分に、オーバーラップした F_RDLCK をかけることができます。F_WRLCK された 部分に F_RDLCK をかけることはできません。
また、F_RDLCK された部分に、F_WRLCKをかけることもできません。書き込みのためのロックと違って、ファイルのロックされた部分を、読み込みを行う複数のプロセスが共有することができるため、「シェアードロック」(shared lock) と呼ばれることがあります。もちろん、これを使うためにはリードのパーミションが必要です。

IPC: ファイル・ロック
アドバイザリロックと強制ロックの選択

サンプルコード

アドバイザリロック
fcntl(2)
flock_sample1-1.c
#include #include #include #include #include int main() { struct flock fl1; struct flock fl2; int fd; fl1.l_type = F_WRLCK; fl1.l_whence = SEEK_SET; fl1.l_start = 0; fl1.l_len = 1; fl1.l_pid = getpid(); fl2.l_type = F_WRLCK; fl2.l_whence = SEEK_SET; fl2.l_start = 1; fl2.l_len = 1; fl2.l_pid = getpid(); if ((fd = open("lock.txt", O_RDWR)) == -1) { perror("open"); exit(1); } printf("Press to try to get lock(1): "); getchar(); fputs("waiting...", stdout); if (fcntl(fd, F_SETLKW, &fl1) == -1) { perror("fcntl"); exit(1); } puts("Locked(1)."); printf("Press to try to get lock(2): "); getchar(); fputs("waiting...", stdout); if (fcntl(fd, F_SETLKW, &fl2) == -1) { perror("fcntl"); exit(1); } puts("Locked(2)."); printf("Press to release lock(2): "); getchar(); fl2.l_type = F_UNLCK; if (fcntl(fd, F_SETLK, &fl2) == -1) { perror("fcntl"); exit(1); } puts("Unlocked(2)."); printf("Press to release lock(1): "); getchar(); fl1.l_type = F_UNLCK; if (fcntl(fd, F_SETLK, &fl1) == -1) { perror("fcntl"); exit(1); } puts("Unlocked(1)."); close(fd); return 0; }
flock_sample1-2.c
#include #include #include #include #include int main() { struct flock fl1; struct flock fl2; int fd; fl1.l_type = F_WRLCK; fl1.l_whence = SEEK_SET; fl1.l_start = 0; fl1.l_len = 1; fl1.l_pid = getpid(); fl2.l_type = F_WRLCK; fl2.l_whence = SEEK_SET; fl2.l_start = 1; fl2.l_len = 1; fl2.l_pid = getpid(); if ((fd = open("lock.txt", O_RDWR)) == -1) { perror("open"); exit(1); } printf("Press to try to get lock(2): "); getchar(); fputs("waiting...", stdout); if (fcntl(fd, F_SETLKW, &fl2) == -1) { perror("fcntl"); exit(1); } puts("Locked(2)."); printf("Press to try to get lock(1): "); getchar(); fputs("waiting...", stdout); if (fcntl(fd, F_SETLKW, &fl1) == -1) { perror("fcntl"); exit(1); } puts("Locked(1)."); printf("Press to release lock(1): "); getchar(); fl1.l_type = F_UNLCK; if (fcntl(fd, F_SETLK, &fl1) == -1) { perror("fcntl"); exit(1); } puts("Unlocked(1)."); printf("Press to release lock(2): "); getchar(); fl2.l_type = F_UNLCK; if (fcntl(fd, F_SETLK, &fl2) == -1) { perror("fcntl"); exit(1); } puts("Unlocked(2)."); close(fd); return 0; }
実行例
# ./flock_sample1-1 Press to try to get lock(1): waiting...Locked(1). Press to try to get lock(2): waiting...Locked(2). Press to release lock(2): Unlocked(2). Press to release lock(1): Unlocked(1).
# ./flock_sample1-2 Press to try to get lock(2): waiting...Locked(2). Press to try to get lock(1): fcntl: Deadlock situation detected/avoided
Deadlock situation detected/avoided

原因
プログラミングのデッドロック状態が検出され、回避されました。

対処方法
システムがデッドロックを検出し回避しなかった場合は、ソフトウェアの一部がハングします。そのプログラムを再度実行してください。デッドロックが再び起こることはないかもしれません。

テクニカルノート
このエラーは、通常、ファイルとレコードのロックに関連しています。ただし、mutex、セマフォ、条件変数、読み取り/書き込みロックが対象になる場合もあります。

このエラーの記号名は、EDEADLK、errno=45 です。

強制ロック
read(2), write(2)
flock_sample2-1.c
#include #include #include #include #include int main() { struct flock fl1; struct flock fl2; int fd; char buff[256] = "2222222222"; ssize_t n; fl1.l_type = F_WRLCK; fl1.l_whence = SEEK_SET; fl1.l_start = 0; fl1.l_len = 10; fl1.l_pid = getpid(); fl2.l_type = F_WRLCK; fl2.l_whence = SEEK_SET; fl2.l_start = 10; fl2.l_len = 10; fl2.l_pid = getpid(); if ((fd = open("sample.txt", O_RDWR|O_CREAT, 0600)) == -1) { perror("open"); exit(1); } printf("Press to try to get lock(1): "); getchar(); fputs("waiting...", stdout); if (fcntl(fd, F_SETLKW, &fl1) == -1) { perror("fcntl"); exit(1); } puts("Locked(1)."); printf("Press to try to read record(2): "); getchar(); fputs("waiting...", stdout); n = pread(fd, buff, fl2.l_len, fl2.l_start); if (n < 0) { printf("Read(2) fail. errno=%d\n", errno); } else { buff[n] = '\0'; printf("Read(2) success. buff=\"%s\"\n", buff); } printf("Press to release lock(1): "); getchar(); fl1.l_type = F_UNLCK; if (fcntl(fd, F_SETLK, &fl1) == -1) { perror("fcntl"); exit(1); } puts("Unlocked(1)."); close(fd); return 0; }
flock_sample2-2.c
#include #include #include #include #include int main() { struct flock fl1; struct flock fl2; int fd; char buff[256] = "1111111111"; ssize_t n; fl1.l_type = F_WRLCK; fl1.l_whence = SEEK_SET; fl1.l_start = 0; fl1.l_len = 10; fl1.l_pid = getpid(); fl2.l_type = F_WRLCK; fl2.l_whence = SEEK_SET; fl2.l_start = 10; fl2.l_len = 10; fl2.l_pid = getpid(); if ((fd = open("sample.txt", O_RDWR|O_CREAT, 0600)) == -1) { perror("open"); exit(1); } printf("Press to try to get lock(2): "); getchar(); fputs("waiting...", stdout); if (fcntl(fd, F_SETLKW, &fl2) == -1) { perror("fcntl"); exit(1); } puts("Locked(2)."); printf("Press to try to write record(1): "); getchar(); fputs("waiting...", stdout); n = pwrite(fd, buff, fl1.l_len, fl1.l_start); if (n < 0) { printf("Write(1) fail. errno=%d\n", errno); } else { buff[n] = '\0'; printf("Write(1) success. buff=\"%s\"\n", buff); } printf("Press to release lock(2): "); getchar(); fl2.l_type = F_UNLCK; if (fcntl(fd, F_SETLK, &fl2) == -1) { perror("fcntl"); exit(1); } puts("Unlocked(2)."); close(fd); return 0; }
実行例
# chmod +l sample.txt root@solaris11:~# ls -l sample.txt -rw-r-lr-- 1 root root 31 1月 17日 08:02 sample.txt # ./flock_sample2-1 Press to try to get lock(1): ★1. レコード1をロックする waiting...Locked(1). Press to try to read record(2): ★4. レコード2を読み込み waiting...Read(2) fail. errno=45 Press to release lock(1): Unlocked(1). # ./flock_sample2-2 Press to try to get lock(2): ★2. レコード2をロックする waiting...Locked(2). Press to try to write record(1): ★3. レコード1に書き込み waiting...Write(1) success. buff="1111111111" Press to release lock(2): Unlocked(2).
  1. flock_sample2-1 で sample.txt のレコード1(先頭から10文字)をロックする。
  2. flock_sample2-2 でレコード2(10文字目から10文字)をロックする。
  3. flock_sample2-2 でレコード1を書き込む。レコード1はflock_sample2-1でロックされていて書き込めない。writeはスリープしてロックの解除を待機する。
  4. flock_sample2-1 でレコード2を読み込みむ。レコード2はflock_sample2-2でロックされているので読めない。lock_sample2-2は、flock_sample2-1が持つロックの解除を、スリープ状態で待っている。この状態で、flock_sample2-1がスリープ状態に入れば、デッドロックになる。そのためreadはEDEADLK(45)を返して失敗する。
Read more ...