Cプログラミング
 Computer >> コンピューター >  >> プログラミング >> Cプログラミング

C言語で学ぶマルチスレッドプログラミング入門|POSIXスレッド(Pthreads)の基本

マルチスレッドとは何か

マルチスレッドは、マルチタスクの特殊な形態です。マルチタスクとは、コンピュータが2つ以上のプログラムを同時に実行できるようにする機能のことです。マルチタスクには、大きく分けて「プロセスベース」と「スレッドベース」の2種類があります。

プロセスベースのマルチタスクは、複数のプログラムの同時実行を扱います。一方、スレッドベースのマルチタスクは、同一プログラム内の複数の処理単位の同時実行を扱います。

マルチスレッドプログラムは、同時に実行できる2つ以上の部分から構成されます。この各部分は「スレッド」と呼ばれ、それぞれが独立した実行パスを持ちます。

C言語におけるマルチスレッドの位置づけ

C言語自体には、マルチスレッドアプリケーション向けの組み込みサポートは用意されていません。そのため、この機能は完全にオペレーティングシステム(OS)に依存します。

本記事ではLinux OSを前提として、POSIXを使ったマルチスレッドCプログラムの書き方を解説します。POSIXスレッド(Pthreads)は、FreeBSD、NetBSD、GNU/Linux、macOS、Solarisなど、多くのUnix系POSIXシステムで利用できるAPIを提供しています。

スレッドの作成:pthread_create

POSIXスレッドを新しく作成するには、次の関数を使用します。

#include <pthread.h>
pthread_create(thread, attr, start_routine, arg)

pthread_createは新しいスレッドを生成し、実行可能な状態にします。この関数はコード内のどこからでも、何度でも呼び出すことができます。各パラメータの意味は以下の通りです。

パラメータ説明
thread新しく作成されたスレッドの一意な識別子。サブルーチンの戻り値として返される不透明な値です。
attrスレッドの属性を設定するために使われる不透明な属性オブジェクト。属性オブジェクトを指定するか、デフォルト値を使う場合はNULLを渡します。
start_routineスレッドが作成された後に実行するCの関数(ルーチン)。
argstart_routineへ渡す単一の引数。void型ポインタへのキャストにより参照渡しする必要があります。引数を渡さない場合はNULLを指定できます。

1つのプロセスで作成できるスレッドの最大数は、実装(環境)によって異なります。また、一度作成されたスレッドは互いに対等な関係(ピア)にあり、さらに別のスレッドを作成することもできます。スレッド間に暗黙の階層構造や依存関係は存在しません。

スレッドの終了:pthread_exit

POSIXスレッドを終了させるには、次の関数を使用します。

#include <pthread.h>
pthread_exit(status)

pthread_exitは、スレッドを明示的に終了させるために使われます。通常、pthread_exit()は、スレッドが作業を完了し、それ以上存在する必要がなくなった時点で呼び出されます。

重要なポイントとして、main()が自分の作成したスレッドよりも先に終了する場合、main()をpthread_exit()で終了させていれば、他のスレッドは実行を継続できます。逆に、通常のreturnやexit()でmain()が終了すると、すべてのスレッドは自動的に強制終了されてしまいます。

サンプルコード

以下は、5つのスレッドを作成し、それぞれが「Hello World!」メッセージを出力するシンプルなCプログラムの例です。

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

#define NUM_THREADS 5

void *PrintHello(void *threadid) {
    long tid;
    tid = (long)threadid;
    printf("Hello World! Thread ID, %ld\n", tid);
    pthread_exit(NULL);
}

int main () {
    pthread_t threads[NUM_THREADS];
    int rc;
    long i;
    for( i = 0; i < NUM_THREADS; i++ ) {
        printf("main() : creating thread, %ld\n", i);
        rc = pthread_create(&threads[i], NULL, PrintHello, (void *)i);
        if (rc) {
            printf("Error: unable to create thread, %d\n", rc);
            exit(-1);
        }
    }
    pthread_exit(NULL);
}

このプログラムでは、main()がループで5回pthread_create()を呼び出し、そのたびにPrintHello関数を実行する新しいスレッドを生成しています。最後にmain()自身もpthread_exit(NULL)で終了することで、生成済みのスレッドがすべて処理を完了できるようになっています。

コンパイルと実行結果

Pthreadsを使用する場合は、コンパイル時にpthreadライブラリをリンクする必要があります。-lpthreadオプションを付けてコンパイルします。

$gcc test.c -lpthread
$./a.out
main() : creating thread, 0
main() : creating thread, 1
main() : creating thread, 2
main() : creating thread, 3
main() : creating thread, 4
Hello World! Thread ID, 0
Hello World! Thread ID, 1
Hello World! Thread ID, 2
Hello World! Thread ID, 3
Hello World! Thread ID, 4

なお、マルチスレッドプログラムでは、各スレッドの実行順序はOSのスケジューラに委ねられます。そのため、実際の出力順序は実行のたびに異なる場合がある点に注意してください。

  1. Javaのスレッドプールとは?仕組みと実装例をわかりやすく解説

    スレッドプールとはスレッドプール(Thread Pool)とは、あらかじめ初期化された複数のスレッドをまとめて管理する仕組みのことです。基本的な考え方は、アプリケーションの起動時に一定数のスレッドを作成してプールに入れておき、それらのスレッドが仕事を待機するというものです。サーバーがリクエストを受け取ると、利用可能なスレッドがあればプールから1つ取り出して処理を任せます。スレッドはタスクの完了後、破棄されることなくプールへ戻り、次の仕事を待ちます。もしプール内に空きスレッドが存在しない場合は、空きができるまでサーバー側で待機することになります。スレッドプールのメリットタスクごとに新しいスレッド

  2. Javaで匿名クラスを使ってスレッドを作成する方法

    スレッド(Thread)とは、プログラムの他の部分と同時に実行できる処理単位のことです。すべてのJavaプログラムには、少なくとも1つの「メインスレッド」が存在します。このメインスレッドは、Java仮想マシン(JVM)がプログラムの起動時にmain()メソッドを呼び出す際に自動的に作成されるものです。Javaでスレッドを作成する一般的な方法としては、Threadクラスを継承する方法と、Runnableインターフェースを実装する方法の2つがあります。さらに、以下のサンプルコードのように、Threadクラスを明示的に継承したクラスを定義しなくても、匿名クラス(無名クラス)を利用すればその場でスレッ