09
5月
2007

ロックとmutex

スレッドの同期には,pthread_joinによる合流の他に,ロックという方法がある.
mutexって何だ?
mutexの属性と初期化
mutexのロックとアンロック
サンプル

mutexって何だ?

mutexはmutual exclusionの略らしい. ということは,mutexは「ミューテクス」とかそんな感じの読みのような気がするが,正しい読みはよくわからない(※). とにかく,pthreadでは,mutexをロックすることによって,スレッド間の同期を行うことができる.
mutex = バイナリセマフォ
ということで,よいと思う. つまり,あるスレッドでロックしたmutexに対し,別のスレッドがロックしようとしたら,最初のスレッドがそのmutexを開放するまで後のスレッドは待たされる.

上図で,破線で囲まれた部分の処理が,同期が保証される領域で,クリティカルセクションという. 具体的にはクリティカルセクションでは,複数のスレッドで参照,更新するデータに対する処理を行う. クリティカルセクションで参照するデータは,別のスレッドによって更新途中になっている不完全なデータを参照したり更新したりしてしまう心配がない. 性能の観点からすると,クリティカルセクションは短い程よい.
※ 私はどういうわけか,「ムテフ」とか「マテフ」と読んでしまうが,これは,まぁ,間違ってるんだろうな.

mutexの属性と初期化

mutexはまず初期化する必要がある.
int pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutex-attr_t *mutexattr);
pthread_mutex_initはmutexattarで指定した属性をもつmutexを作成する. Linuxではmutex属性はmutex kindのみであり,これには以下があり,ロック使用としたmutexがすでにロックされていた場合の挙動が異なる.
fast
対象のmutexが開放されるまで待つ.
recursive
すぐに返る. mutexにはロックされた回数が保持され,同数アンロックされるまでロック解除状態にはならない… 共有ロックで使用できそうな気もするが,ロック状態とロック解除状態は外からどうやって見分けるんだ?
error checking
ロック関数はすぐに返る. 返り値はEDEADLK.
mutexを破壊する関数もある.
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
仕様としては,破壊したmutexは使用してはならない. Linuxではこの関数は何もしないらしいが,OSやハードウェアによってはmutexのために何か資源を保持するかもしれないので,いらなくなったら破壊しておくのが行儀のよいコードらしい. 可搬性はないようだが,以下のマクロによって,簡単にmutexを初期化できる.
pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
それぞれmutex kindがfast,recursive,error checkingであるmutexを作成する.

mutexのロックとアンロック

mutexを作成したら,あとは各スレッドにてロックしたりアンロックしたりする. ロックには
int pthread_mutex_lock(pthread_mutex_t *mutex));
を使用する. 引数に指定したmutexに対し,ロックをかける. 指定したmutexがロックされていた場合の挙動はmutex属性による.
int pthread_mutex_trylock(pthread_mutex_t *mutex);
は,基本的にpthread_mutex_lockと同様だが,mutex種別がfastで,指定したmutexがロック中でもすぐに返る. この際の返り値はEBUSY.
int pthread_mutex_unlock(pthread_mutex_t *mutex);
では,指定したmutexをアンロックする. mutex属性がerror checkingであれば,指定したmutexがロックされていなかったり,呼び出したスレッドがロックしたスレッドでなければエラーになる. 逆に言うと,mutex属性がfastやrecursiveだとロックされたmutexを別のスレッドでアンロックできるようになっているが,可搬性のためには使用すべきでない.

サンプル

ではサンプルを示す.

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>

#define MY_ABORT(__COMMENT__) \
      fprintf (stderr, "\n" __FILE__ "(%d) " __COMMENT__ "\n", __LINE__); \
      exit (1);

#define MY_THREAD_ABORT(__COMMENT__) \
      fprintf (stderr, "\n" __FILE__ "(%d) " __COMMENT__ "\n", __LINE__); \
      pthread_exit(NULL);

/*
 * スレッドパラメータ格納用
 */
typedef struct {
  int var1;
  int var2;
  int sum;
  int printFlag;
  pthread_mutex_t printLock;
} SHARED_THREAD_ARG;

/*
 * 表示スレッドイニシャル関数
 */
void *printThread(void *arg)
{
  SHARED_THREAD_ARG *my_thread_arg =(SHARED_THREAD_ARG*)arg;

  struct timespec sleepTime;
  sleepTime.tv_sec = 0;
  sleepTime.tv_nsec = 0.1 * 1000 * 1000;

  while(my_thread_arg->printFlag > 0)
    {
#ifdef SYNC
      if (pthread_mutex_lock(&my_thread_arg->printLock) != 0)
        {
          MY_THREAD_ABORT("faield to mutex lock");
        }
#endif
      printf("%d + %d = %d\n",
             my_thread_arg->var1,
             my_thread_arg->var2,
             my_thread_arg->sum);
#ifdef SYNC
      if (pthread_mutex_unlock(&my_thread_arg->printLock) != 0)
        {
          MY_THREAD_ABORT("failed to mutex unlock");
        }
#else
#endif
      nanosleep(&sleepTime, NULL);
    }

  return 0;
}

/*
 * 計算スレッドイニシャル関数
 */
void *calcurateThread(void *arg)
{
  SHARED_THREAD_ARG *my_thread_arg =(SHARED_THREAD_ARG*)arg;

  struct timespec sleepTime;
  sleepTime.tv_sec = 0;
  sleepTime.tv_nsec = 0.5 * 1000 * 1000;

  int i = 0;

  for (i = 0; i < 3; i++)
    {
#ifdef SYNC
      if (pthread_mutex_lock(&my_thread_arg->printLock) != 0)
        {
          MY_THREAD_ABORT("failed to mutex lock");
        }
#endif
      nanosleep(&sleepTime, NULL);
      my_thread_arg->var1++;
      nanosleep(&sleepTime, NULL);
      my_thread_arg->var2++;
      nanosleep(&sleepTime, NULL);
      my_thread_arg->sum = my_thread_arg->var1
        + my_thread_arg->var2;
#ifdef SYNC
      if(pthread_mutex_unlock(&my_thread_arg->printLock) != 0)
        {
          MY_THREAD_ABORT("failed to mutex lock");
        }
#endif
    }

  my_thread_arg->printFlag = 0;

  return 0;
}


int main(int argc,char *argv[])
{
  int status;
  void *thread_return;

  // スレッドa,b共有パラメータ
  SHARED_THREAD_ARG sharedArg = {0, 0, 0, 1, PTHREAD_MUTEX_INITIALIZER};

  pthread_t thread_a;
  // スレッドaを生成
  status=pthread_create(&thread_a, NULL, printThread, &sharedArg);
  if(status!=0){
    MY_ABORT("failed to create thread_a");
  }

  pthread_t thread_b;
  // スレッドbを生成
  status=pthread_create(&thread_b, NULL, calcurateThread, &sharedArg);
  if(status!=0){
    MY_ABORT("failed to create thread_b");
  }

  // スレッドaが終了するのを待つ.
  status = pthread_join(thread_a, &thread_return);
  if (status != 0) {
    MY_ABORT("failed to join thread_a");
  }

  // スレッドbが終了するのを待つ.
  status = pthread_join(thread_b, &thread_return);
  if (status != 0) {
    MY_ABORT("failed to join thread_b");
  }
  
  fprintf(stderr, "\n");

  return 0;
}

You may also like...