flock(2) は thread 間で排他するか

「〜ったら?」と言うだけなのもアレなので、書いてみた。ちなみに向こうの comment 欄では deadlock がどうとか世迷い言を言ってるけど、気にしないように。

Thread を二つ作って、気ままに flock() させる。Thread 間のときだけ動作不定なんてことは無いだろうから、I/O error の類には遭遇しないものと仮定すると、両方 lock が成功するか、片方失敗する。両方成功した場合は、sem_trywait() で早かった方が sem_wait() で待ちに入り、もう一方が起こす。片方失敗の場合は、早かった方が sem_wait() するので、負け犬の方が flock() (を試みて失敗) するまで lock を持ちつづけることが保証される。

TODO: 思惑通りの動作をすることを証明する。

#include <sys/file.h>
#include <pthread.h>
#include <assert.h>
#include <semaphore.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

#define TEST_FILE "test.txt"

static sem_t sem;

void *
f (void *_)
{
  FILE *fp = fopen (TEST_FILE, "a+");
  int fd = fileno (fp);

  puts ("Thread");
  if (fd == -1)
    {
      perror (NULL);
      exit (1);
    }

  if (flock (fd, LOCK_EX | LOCK_NB) == 0)
    {
      /* I got the lock.  The semaphore starts out with value 1, so we
       * receive EAGAIN iff we're the second to receive the lock.  */
      if (sem_trywait (&sem) == 0)
        {
          /* The second thread will sem_post() once it sees that the
           * file is locked, which it detects either by seeing the
           * semaphore's value or by the return value of flock().  */
          sem_wait (&sem);
        }
      else if (errno == EWOULDBLOCK)
        {
          /* Loser thread.  Feed the winner.  */
          puts ("Both threads successfully locked.");
          sem_post (&sem);
        }
      else
        assert (0);
    }
  else
    {
      if (errno == EWOULDBLOCK)
        {
          /* The other thread has the lock.  Wake it up and exit.  */
          puts ("One thread couldn't lock the file.");
          sem_post (&sem);
        }
      else
        assert (0);
    }
  fclose (fp);
  return NULL;
}

int
main ()
{
  pthread_t ths[2];
  int i;
  sem_init (&sem, 0, 1);
  for (i = 0; i < 2; i++)
    pthread_create (&ths[i], NULL, f, NULL);
  for (i = 0; i < 2; i++)
    pthread_join (ths[i], NULL);
  return 0;
}