[C言語] Pthread_create関数の使い方や引数・戻り値を解説
pthread_create
関数は、POSIXスレッドを作成するために使用されます。この関数は、新しいスレッドを生成し、指定された関数をそのスレッドで実行します。
引数には、スレッド識別子を格納するためのポインタ、スレッド属性を指定するためのポインタ、スレッドで実行する関数へのポインタ、関数に渡す引数へのポインタが含まれます。
戻り値は、成功時に0を返し、エラーが発生した場合はエラー番号を返します。エラー番号は、スレッドの作成に失敗した理由を示します。
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
: スレッド関数に渡す引数。
スレッドの作成手順
- スレッド識別子の宣言: スレッドを識別するための
pthread_t型
の変数を宣言します。 - スレッド属性の設定(必要に応じて): スレッド属性を設定する場合は、
pthread_attr_t型
の変数を宣言し、pthread_attr_init関数
で初期化します。 - スレッドの作成:
pthread_create
関数を呼び出し、スレッドを作成します。 - スレッドの実行: 指定した関数が新しいスレッドで実行されます。
以下は、スレッドを作成するサンプルコードです。
#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_join
やpthread_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
を用いることで、C言語でのマルチスレッドプログラミングが可能となり、プログラムの効率を大幅に向上させることができます。
これを機に、実際のプログラムでスレッドを活用し、より高度な並列処理を試してみてはいかがでしょうか。