[C言語] Pthread_create関数の使い方や引数・戻り値を解説

pthread_create関数は、POSIXスレッドを作成するために使用されます。この関数は、新しいスレッドを生成し、指定された関数をそのスレッドで実行します。

引数には、スレッド識別子を格納するためのポインタ、スレッド属性を指定するためのポインタ、スレッドで実行する関数へのポインタ、関数に渡す引数へのポインタが含まれます。

戻り値は、成功時に0を返し、エラーが発生した場合はエラー番号を返します。エラー番号は、スレッドの作成に失敗した理由を示します。

この記事でわかること
  • Pthread_create関数の基本的な使い方とそのシグネチャ
  • スレッドの作成手順と終了時のリソース解放方法
  • 関数の引数と戻り値の詳細
  • マルチスレッドによる並列処理やスレッドプールの実装例
  • スレッドの競合状態やデッドロックの回避方法

目次から探す

Pthread_create関数とは

pthread_create関数は、POSIXスレッド(Pthreads)ライブラリの一部であり、C言語でマルチスレッドプログラミングを行う際に使用されます。

この関数は、新しいスレッドを作成し、指定された関数をそのスレッドで実行するために使用されます。

スレッドは、プロセス内で独立して実行される軽量な実行単位であり、並列処理を実現するために重要な役割を果たします。

pthread_create関数を使用することで、複数のタスクを同時に実行し、プログラムのパフォーマンスを向上させることが可能です。

スレッドの作成には、スレッド識別子、スレッド属性、実行する関数、およびその関数に渡す引数が必要です。

これにより、柔軟なスレッド管理が可能となります。

Pthread_create関数の使い方

関数のシグネチャ

pthread_create関数のシグネチャは以下の通りです。

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
  • pthread_t *thread: 新しく作成されるスレッドの識別子を格納するためのポインタ。
  • const pthread_attr_t *attr: スレッドの属性を指定するためのポインタ。

デフォルト属性を使用する場合はNULLを指定。

  • void *(*start_routine)(void *): スレッドが実行する関数へのポインタ。
  • void *arg: スレッド関数に渡す引数。

スレッドの作成手順

  1. スレッド識別子の宣言: スレッドを識別するためのpthread_t型の変数を宣言します。
  2. スレッド属性の設定(必要に応じて): スレッド属性を設定する場合は、pthread_attr_t型の変数を宣言し、pthread_attr_init関数で初期化します。
  3. スレッドの作成: pthread_create関数を呼び出し、スレッドを作成します。
  4. スレッドの実行: 指定した関数が新しいスレッドで実行されます。

以下は、スレッドを作成するサンプルコードです。

#include <pthread.h>
#include <stdio.h>
void* printMessage(void* arg) {
    printf("スレッドからのメッセージ: %s\n", (char*)arg);
    return NULL;
}
int main() {
    pthread_t thread;
    const char* message = "こんにちは、世界!";
    // スレッドの作成
    if (pthread_create(&thread, NULL, printMessage, (void*)message) != 0) {
        perror("スレッドの作成に失敗しました");
        return 1;
    }
    // スレッドの終了を待機
    pthread_join(thread, NULL);
    return 0;
}
スレッドからのメッセージ: こんにちは、世界!

このコードは、新しいスレッドを作成し、printMessage関数を実行します。

スレッドはメインスレッドとは独立して動作し、指定されたメッセージを出力します。

スレッドの終了とリソースの解放

スレッドが終了すると、pthread_join関数を使用してメインスレッドがその終了を待機し、リソースを解放します。

pthread_joinは、指定したスレッドが終了するまでブロックし、スレッドの終了ステータスを取得することができます。

スレッドが終了した後、関連するリソースは自動的に解放されますが、スレッド属性を使用した場合は、pthread_attr_destroyを呼び出して属性オブジェクトを解放する必要があります。

Pthread_create関数の引数

スレッド識別子(pthread_t)

pthread_tは、スレッドを一意に識別するためのデータ型です。

