前回のブログのスレッドの概念で、スレッドの多くの利点について話しましたが、合理的なアクセス制御がないので、スレッドを制御する方法を見てみましょう〜
POSIXスレッドライブラリ
Linuxには実際のスレッドがないことがわかっています。CPUが認識するのはプロセスエンティティのみであるため、スレッドは無になります。つまり、Linuxはスレッドを管理するための一連のシステムコールを提供しません。オペレーティングシステムは構造を提供しないため、スレッドを管理するための一連のインターフェイスを実装します。
POSIXは、ポータブルオペレーティングシステムインターフェイスの略です。ここで紹介する関数もPOSIX標準に従います。これらの関数は、ユーザーレベルの呼び出しのセットであり、関連する関数を含む完全なシリーズを構成します。ほとんどの名前は「pthread_」で始まります。はい、関数ライブラリを使用するには、ヘッダーファイル<pthread.h>を導入する必要があります。ここで最も重要な点は、これらのスレッド関数ライブラリをリンクするときにコンパイル時に「-pthread」オプションを追加する必要があることです。- lリンクを意味しますサードパーティのライブラリ。
1.スレッドの作成
パラメータ1:出力パラメータであるスレッドのIDを返します。
パラメータ2:nullptrを使用してデフォルトの属性を表すスレッドの属性を設定します。
パラメータ3:子スレッドに関数のアドレス(関数の名前)を実行させます。
パラメータ4 :子スレッドは関数を実行します
戻り値のパラメーター:成功は0を返し、失敗はエラーコードを返します。
試してみるスレッドを作成しましょう。
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void *handle(void *arg)
{
while(1)
{
cout<<"i am new thread"<<endl;
sleep(1);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,handle,(void*)"thread 1");
while(1)
{
cout<<"i am main thread"<<endl;
sleep(2);
}
return 0;
}
作成したスレッドを確認してください。
ps -ajL
-Lオプションは、スレッド関連の情報を表示できます
- 上の図から、同じPIDが8974のプロセスが2つあることがわかります。これは、これら2つのプロセスが実際のプロセスではないことを意味します。2つのLWP(軽量プロセス)は異なり、2つが異なる軽量プロセスまたはスレッドであることを示しています。実際、タスクをスケジューリングするCPUの単位はlwpであり、前述のプロセスのスケジューリング単位はシングルスレッドプロセスのPIDです。
- 各ユーザーモードスレッドは、カーネル内のスケジューリングエンティティに対応します。したがって、OSはスレッドを記述して編成し、スレッドを管理する必要があります。そのため、スレッドには独自のプロセス記述子(task_struct構造)、スレッドローカルストレージ、およびスレッドスタックもあります。 、および構造の開始アドレスは、スレッド構造を説明する特定のアドレスを見つけることです。したがって、スレッドを一意に識別するために使用される整数変数tidは、これらのスレッド説明の最初のアドレスであり、LWPはスケジューリングに使用されるIDです。
- 注意:スレッドとプロセスは異なり、プロセスには親プロセスの概念があります。ただし、スレッドグループでは、すべてのスレッドがピアです。つまり、同じプロセス内のスレッドはスレッドグループと呼ばれ、階層関係はなく、メインスレッドと新しいスレッドのみがあります。
スレッドの終了
プロセスを終了せずにスレッドを終了するには、次の3つの方法があります。
- スレッド関数から戻ります。メインスレッドのリターンの場合、これはメイン関数のexitを呼び出すことと同じです。
- スレッドはpthread_exitを呼び出してそれ自体を終了できます。
- スレッドはpthread_cancel関数を呼び出して、スレッドグループ内の他のスレッドを終了できます。通常、メインスレッドが他の新しいスレッドをキャンセルするシナリオで使用されます。
ここでの詳細の1つは、メインスレッドで子スレッドをキャンセルする場合、メインスレッドがスケジュールされた後に子スレッドがキャンセルされていることを確認する必要があることです。そうしないと、正しい結果が得られません。子スレッドが正しくキャンセルされると、結合関数のポインターは-1を指します。
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void* handle(void *arg)
{
int i=3;
while(i--)
{
cout<<"i am new"<<endl;
}
pthread_exit((void*)2);
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,handle,(void*)"thread 1");
void* status;
pthread_join(tid,&status);
cout<<(int*)status<<endl;
return 0;
}
スレッド待機
- メインスレッドが待機している理由:
メモリリークを防ぎ、
メインスレッドが新しいスレッドの終了ステータスを取得し
、スレッドの終了シーケンスの同期を確保します
- 待機機能
最初のパラメーターは待機中のスレッドのIDであり、2番目のパラメーターはスレッドの戻り値を指すポインターを指します。
関数呼び出しは成功した場合は0を返し、失敗した場合はエラーコードを返します。
- この関数を呼び出すスレッドは中断され、IDがthreadのスレッドが終了するまで待機します。したがって、スレッドの終了方法が異なると、結合によって取得される終了状態も異なります。
スレッド出口の詳細を次に示します。結合関数の2番目のパラメーターは、スレッド出口の終了コードを取得することです。これは単なる終了コードであり、プロセスが待機しているときとは異なり、プロセスが終了するかどうかを判断できます。シグナル、スレッドはプロセスのブランチであるため、スレッドが異常である限り、プロセスは終了するため、例外コードを取得することはできず、終了コード
1のみを取得できます。スレッドスレッドがリターンを介して戻る場合、 retvalが指すユニットは、関数の戻り値を格納します。
2.他のスレッドがpthread_cancel関数を呼び出したためにスレッドスレッドが異常終了した場合、定数PTHREADCANLELEDがretvalが指すユニットに格納されます。
3.スレッドスレッドがpthread_exit関数を呼び出して終了する場合、retvalが指すユニットは、exit関数に渡されたパラメーターを格納します。
4.スレッドの終了ステータスに関心がない場合は、NULを2番目のパラメーターに直接渡します。
- テストコード
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void* handle(void* arg)
{
int i=3;
while(i--)
cout<<"i am new"<<endl;
return (void*)1;
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,handle,(void*)"thread 1");
void* status;
pthread_join(tid,&status);
cout<<(int*)status<<endl;
return 0;
}
スレッドID
- スレッドIDの取得方法
- スレッドの前では、プロセスはカーネル内のプロセス記述子に対応し、プロセスIDに対応します。しかし、スレッドの概念の導入後、状況は変化しました。ユーザープロセスはN個のユーザーモードスレッドを管理します。独立したスケジューリングエンティティとして、各スレッドはカーネルモードで独自のプロセス記述子を持ち、プロセスとカーネルの記述子を持ちます。これは1:Nの関係になります。POSIX標準では、getpid関数を呼び出すときに、プロセス内のすべてのスレッドが同じプロセスIDを返す必要があります。上記の問題を解決するにはどうすればよいですか。
- Linuxカーネルでは、マルチスレッドプロセスはスレッドグループと呼ばれ、スレッドグループ内の各スレッドには、カーネル内のそれに対応するプロセス記述子があります。表面のプロセス記述子構造のpidはプロセスIDに対応しますが、実際にはそうではなく、最初のスレッドであるメインスレッドのIDに対応します。プロセス記述のtgidはスレッドグループIDを意味します。値に対応するのはユーザーレベルのプロセスIDです。
- したがって、実際には、表示されるプロセスpidはtest_structのtgidに対応し、表示されるlwpはtask_structのpidに対応します。次に、task_structの構造を見てみましょう。
struct task_struct {
...
pid_t pid;
pid_t tgid;
...
struct task_struct *group_leader;//主线程
...
struct list_head thread_group;//用来描述一个线程组的链表
...
};
次回ユーザーモードでgitpidを呼び出すと、システムが実際にtest_structのtgidを返したことがわかります。LinuxではGittidもスレッドIDを返すために提供されていますが、このシステムコールはカプセル化されておらず、使用するのにあまり便利ではありません。
糸の分離
デフォルトでは、新しく作成されたスレッドは参加可能です。スレッドが終了した後、そのスレッドでpthread_join操作を実行する必要があります。そうしないと、リソースを解放できず、メモリリークが発生します。の戻り値を気にしない場合スレッド、結合は負担なので、子スレッドを分離してスレッドリソースを自動的に解放することを選択できます。スレッドは自分自身を分離することも、他の人に分離を支援させることもできます。
注意:スレッドは切り離された後待つことができません
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void* handle(void* arg)
{
pthread_detach(pthread_self());
int i=3;
while(i--)
cout<<"i am new"<<endl;
pthread_cancel(pthread_self());
return (void*)1;
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,handle,(void*)"thread 1");
void* status;
int ret=pthread_join(tid,&status);
cout<<(int*)status<<":"<<ret<<endl;
return 0;
}
総括する:
Linuxで使用されるスレッド制御関数は、実際にはユーザーレベルの関数のグループです。これらの関数の使用法を習得できる限り、スレッドの制御は非常に簡単です。