SDL2の排他制御

SDL_semaphore構造体とSDL_mutex構造体で排他制御を実行します。

 

1 SDL_semaphore構造体

SDL_Semaphore構造体でセマフォによるクリティカルセクションの排他制御を実行します。メンバ変数は隠蔽されている為、外部からはAPI経由でアクセスします。

/* The SDL semaphore structure, defined in SDL_syssem.c */
struct SDL_semaphore;
typedef struct SDL_semaphore SDL_sem;

1.1 SDL_CreateSemaphore

SDL_CreateSemaphoreでセマフォを作成します。

SDL_sem* SDL_CreateSemaphore(Uint32 initial_value)

引数initial_valueにはクリティカルセクションに入ることができるスレッドの数を指定します。一般的なセマフォと同様、1の場合はミューテックスとほぼ同様になります(ミューテックスと比べて優先度継承がない)。0の場合はSDL_cond構造体のように同期処理に使用可能です(SDL_cond構造体はSDL_mutexとセットでしか使えない)。


1.2 SDL_DestroySemaphore

SDL_DestroySemaphoreでセマフォを解放します。

void SDL_DestroySemaphore(SDL_sem* sem)

1.3 SDL_SemWait

SDL_SemWaitでセマフォをデクリメントします。クリティカルセクションに突入可能な数をすでに超えている場合はスリープします。

int SDL_SemWait(SDL_sem* sem)

1.4 SDL_SemPost

SDL_SemPostでセマフォをインクリメントし、スリープしているスレッドをひとつ起こします。インクリメントした数はDL_CreateSemaphoreで指定した数を超えることができます。

int SDL_SemPost(SDL_sem* sem)

1.5 SDL_SemWaitとSDL_SemPostの順序

クリティカルセクションに入ることができるスレッド数が枯渇しており、SDL_SemWait前にSDL_SemPostが実行された場合、SDL_SemWaitを実行したスレッドはずっとスリープしてしましそうですが、スリープしません。


SDL_SemPost実行前にSDL_SemWaitを実行したスレッドはスリープします。SDL_SemPost実行後にSDL_SemWaitを実行したスレッドはスリープしません。以下のプログラムはreturn 0まで実行されます。

#include <iostream>
#include <SDL.h>

int main(int argc, char *argv[])
{
  SDL_semaphore *semaphore;
  semaphore = SDL_CreateSemaphore(0);
  SDL_SemPost(semaphore);
  SDL_SemPost(semaphore);
  SDL_SemWait(semaphore);
  SDL_SemWait(semaphore);
  SDL_DestroySemaphore(semaphore);
  return 0;
}

本機能はスレッド間の同期処理に使用できます。


2 SDL_mutex構造体

SDL_mutex構造体でミューテックスによるクリティカルセクションの排他制御を実行します。メンバ変数は隠蔽されている為、外部からはAPI経由でアクセスします。SDL_semaphore構造体との違いはSDL_mutex構造体は優先度継承が使用できる点です。

/* The SDL mutex structure, defined in SDL_sysmutex.c */
struct SDL_mutex;
typedef struct SDL_mutex SDL_mutex;

2.1 SDL_CreateMutex

SDL_CreateMutexでミューテックスを生成します。

SDL_mutex* SDL_CreateMutex(void)

2.2 SDL_DestroyMutex

SDL_DestroyMutexでミューテックスを破棄します。

void SDL_DestroyMutex(SDL_mutex* mutex)

2.3 SDL_LockMutex

SDL_LockMutexでミューテックスをロックします。すでに他のスレッドがロック中の場合はスリープします。

int SDL_LockMutex(SDL_mutex* mutex)

SDL_TrylockMutexはすでに他のスレッドがロック中の場合はスリープせず、ロックできなかったことを知らせます。

int SDL_TryLockMutex(SDL_mutex* mutex)

2.4 SDL_UnlockMutex

SDL_UnlockMutexでミューテックスをアンロックします。

int SDL_UnlockMutex(SDL_mutex* mutex)

3 SDL_cond構造体

SDL_cond構造体は同期機構を提供する構造体で、SDL_mutex構造体と組み合わせて使用します。SDL_mutex構造体を使わない区間での同期にはSDL_semaphore構造体を使う必要があります。


3.1 SDL_CreateCond

SDL_CreateCondでSDL_cond構造体を生成します。

SDL_cond* SDL_CreateCond(void)

3.2 SDL_DestroyCond

SDL_DestroyCondでSDL_cond構造体を破棄します。

void SDL_DestroyCond(SDL_cond* cond)

3.3 SDL_CondWait

SDL_CondWaitでSDL_cond構造体へのシグナルが発行されるのを待ちます。引数mutexをアンロックしてからスリープします。シグナルが発行されたら、引数mutexをロックしてから呼び出しもとに復帰します。

int SDL_CondWait(SDL_cond*  cond,
                 SDL_mutex* mutex)

3.4 SDL_CondSignal

SDL_CondSignalでSDL_cond構造体へシグナルを発行します。ひとつのSDL_cond構造体に対して、複数のスレッドでSDL_CondWaitを呼び出している場合、ひとつのスレッドしか起こしません。