pthread_create関数を呼び出す際に、この識別子を格納するための変数を渡します。

スレッドが正常に作成されると、この変数には新しいスレッドの識別子が設定されます。

この識別子は、スレッドの管理や操作(例:pthread_joinpthread_cancel)に使用されます。

スレッド属性(pthread_attr_t)

pthread_attr_tは、スレッドの属性を指定するためのデータ型です。

スレッド属性を設定することで、スレッドの動作をカスタマイズできます。

例えば、スレッドのスタックサイズやスケジューリングポリシーを設定することが可能です。

デフォルトの属性を使用する場合は、pthread_create関数のこの引数にNULLを指定します。

カスタム属性を使用する場合は、pthread_attr_initで初期化し、必要な属性を設定した後、pthread_createに渡します。

スレッド関数(void* (start_routine)(void))

start_routineは、新しいスレッドで実行される関数へのポインタです。

この関数は、void*型の引数を1つ受け取り、void*型の値を返す必要があります。

スレッドが開始されると、この関数が実行されます。

関数の戻り値は、スレッドの終了ステータスとして使用され、pthread_joinを使用して取得できます。

スレッド関数の引数(void* arg)

argは、スレッド関数に渡される引数です。

この引数は、void*型であるため、任意のデータ型を渡すことができます。

通常、スレッド関数が必要とするデータを指すポインタを渡します。

スレッド関数内でこの引数を適切な型にキャストして使用します。

これにより、スレッドごとに異なるデータを処理することが可能になります。

以下は、これらの引数を使用したサンプルコードです。

#include <pthread.h>
#include <stdio.h>
void* threadFunction(void* arg) {
    int* num = (int*)arg;
    printf("スレッドで受け取った数値: %d\n", *num);
    return NULL;
}
int main() {
    pthread_t thread;
    int number = 42;
    // スレッドの作成
    if (pthread_create(&thread, NULL, threadFunction, (void*)&number) != 0) {
        perror("スレッドの作成に失敗しました");
        return 1;
    }
    // スレッドの終了を待機
    pthread_join(thread, NULL);
    return 0;
}
スレッドで受け取った数値: 42

このコードでは、整数42をスレッド関数に渡し、スレッド内でその値を出力しています。

pthread_createの引数を適切に設定することで、スレッドの動作を制御できます。

Pthread_create関数の戻り値

成功時の戻り値

pthread_create関数が正常にスレッドを作成した場合、戻り値は0です。

この戻り値は、スレッドの作成が成功したことを示します。

スレッド識別子は、関数の第1引数として渡されたpthread_t型の変数に格納されます。

この識別子を使用して、スレッドの管理や操作を行うことができます。

エラー時の戻り値とエラーコード

pthread_create関数が失敗した場合、戻り値は0以外のエラーコードになります。

これらのエラーコードは、スレッドの作成に失敗した理由を示します。

主なエラーコードは以下の通りです。

スクロールできます
エラーコード説明
EAGAINシステムリソースが不足しているため、新しいスレッドを作成できない。
EINVAL無効なスレッド属性が指定された。
EPERMスレッドの属性で指定された操作を実行する権限がない。

エラーハンドリングの方法

エラーハンドリングは、pthread_create関数の戻り値を確認することで行います。

戻り値が0以外の場合、エラーが発生したことを示しているため、適切な対処を行う必要があります。

以下は、エラーハンドリングを行うサンプルコードです。

#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
void* threadFunction(void* arg) {
    printf("スレッドが実行されました\n");
    return NULL;
}
int main() {
    pthread_t thread;
    int result;
    // スレッドの作成
    result = pthread_create(&thread, NULL, threadFunction, NULL);
    if (result != 0) {
        fprintf(stderr, "スレッドの作成に失敗しました: %s\n", strerror(result));
        return 1;
    }
    // スレッドの終了を待機
    pthread_join(thread, NULL);
    return 0;
}

このコードでは、pthread_createの戻り値をresult変数に格納し、0以外の場合にエラーメッセージを出力しています。

