マルチスレッド

 

1. 定義

プロセス: 実行中の一つのアプリケーションプログラム

スレッド: プロセスを構成する並列実行可能な関数 (一つ、あるいは複数)

 

2. マルチスレッドの使用例

複数のネットワーク接続を使う場合の、個々の通信モジュール

送信側: キャプチャ、エンコード、送信モジュール (オーディオ、ビデオ毎)

受信側: 再生、デコード、受信モジュール (オーディオ、ビデオ毎)

 

3. 簡単なマルチスレッドプログラム

以下のプログラムでは、一つのスレッドを新しく生成し、計二つのスレッドで Main, Thread を交互に表示させている (0.5秒間隔)。

Windows版: CreateThread 関数

#include <stdio.h>
#include <windows.h>

/* スレッドで生成される関数 */
void thread(void);

void 
main() {
   /* スレッド用パラメータ */
   HANDLE handle;
   int id;

   /* スレッドの生成 */
   handle = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) thread, NULL, 0, &id);

   /* ループ */
   while(1) {
      printf("Main\n");
      Sleep(1000);
   }
}

void 
thread(void)
{
   /* ループ */
   Sleep(500);
   while(1) {
      printf("Thread\n");
      Sleep(1000);
   }
}

 

Linux版: pthread 関数 (POSIX)

コンパイル:  gcc ソース名 -lpthread

#include <stdio.h>
#include <pthread.h>

/* スレッドで生成される関数 */
void thread(void);

void
main() {
   /* スレッド用パラメータ */
   int handle;
   pthread_t id;

   /* スレッドの生成 */
   handle = pthread_create(&id, NULL, (void *(*)(void*)) thread, NULL);

   /* ループ */
   while(1) {
      printf("Main\n");
      usleep(1000*1000);
   }
}

void 
thread(void)
{
   /* ループ */
   usleep(500*1000);
   while(1) {
      printf("Thread\n");
      usleep(1000*1000);
   }
}

 

4. 簡単な排他制御プログラム

複数のスレッドが同じメモリ空間、あるいはファイルに同時にアクセスすると、競合・衝突が発生する。この場合、Segmentation Fault となるか、場合によってはシステムがハングアップする。これを防ぐためには、以下の排他制御アルゴリズムのいずれかを使用する。

クリティカルセクション
ミューテックス
セマフォ

 

Windows版: クリティカルセクションの場合

クリティカルセクションでは、EnterCriticalSection で他のスレッドからアクセス権が開放されるのを待ち (ブロッキング)、アクセス権を獲得すると次の文を実行する。必要な処理を実行すると LeaveCriticalSection を呼び出し、アクセス権を待っている他のスレッドに優先権を渡す。

以下のプログラムでは、スレッドを二つ生成し、それぞれのスレッドが配列に0、あるいは1を書込み、配列に書き込まれた値をチェックする。排他制御を行わない場合は、値のチェック中に別のスレッドから値が書込まれるため、プログラムの最後に出力される誤り率の値がゼロではなくなる。これは特に配列のサイズを大きくすればするほど顕著になる。

一方、プログラム中でコメントアウトしている EnterCriticalSection と LeaveCriticalSection を有効にすると、クリティカルセクションによる排他制御が実行され、配列サイズに関係無く誤り率はゼロになる。各自、確認すべし。

#include <stdio.h>
#include <windows.h>

#define SIZE 1000000
#define LOOP 10

char buffer[SIZE];
static int wrong = 0;

/* クリティカルセクション */
CRITICAL_SECTION section;

/* スレッドで生成される関数 */
void thread1(void);
void thread2(void);

void 
main() {
   /* スレッド用パラメータ */
   HANDLE handle[2];
   int id1, id2;

   /* クリティカルセクションの初期化 */
   InitializeCriticalSection(&section);

   /* スレッドの生成 */
   handle[0] = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) thread1, NULL, 0, &id1);
   handle[1] = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) thread2, NULL, 0, &id2);

   /* スレッド、クリティカルセクションの終了、誤り率の表示 */
   WaitForMultipleObjects(2, handle, TRUE, INFINITE);
   DeleteCriticalSection(&section);
   printf("誤り率: %g\n", (double) wrong / (double) (2 * SIZE * LOOP)); 
}

void 
thread1(void)
{
   int i,j;

   /* ループ、誤り回数の測定 */
   for(i=0; i<LOOP; i++) {
//   EnterCriticalSection(&section);
      memset(buffer, 0, SIZE);
      for(j=0; j<SIZE; j++) 
         if(buffer[j] != 0) wrong++;
//   LeaveCriticalSection(&section);
   }
}

void 
thread2(void)
{
   int i,j;

   /* ループ、誤り回数の測定 */
   for(i=0; i<LOOP; i++) {
//   EnterCriticalSection(&section);
      memset(buffer, 1, SIZE);
      for(j=0; j<SIZE; j++) 
         if(buffer[j] != 1) wrong++;
//   LeaveCriticalSection(&section);
   }
}

 

Windows版: ミューテクスの場合

ミューテクスでは、WaitForSingleObject で他のスレッドからアクセス権が開放されるのを待ち (ブロッキング)、アクセス権を獲得すると次の文を実行する。必要な処理を実行すると ReleaseMutex を呼び出し、アクセス権を待っている他のスレッドに優先権を渡す。

下記のプログラム自体はクリティカルセクションの場合とまったく同じ動作をする。

プログラム中でコメントアウトしている WaitForSingleObject と ReleaseMutex を有効にすると、ミューテクスによる排他制御が実行され、誤り率は常にゼロになる。各自、確認すべし。

#include <stdio.h>
#include <windows.h>

#define SIZE 1000000
#define LOOP 10

char buffer[SIZE];
static int wrong = 0;

/* ミューテクス */
HANDLE mutex;

/* スレッドで生成される関数 */
void thread1(void);
void thread2(void);

void 
main() {
   /* スレッド用パラメータ */
   HANDLE handle[2];
   int id1, id2;

   /* ミューテクスの初期化 */
   mutex = CreateMutex(0, FALSE, 0);

   /* スレッドの生成 */
   handle[0] = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) thread1, NULL, 0, &id1);
   handle[1] = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) thread2, NULL, 0, &id2);

   /* スレッド、ミューテクスの終了、誤り率の表示 */
   WaitForMultipleObjects(2, handle, TRUE, INFINITE);
   CloseHandle(mutex);
   printf("誤り率: %g\n", (double) wrong / (double) (2 * SIZE * LOOP)); 
}

void 
thread1(void)
{
   int i,j;

   /* ループ、誤り回数の測定 */
   for(i=0; i<LOOP; i++) {
//   WaitForSingleObject(mutex, INFINITE);
      memset(buffer, 0, SIZE);
      for(j=0; j<SIZE; j++) 
         if(buffer[j] != 0) wrong++;
//   ReleaseMutex(mutex);
   }
}

void 
thread2(void)
{
   int i,j;

   /* ループ、誤り回数の測定 */
   for(i=0; i<LOOP; i++) {
//   WaitForSingleObject(mutex, INFINITE);
      memset(buffer, 1, SIZE);
      for(j=0; j<SIZE; j++) 
         if(buffer[j] != 1) wrong++;
//   ReleaseMutex(mutex);
   }
}

 

Linux版: 準備中