int SDL_CondSignal(SDL_cond* cond)

3.5 SDL_CondBroadcast

ひとつのSDL_cond構造体に対して、複数のスレッドからSDL_CondWaitが呼び出された場合に、SDL_CondBroadcastでスリープしているスレッドを全て起こします。

int SDL_CondBroadcast(SDL_cond* cond)

4 サンプルプログラム

外部変数gNumberをインクリメントするスレッドとデクリメントするスレッドが動作するサンプルプログラムです。gNumberの読み込みと更新をアトミックにする為、SDL_mutex構造体を使います。ただし、gNumberの上限値を下限値を超えないようにする為、SDL_cond構造体を用いています(SDL_semaphore構造体を使用して、SDL_mutex構造体のロックとアンロックを呼び出し元で実行しても良いです)。

#include <SDL.h>
#include <iostream>

#define NUMBER_UPPER_LIMIT (128)
#define NUMBER_UNDER_LIMIT (-1)

#define SDL_PrintError(name)                                    \
  do {                                                          \
    std::cerr << #name << ": " << SDL_GetError() << std::endl;  \
  } while (0)

/** NOTE: Number which is increment and decrement. */
static int gNumber = 0;

/** NOTE: Mutex for thread-safe with gNumber. */
static SDL_mutex *gMutex = nullptr;;

/** NOTE: Condition for waiting gNumber is under 10. */
static SDL_cond *gOverflowCond = nullptr;

/** NOTE: Condition for waiting gNumber is over 0. */
static SDL_cond *gUnderflowCond = nullptr;

static bool initialize()
{
  if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
    SDL_PrintError(SDL_Init);
    return false;
  }

  gMutex = SDL_CreateMutex();
  if (gMutex == nullptr)
    goto err1;

  gOverflowCond = SDL_CreateCond();
  if (gOverflowCond == nullptr)
    goto err2;

  gUnderflowCond = SDL_CreateCond();
  if (gUnderflowCond == nullptr)
    goto err3;

  return true;

 err3:
  SDL_DestroyCond(gOverflowCond);
 err2:
  SDL_DestroyMutex(gMutex);
 err1:
  SDL_Quit();
  return false;
}

static void finalize()
{
  SDL_DestroyCond(gUnderflowCond);
  SDL_DestroyCond(gOverflowCond);
  SDL_DestroyMutex(gMutex);
  SDL_Quit();
}

/** NOTE: Increment number thread. */
static int inc(void *)
{
  for (int n = 0; n < 4096; ++n) {
    SDL_LockMutex(gMutex);

    /** NOTE: Wait gNumber be under upper limit with unlock mutex. */
    if (gNumber == NUMBER_UPPER_LIMIT) {
      std::cout << "Overflow" << std::endl;
      SDL_CondWait(gOverflowCond, gMutex);
    }

    /** NOTE: Wake waiting thread. */
    if (gNumber++ == NUMBER_UNDER_LIMIT)
      SDL_CondSignal(gUnderflowCond);

    SDL_UnlockMutex(gMutex);
  }

  return 0;
}

/** NOTE: Decrement number thread. */
static int dec(void *)
{
  for (int n = 0; n < 4096; ++n) {
    SDL_LockMutex(gMutex);

    /** NOTE: Wait gNumber be over under limit with unlock mutex. */
    if (gNumber == NUMBER_UNDER_LIMIT) {
      std::cout << "Underflow" << std::endl;
      SDL_CondWait(gUnderflowCond, gMutex);
    }

    /** NOTE: Wake waiting thread. */
    if (gNumber-- == NUMBER_UPPER_LIMIT)
      SDL_CondSignal(gOverflowCond);

    SDL_UnlockMutex(gMutex);
  }

  return 0;
}

int main(int argc, char *argv[])
{
  SDL_Thread *incThread;
  SDL_Thread *decThread;
  int ret;

  if (!initialize())
    return 1;

  incThread = SDL_CreateThread(inc, "Inc", nullptr);
  if (incThread == nullptr) {
    SDL_PrintError(SDL_CreateThread);
    goto err1;
  }

  decThread = SDL_CreateThread(dec, "Dec", nullptr);
  if (decThread == nullptr) {
    SDL_PrintError(SDL_CreateThread);
    goto err2;
  }

  SDL_WaitThread(incThread, &ret);
  SDL_WaitThread(decThread, &ret);
  std::cout << "gNumber = " << gNumber << std::endl;

  finalize();
  return 0;

 err2:
  SDL_DetachThread(incThread);
 err1:
  finalize();
  return 1;
}

実行結果は以下のようになります。スレッドの動作はタイミングによって変わるので、OverflowとUnderflowの文字列表示は不定です。

$ ./a.out
Overflow
Underflow
Underflow
Underflow
Underflow
gNumber = 0
$ ./a.out
Overflow
Underflow
Overflow
Underflow
Overflow
Underflow
Overflow
Underflow
Overflow
Underflow
Overflow
Underflow
Overflow
Underflow
Overflow
Overflow
Underflow
Overflow
gNumber = 0