strerror関数を使用して、エラーコードに対応するエラーメッセージを取得し、ユーザーにわかりやすく表示しています。

エラーハンドリングを適切に行うことで、プログラムの信頼性を向上させることができます。

Pthread_create関数の応用例

マルチスレッドによる並列処理

マルチスレッドによる並列処理は、複数のスレッドを使用して同時に複数のタスクを実行することで、プログラムのパフォーマンスを向上させる手法です。

例えば、大量のデータを処理する場合、データを複数の部分に分割し、それぞれを別々のスレッドで処理することで、処理時間を短縮できます。

#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS 4
void* processData(void* arg) {
    int thread_id = *(int*)arg;
    printf("スレッド %d がデータを処理中\n", thread_id);
    // データ処理のコードをここに記述
    return NULL;
}
int main() {
    pthread_t threads[NUM_THREADS];
    int thread_ids[NUM_THREADS];
    for (int i = 0; i < NUM_THREADS; i++) {
        thread_ids[i] = i;
        pthread_create(&threads[i], NULL, processData, (void*)&thread_ids[i]);
    }
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }
    return 0;
}

このコードは、4つのスレッドを作成し、それぞれがデータを処理する例です。

スレッドプールの実装

スレッドプールは、一定数のスレッドを事前に作成し、タスクが発生するたびにスレッドを再利用する手法です。

これにより、スレッドの作成と破棄のオーバーヘッドを削減し、効率的なリソース管理が可能になります。

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define POOL_SIZE 4
void* worker(void* arg) {
    printf("スレッドプールのスレッドがタスクを実行中\n");
    // タスクの実行コードをここに記述
    return NULL;
}
int main() {
    pthread_t pool[POOL_SIZE];
    for (int i = 0; i < POOL_SIZE; i++) {
        pthread_create(&pool[i], NULL, worker, NULL);
    }
    for (int i = 0; i < POOL_SIZE; i++) {
        pthread_join(pool[i], NULL);
    }
    return 0;
}

このコードは、スレッドプールを使用してタスクを実行する例です。

スレッド間通信の実装

スレッド間通信は、スレッド間でデータを共有し、協調して作業を行うための手法です。

一般的には、共有メモリやメッセージキューを使用して実現します。

#include <pthread.h>
#include <stdio.h>
int shared_data = 0;
pthread_mutex_t mutex;
void* increment(void* arg) {
    pthread_mutex_lock(&mutex);
    shared_data++;
    printf("スレッドがデータをインクリメント: %d\n", shared_data);
    pthread_mutex_unlock(&mutex);
    return NULL;
}
int main() {
    pthread_t threads[2];
    pthread_mutex_init(&mutex, NULL);
    for (int i = 0; i < 2; i++) {
        pthread_create(&threads[i], NULL, increment, NULL);
    }
    for (int i = 0; i < 2; i++) {
        pthread_join(threads[i], NULL);
    }
    pthread_mutex_destroy(&mutex);
    return 0;
}

このコードは、2つのスレッドが共有データをインクリメントする例です。

スレッドの同期と排他制御

スレッドの同期と排他制御は、複数のスレッドが同時に共有リソースにアクセスする際に、データの整合性を保つための手法です。

pthread_mutex_tを使用して、クリティカルセクションを保護します。

#include <pthread.h>
#include <stdio.h>
int counter = 0;
pthread_mutex_t lock;
void* incrementCounter(void* arg) {
    pthread_mutex_lock(&lock);
    counter++;
    printf("カウンターの値: %d\n", counter);
    pthread_mutex_unlock(&lock);
    return NULL;
}
int main() {
    pthread_t threads[5];
    pthread_mutex_init(&lock, NULL);
    for (int i = 0; i < 5; i++) {
        pthread_create(&threads[i], NULL, incrementCounter, NULL);
    }
    for (int i = 0; i < 5; i++) {
        pthread_join(threads[i], NULL);
    }
    pthread_mutex_destroy(&lock);
    return 0;
}

このコードは、5つのスレッドが同じカウンターをインクリメントする際に、pthread_mutex_tを使用して排他制御を行う例です。

