Linux スレッド シリーズのパート I では、Linux のスレッドに関連するさまざまな側面について説明しました。
この記事では、スレッドの作成方法と識別方法に焦点を当てます。また、基本的なスレッド プログラミングの方法を説明する C プログラムの実際の例も紹介します。
Linux スレッド シリーズ:パート 1、パート 2 (この記事)、パート 3.
スレッドの識別
プロセスがプロセス ID によって識別されるように、スレッドはスレッド ID によって識別されます。しかし興味深いことに、この 2 つの類似点はここで終わります。
- プロセス ID はシステム全体で一意ですが、スレッド ID は単一のプロセスのコンテキストでのみ一意です。
- プロセス ID は整数値ですが、スレッド ID は必ずしも整数値ではありません。構造物かもしれません
- プロセス ID は簡単に印刷できますが、スレッド ID は簡単に印刷できません。
上記の点から、プロセス ID とスレッド ID の違いについてのアイデアが得られます。
スレッド ID は、タイプ「pthread_t」で表されます。すでに説明したように、ほとんどの場合、この型は構造体であるため、2 つのスレッド ID を比較できる関数が必要です。
#include <pthread.h> int pthread_equal(pthread_t tid1, pthread_t tid2);
ご覧のとおり、上記の関数は 2 つのスレッド ID を取り、両方のスレッド ID が等しい場合はゼロ以外の値を返し、そうでない場合はゼロを返します。
スレッドが自身のスレッド ID を知りたい場合、別のケースが発生する可能性があります。この場合、次の関数が目的のサービスを提供します。
#include <pthread.h> pthread_t pthread_self(void);
したがって、関数「pthread_self()」は、スレッドが独自のスレッド ID を出力するために使用されていることがわかります。
ここで、上記の 2 つの機能が必要になる場合について質問します。リンク リストに異なるスレッドのデータが含まれている場合があるとします。リスト内のすべてのノードには、スレッド ID と対応するデータが含まれています。スレッドがリンクされたリストからデータをフェッチしようとするときはいつでも、最初に「pthread_self()」を呼び出して独自の ID を取得し、次にすべてのノードで「pthread_equal()」を呼び出して、ノードにデータが含まれているかどうかを確認します。 .
上記の一般的なケースの例は、マスター スレッドが処理するジョブを取得し、それらをリンク リストにプッシュするケースです。これで、個々のワーカー スレッドがリンク リストを解析し、割り当てられたジョブを抽出します。
スレッドの作成
通常、プログラムが起動してプロセスになると、デフォルト スレッドで起動します。したがって、すべてのプロセスには少なくとも 1 つの制御スレッドがあると言えます。プロセスは、次の関数を使用して追加のスレッドを作成できます:
#include <pthread.h> int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void), void *restrict arg)
上記の関数には 4 つの引数が必要です。最初にそれらについて少し説明しましょう:
- 最初の引数は pthread_t タイプのアドレスです。関数が正常に呼び出されると、アドレスが最初の引数として渡される変数は、新しく作成されたスレッドのスレッド ID を保持します。
- 2 番目の引数には、新しいスレッドに含めたい特定の属性を含めることができます。優先度など
- 3 番目の引数は関数ポインタです。これは、各スレッドが関数で開始し、その関数のアドレスが 3 番目の引数としてここに渡されることに注意してください。これにより、カーネルはどの関数からスレッドを開始するかを認識できます。
- 関数 (そのアドレスは上記の 3 番目の引数で渡されます) もいくつかの引数を受け入れることができるため、これらの引数を void 型へのポインターの形式で渡すことができます。では、なぜ void 型が選ばれたのでしょうか。これは、関数が複数の引数を受け入れる場合、このポインターがこれらの引数を含む構造体へのポインターになる可能性があるためです。
実用的なスレッドの例
以下は、上記で説明した 3 つの関数すべてを使用しようとしたサンプル コードです。
#include<stdio.h> #include<string.h> #include<pthread.h> #include<stdlib.h> #include<unistd.h> pthread_t tid[2]; void* doSomeThing(void *arg) { unsigned long i = 0; pthread_t id = pthread_self(); if(pthread_equal(id,tid[0])) { printf("\n First thread processing\n"); } else { printf("\n Second thread processing\n"); } for(i=0; i<(0xFFFFFFFF);i++); return NULL; } int main(void) { int i = 0; int err; while(i < 2) { err = pthread_create(&(tid[i]), NULL, &doSomeThing, NULL); if (err != 0) printf("\ncan't create thread :[%s]", strerror(err)); else printf("\n Thread created successfully\n"); i++; } sleep(5); return 0; }
したがって、このコードは次のようになります:
- pthread_create() 関数を使用して 2 つのスレッドを作成します
- 両方のスレッドの開始関数は同じままです。
- 関数「doSomeThing()」内で、スレッドは pthread_self() および pthread_equal() 関数を使用して、実行中のスレッドが作成された最初のスレッドか 2 番目のスレッドかを識別します。
- また、同じ関数「doSomeThing()」内で、時間のかかる作業をシミュレートするために for ループが実行されます。
上記のコードを実行すると、次のような出力が得られます:
$ ./threads Thread created successfully First thread processing Thread created successfully Second thread processing
出力に見られるように、最初のスレッドが作成されて処理が開始され、次に 2 番目のスレッドが作成されて処理が開始されます。ここで注意すべき点の 1 つは、スレッドの実行順序が常に固定されているわけではないということです。 OS のスケジューリング アルゴリズムに依存します。
注:この記事の説明全体は、Posix スレッドで行われています。型からわかるように、pthread_t 型は POSIX スレッドを表します。アプリケーションが POSIX スレッドがサポートされているかどうかをテストしたい場合、アプリケーションはコンパイル時のテストにマクロ _POSIX_THREADS を使用できます。 posix API への呼び出しを含むコードをコンパイルするには、コンパイル オプション「-pthread」を使用してください。