C言語で並行処理を始めるためのチュートリアル:コンカレンシーサポートライブラリとサンプルコード


主なコンカレンシーサポートライブラリ

  • Cilk Plus
    Cilk Plusは、MIT Computer Science and Artificial Intelligence Laboratory (CSAIL) が開発した並行処理ライブラリです。タスク盗みと呼ばれる手法を使用して、効率的な並行処理を実現します。
  • Intel Threading Building Blocks (TBB)
    TBBは、Intel社が提供するオープンソースのライブラリで、スケーラブルで効率的な並行処理アプリケーションの開発を支援します。スケジューリング、同期、タスク管理などの機能を提供します。
  • OpenMP
    OpenMPは、マルチコアCPUや分散コンピュータ上で並行処理を容易にする、オープンソースのライブラリです。ディレクティブと呼ばれる指示を使用して、ループの並列化やスレッド間のデータ共有を制御できます。
  • POSIX Threads (pthreads)
    pthreadsは、IEEEが策定した標準的なスレッドライブラリです。多くのオペレーティングシステムでサポートされており、ポータブルな並行処理プログラミングに適しています。

コンカレンシーサポートライブラリを使用する利点

  • スケーラビリティの向上
    並行処理アプリケーションは、より多くのコアやプロセッサで実行するようにスケールさせることができます。
  • コードの簡素化
    コンカレンシーサポートライブラリを使用すると、複雑な並行処理タスクをより簡単に記述できます。
  • パフォーマンスの向上
    複数のタスクを並行して実行することで、プログラム全体のパフォーマンスを向上させることができます。

コンカレンシーサポートライブラリを使用する際の注意点

  • パフォーマンスのオーバーヘッド
    コンカレンシーサポートライブラリを使用すると、スレッドや同期プリミティブのオーバーヘッドが発生する可能性があります。
  • データ競合
    複数のタスクが同じデータにアクセスする場合、データ競合が発生する可能性があります。データ競合は、予期しない結果やプログラムの破損につながる可能性があります。
  • デッドロック
    適切な同期を使用しないと、デッドロックが発生する可能性があります。デッドロックとは、2つ以上のタスクが互いに待機し、どちらも先に進めなくなる状態です。


例:POSIX Threads (pthreads) を使用したスレッドの作成と同期

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

void *thread_function(void *arg) {
  printf("Thread says: %s\n", (char *)arg);
  return NULL;
}

int main() {
  pthread_t thread1, thread2;
  char message1[] = "Hello World";
  char message2[] = "Goodbye World";

  pthread_create(&thread1, NULL, thread_function, (void *)message1);
  pthread_create(&thread2, NULL, thread_function, (void *)message2);

  pthread_join(thread1, NULL);
  pthread_join(thread2, NULL);

  return 0;
}

このコードを実行すると、次の出力が表示されます。

Thread says: Hello World
Thread says: Goodbye World

例:OpenMP を使用したループの並列化

この例では、OpenMPを使用して配列の要素を合計するループを並列化する方法を示します。

#include <stdio.h>
#include <omp.h>

int main() {
  int i, sum = 0;
  int n = 10;
  int array[n];

  for (i = 0; i < n; i++) {
    array[i] = i + 1;
  }

  #pragma omp parallel for
  for (i = 0; i < n; i++) {
    sum += array[i];
  }

  printf("The sum is: %d\n", sum);
  return 0;
}
The sum is: 55

例:Intel TBB を使用したタスクスケジューリング

この例では、Intel TBBを使用して2つのタスクをスケジューリングする方法を示します。

#include <tbb/task.h>
#include <stdio.h>

void task1() {
  printf("Task 1 is running\n");
}

void task2() {
  printf("Task 2 is running\n");
}

int main() {
  tbb::task::enqueue(task1);
  tbb::task::enqueue(task2);

  tbb::task::execute_all();

  return 0;
}
Task 1 is running
Task 2 is running

この例では、Cilk Plusを使用して再帰関数を並行化する方法を示します。

#include <cilk.h>
#include <stdio.h>

int factorial(int n) {
  if (n == 1) {
    return 1;
  } else {
    return n * cilk_spawn factorial(n - 1);
  }
}

int main() {
  int result = factorial(10);
  printf("The factorial of 10 is: %d\n", result);
  return 0;
}
The factorial of 10 is: 3628800


手動同期とスレッド

  • これは、ライブラリを使用するよりも低レベルな制御を提供しますが、より複雑でエラーが発生しやすい可能性があります。
  • スレッドを手動で作成し、同期プリミティブ(ミューテックス、セマフォア、条件変数など)を使用して、データアクセスとコード実行を同期できます。

言語固有の並行処理機能

  • これらの機能は、特定の言語で並行処理をプログラミングするためのより軽量で高効率な方法を提供できますが、C言語では使用できません。
  • C++やC#などの言語には、並行処理をサポートする言語固有の機能が組み込まれています。

メッセージパッシング

  • これは、密結合度の低い並行処理アプリケーションに適していますが、より複雑なデータ構造の共有には適していない場合があります。
  • タスク間通信にメッセージパッシングを使用できます。

イベント駆動型プログラミング

  • これは、GUIアプリケーションやネットワークアプリケーションに適していますが、他の並行処理パラダイムほど汎用性が高くない場合があります。
  • イベント駆動型プログラミングを使用して、イベントが発生したときにタスクを実行できます。

代替方法を選択する際の考慮事項

  • ツールとライブラリのサポート: 使用しているツールとライブラリが、選択した代替方法をサポートしていることを確認する必要があります。
  • 開発者のスキル: 開発者のコンカレンシーサポートライブラリ、手動同期、言語固有の並行処理機能、メッセージパッシング、イベント駆動型プログラミングに関するスキルを考慮する必要があります。
  • アプリケーションのニーズ: アプリケーションのニーズを慎重に評価する必要があります。ワークロードの種類、必要なパフォーマンスレベル、許容される複雑さのレベルなどを考慮する必要があります。

コンカレンシーサポートライブラリの代替方法の例

以下に、コンカレンシーサポートライブラリの代替方法の例をいくつか示します。

  • Cilk Plus の代替方法
    イベント駆動型プログラミング
  • Intel TBB の代替方法
    メッセージパッシング
  • OpenMP の代替方法
    C++の並行処理機能
  • pthreads の代替方法
    手動同期とスレッド