これにより、データの競合を防ぎ、正しい結果を得ることができます。

Pthread_create関数を使う際の注意点

スレッドの競合状態

スレッドの競合状態は、複数のスレッドが同時に共有リソースにアクセスし、予期しない結果を引き起こす状況を指します。

例えば、2つのスレッドが同時に変数を更新しようとすると、データの整合性が失われる可能性があります。

これを防ぐために、pthread_mutex_tなどの同期機構を使用して、クリティカルセクションを保護する必要があります。

適切な排他制御を行うことで、競合状態を回避し、データの一貫性を保つことができます。

デッドロックの回避

デッドロックは、複数のスレッドが互いにリソースを待ち続けることで、システムが停止する状態です。

デッドロックを回避するためには、以下のような戦略を採用することが重要です。

  • リソースの取得順序を統一する: すべてのスレッドが同じ順序でリソースを取得するように設計します。
  • タイムアウトを設定する: リソースの取得に時間制限を設け、タイムアウトが発生した場合はリソースを解放して再試行します。
  • デッドロック検出アルゴリズムを使用する: デッドロックの可能性を検出し、適切な対処を行うアルゴリズムを実装します。

スレッドの優先度とスケジューリング

スレッドの優先度とスケジューリングは、スレッドの実行順序や実行時間に影響を与える要素です。

POSIXスレッドでは、スレッドの優先度を設定することができますが、すべてのシステムでサポートされているわけではありません。

スレッドの優先度を設定する際は、以下の点に注意する必要があります。

  • リアルタイムスケジューリング: リアルタイムスケジューリングポリシーを使用する場合、スレッドの優先度を適切に設定し、システム全体のパフォーマンスに影響を与えないようにします。
  • 優先度の逆転を防ぐ: 低優先度のスレッドが高優先度のスレッドをブロックする状況を避けるため、優先度継承プロトコルを使用することが推奨されます。
  • システム依存性: スレッドのスケジューリングはシステム依存であるため、異なるプラットフォームでの動作を確認し、必要に応じて調整します。

これらの注意点を考慮することで、pthread_create関数を使用したマルチスレッドプログラミングの信頼性と効率性を向上させることができます。

よくある質問

Pthread_create関数でスレッドが作成されないのはなぜ?

pthread_create関数でスレッドが作成されない場合、いくつかの原因が考えられます。

まず、システムリソースが不足している可能性があります。

この場合、エラーコードEAGAINが返されます。

次に、スレッド属性が無効である場合、エラーコードEINVALが返されることがあります。

また、スレッドの作成に必要な権限が不足している場合、EPERMが返されます。

これらのエラーコードを確認し、適切な対処を行うことが重要です。

スレッドの終了を確認する方法は?

スレッドの終了を確認するには、pthread_join関数を使用します。

この関数は、指定したスレッドが終了するまでブロックし、スレッドの終了ステータスを取得することができます。

pthread_joinを呼び出すことで、スレッドが正常に終了したかどうかを確認し、必要に応じてリソースを解放することができます。

例:pthread_join(thread, NULL);

スレッドの数に制限はあるのか?

スレッドの数には、システムや環境によって異なる制限があります。

一般的に、システムのリソース(メモリやCPU)に依存しており、無制限にスレッドを作成することはできません。

POSIXスレッドの実装によっては、PTHREAD_THREADS_MAXという定数で最大スレッド数が定義されていることがありますが、これは必ずしもすべてのシステムで適用されるわけではありません。

スレッドを大量に作成する場合は、システムのリソースを考慮し、適切なスレッド管理を行うことが重要です。

まとめ

この記事では、pthread_create関数の基本的な使い方から応用例までを詳しく解説しました。

pthread_createを用いることで、C言語でのマルチスレッドプログラミングが可能となり、プログラムの効率を大幅に向上させることができます。

これを機に、実際のプログラムでスレッドを活用し、より高度な並列処理を試してみてはいかがでしょうか。

  • URLをコピーしました!
目次から探す