目次
(4) LinuxやWindowsなど他のOSでのスレッドの比較
2. ページテーブルの理解 - 仮想アドレスと物理アドレス間の変換
ps -aL (all light) は、すべての軽量プロセスを表示します。
1. スレッドが異常な場合はどうすればよいですか? - スレッドの堅牢性の問題
(2) スレッドを作成する pthread_create の戻り値 pthread_t について説明する
(1) pthread_ detach(pthread_ self()); 新しいスレッドは自己切り離されます。
(2) pthread_detach(tid1); メインスレッドが新しいスレッドを分離します
3. スレッドの分離は、スレッドが終了する 4 番目の方法として理解できます。
1. Linux でのスレッド
1. Linux でのスレッドの概念
(1) 教材における大まかなスレッドの定義
1. プロセス内で実行される実行フロー (スレッドはプロセスの仮想アドレス空間で実行されます)
2. スレッドはプロセスよりも細かく、低コストです
3. スレッドは CPU スケジューリングの基本単位です
(2) スレッドの紹介
フォーク後、父と息子はコードを共有します。if
else で判断できるため、父と息子のプロセスは異なるコードブロックを実行できます --> 異なる実行フローにつながり、特定のリソースを分割できます
(3) スレッドの実際の定義と概略図
スレッド (実行フロー) はシステム スケジューリングの基本単位です。Linux には実際のスレッドは存在せず、Linux スレッドは軽量プロセスと呼ばれるプロセスによってシミュレートされます。スレッドの実行はプロセスよりも細かく、スケジューリングコストが低くなります(プロセスを切り替えるときにページテーブルや電気的アドレス空間などを切り替える必要はなく、スレッドのコンテキストデータを切り替えるだけで済みます)。プロセスの一部を実行し、プロセスのデータの一部を使用してプロセスのリソースの一部にアクセスするためです。
(4) LinuxやWindowsなど他のOSでのスレッドの比較
Linux では次のように考えられます。
プロセスとスレッドの間に概念的な違いはなく、両方とも実行ストリームと呼ばれます。
Linux スレッドはプロセスでシミュレートされます(実際にはプロセス PCB でシミュレートされます)
Linux 上の tcb は、論理構造が同じであるため、pcb です。
他のオペレーティング システム (Windows など) では、次の点が考慮されます。
プロセスとスレッドは実行フローのレベルで異なります。TCB 構造の追加は保守コストの増加につながります。 スレッド:
process=n:1 プロセス - PCB; スレッド - TCB (スレッド制御ブロック)
これで、CPU が認識するすべての task_struct が実行フロー (スレッド) になります。
(5)LWP
LWP - ライトウェイトプロセス: 軽量プロセス。LWP=PID の実行フローはメインスレッドであり、一般にプロセスとして知られています。
詳細概念: LWP は軽量プロセスです。Linux では、プロセスはリソース割り当ての基本単位であり、スレッドは CPU スケジューリングの基本単位であり、スレッドはプロセス PCB 記述を使用して実装され、すべての PCB は同じプロセス内にあります。同じ仮想アドレス空間を共有するため、従来のプロセスよりも軽量です
(6) 軽量プロセスIDとプロセスIDの違い
Linux の軽量プロセスは PCB であるため、各軽量プロセスには独自の軽量プロセス ID (PCB 内の PID) があり、同じプログラム内の軽量プロセスは共通のスレッド グループ ID を持つスレッド グループを形成します。
2. プロセスを再定義する
かつて: プロセス - カーネル データ構造 + プロセスに対応するコードとデータ
現在: プロセス - カーネル パースペクティブ:システム リソースの割り当てを担当する基本エンティティ(プロセスの基本属性)、つまり、リソースを適用する基本単位システム!
内部に実行フロー task_struct を持つプロセスは 1 つだけ —単一の実行フロー プロセス
内部に複数の実行フロー task_struct を持つプロセス —複数の実行フロー プロセス
スレッド (実行フロー) がスケジューリングの基本単位です。
以下の紫枠で囲ったプロセスPCB、仮想メモリ、ページテーブル、メモリ内のデータとコード、このリソースの集まりをプロセスと呼びます。
軽量プロセスの説明:
task_ struct <= 従来のプロセス PCB、単一実行フロー プロセス (task_ struct が 1 つだけ) の場合、task_ struct = 従来のプロセス PCB、複数実行フロー プロセス task_ struct < 従来のプロセス PCB
(1)スレッドのメリット
マルチスレッドを使用すると、CPU リソースを最大限に活用し、タスク処理をより効率的にし、プログラムの応答を改善できます。つまり、時間のかかる操作ではスレッドを使用してアプリケーションの応答を改善します。
マルチコア CPU の場合、各コアはプログラム処理用の独立したレジスタのセットを備えているため、複数の実行フローの情報を異なるコアにロードして同時に並列実行することができ、CPU リソースを最大限に活用して処理を向上させることができます。マルチ CPU システムでは、CPU 使用率を向上させるためにスレッドが使用されます。CPUスレッドが個別にプログラムを実行できるようにするのではなく、CPU スレッド スケジューラ内の異なるスレッドがプログラム全体を一緒に実行します。
(2)スレッドのデメリット
(3)スレッド例外
(4)スレッドの使用方法
3. スレッドおよびプロセスの共有/プライベートリソース
4.プロセスとスレッドの関係
① Linux でプロセスがスレッドより安全である理由は、各プロセスが独立した仮想アドレス空間を持ち、各プロセスが独自のデータを持ち、独立していてデータを共有しないためです。この記述は誤りであり、広範すぎ、一方的です。
②複数のプロセス間でのデータ共有は、マルチスレッドプログラミングよりも複雑です。スレッド間の通信は単純です (アドレス空間とページ テーブル情報を共有するため、パラメーターとグローバル データを通信できます)。一方、異なるプロセス間の通信はより複雑で、通常、これを実現するにはカーネル (システム コール) を呼び出す必要があります。
③マルチスレッドの作成、切り替え、破棄はマルチプロセスよりも高速です。プロセス内のリソースのほとんどはスレッド間で共有されるため、共有データを再作成したり破棄したりする必要がないため、消費量はプロセスの消費量よりも少なく、逆も同様で、プロセスよりも高速です。
④計算量が多い場合はマルチスレッドが望ましい。複数プロセス、複数スレッドを使用することで大量の計算を並列・同時処理できますが、スレッドのリソース消費量は複数プロセスに比べて少なく、安定性は複数プロセスに比べて劣るため、具体的でより詳細な需要シナリオについて
⑤ 「プロセスには少なくとも 1 つのスレッドがある」は正しいですが、「プログラムには少なくとも 1 つのプロセスがある」は間違いです。プログラムは静的でプロセスを含まないからです。プロセスはプログラムが実行されているときの実体であり、それはプログラムの実行です
⑥スレッド自体はシステムリソースを所有しません。プロセスがリソース割り当て単位であるため、スレッドはシステム リソースを所有しませんが、プロセスのリソースを共有し、プロセスのリソースはシステムによって割り当てられます。
⑦どのスレッドも別のスレッドを作成または取り消すことができます。
コントラストの寸法 |
マルチプログレス |
マルチスレッド化 |
要約する |
データ共有、同期 |
データ共有は複雑であり、IPC が必要ですが、データは分離されており、同期は簡単です |
プロセスデータを共有するためデータ共有は簡単ですが、その分同期が複雑になります |
それぞれに独自の利点があります |
RAM、CPU |
大量のメモリを占有し、スイッチングが複雑で、CPU 使用率が低い |
メモリ占有量が少なく、切り替えが簡単、CPU 使用率が高い |
スレッドドミナント |
作成、破壊、切り替え |
作成、破棄、切り替えが複雑で時間がかかる |
作成、破棄、切り替えはシンプルかつ高速です |
スレッドドミナント |
プログラミング、デバッグ |
プログラムもデバッグも簡単 |
プログラミングは複雑、デバッグも複雑 |
プロセス優位 |
信頼性 |
プロセスは相互に影響を与えません |
スレッドがハングするとプロセス全体がハングします。 |
プロセス優位 |
配布された |
マルチコア、マルチマシン分散に適応します。1 台のマシンでは不十分な場合は、複数のマシンに拡張するのが簡単です。 |
マルチコア分散への適応 |
プロセス優位 |
2. ページテーブルの理解 - 仮想アドレスと物理アドレス間の変換
1. ページテーブルの理解
変換処理中に仮想アドレスは直接変換されません 仮想アドレスは
32 ビットです: 32 ビットを 10+10+12 に分割します
0101 0101 00 0100 0111 11 0000 1110 0101
XXXX XXXX xx yyyy yyyy yy zzzz zzzz zzzz
仮想アドレスの最初の 10 ビットは、第 1 レベルのページ テーブル (ページ ディレクトリ) で対応する第 2 レベルのページ テーブルを見つけます。対応する第 2 レベルのページ テーブルを見つけた後、中間の 10 ビットは、第 2 レベルのページ テーブルで対応するページ開始アドレスを見つけます。 -レベル ページ テーブル (物理メモリ); 対応するページの開始アドレスを見つけた後、物理メモリ内のページ (4KB) 内の対応するデータのアドレスを見つけるためのオフセットとして最後の 12 ビットが使用されます。12 ビットには 2^12=4096 ワードのセクション = 4KB があり、物理メモリ管理単位だけがページであり、ページは 4KB であるため、最後の 12 ビットはページのすべてのアドレスをカバーできます。アドレスを見つけた後、CPU は物理メモリのデータを読み取ります。
2. ページテーブルの利点
(1) プロセスの仮想アドレス管理とメモリ管理、ページテーブル + ページによる分離
(2) 省スペース: ページング機構 + ページテーブルのオンデマンド作成
ページ テーブルもメモリを占有します。ページ テーブルは分離されており、ページ テーブルはオンデマンドで作成できます。たとえば、ページ ディレクトリの 3 番目のアドレスは一度も使用されていないため、対応する 2 次ディレクトリを作成することはできません。必要に応じて作成されます。ページ テーブルのサイズは 2^32/2^12=2^20 バイト (ページ ディレクトリとセカンダリ ページ テーブル)
仮想アドレスから物理アドレスへの変換 - ソフトウェア (ページ テーブル) とハードウェア (MMu) を組み合わせたハードウェア MMU によって実行されます。
3. スレッドインターフェース
1.pthread_create はスレッドを作成します
pthread_create はライブラリ関数であり、ユーザー状態でユーザースレッドを作成する機能であり、このスレッドの実行スケジューリングは軽量プロセス LWP に基づいて実現されます。
新しいスレッドを作成するインターフェイスはシステム インターフェイスではなく、 linux によってもたらされるネイティブため、メイクファイル g++ -o $@ $^ -lpthread でリンクする必要があるサードパーティ ライブラリ関数です。 -std=c++ 11
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); (pthread_t 就是unsigned long int)
thread : 出力パラメータ、スレッド ID。attr : スレッド属性。現在は考慮されていません。nullptr に設定されます。start_routine : スレッド実行時のコールバック関数 (スレッドによって実行される関数メソッド)。arg: このパラメータを通じてスレッドの属性を渡します - 現在渡せる属性: 「作成されるスレッドの名前」、arg は start_routine関数の仮パラメータに渡されます
戻り値: 成功した場合は 0 を返し、失敗した場合はエラー コード errno を返します。
例: int n = pthread_create(&tid, nullptr, startRoutine, (void *)"thread1"); 新しいスレッドは startRoutine 関数から実行に入り、メインスレッドは n を取得して次のコードの実行を継続します。
ps -aL (all light) は、すべての軽量プロセスを表示します。
LWP——軽量待機プロセス: LWP は軽量プロセスであり、プロセス内の PCB を記述します。
メイクファイル:
mythread:mythread.cc
g++ -o $@ $^ -lpthread -std=c++11
.PHONY:clean
clean:
rm -f mythread
mythread.cc
デモを終了するには戻る
#include<cstdio>
#include<unistd.h>
#include<pthread.h>
#include<iostream>
using namespace std;
void printTid(const char* name,const pthread_t &tid)//引用
{
printf("%s 正在运行,thread id:0x%x\n",name,tid);
}
void* startRoutine(void* args)
{
const char* name=static_cast<const char*>(args);
int cnt=5;
while(true)
{
printTid(name,pthread_self());
sleep(1);
if(!(cnt--))
break;
}
cout<<"新线程退出…………"<<endl;
return nullptr;
}
int main()
{
pthread_t tid;
int n=pthread_create(&tid,nullptr,startRoutine,(void*)"thread 1");
sleep(10);
pthread_join(tid,nullptr);
while(true)
{
printTid("main thread",pthread_self());
sleep(1);
}
return 0;
}
2.pthread_self
man 3 pthread_self
pthread_t pthread_self(void);
スレッドは独自のスレッド ID を取得します
3.thread_join
man 3 pthread_join
int pthread_join(pthread_t thread, void **retval); thread: スレッド ID。retval: 出力パラメータ、スレッド終了の終了コード。(参加には終了信号は必要ありません)
スレッドが終了すると、通常は参加して待機する必要がありますが、参加しないとプロセスと同様のメモリ リークの問題が発生します。(つまり、関数は次のとおりです: スレッド リソースの解放 - スレッドが終了することが前提です。そして、スレッドに対応する終了コードを取得します)
成功した場合は 0 を返し、エラーの場合はエラー コードを返します。
4.pthread_exit
スレッドを終了します。スレッドの終了 - 通常の終了のみが考慮されます
(1) pthread_exit と exit の比較
exit(1):プロセスを終了することを意味し、メイン/新規スレッドが exit を呼び出すと、プロセス全体が終了することを意味します。pthread_exit() は単にスレッドを終了することを意味します。
(2) スレッド出口には次の 3 種類があります。
1. スレッドの終了方法、return —— return (void*)111;
2. スレッドの終了方法、pthread_exit —— pthread_exit((void*)1111);
3. スレッド終了メソッド: スレッドキャンセル要求、pthread_cancel —— pthread_cancel(tid);
void pthread_exit(void *retval); retval: スレッド終了コード
pthread_exit デモを終了します
#include<cstdio>
#include<unistd.h>
#include<pthread.h>
#include<iostream>
using namespace std;
void printTid(const char* name,const pthread_t &tid)//引用
{
printf("%s 正在运行,thread id:0x%x\n",name,tid);
}
void* startRoutine(void* args)
{
const char* name=static_cast<const char*>(args);
int cnt=5;
while(true)
{
printTid(name,pthread_self());
sleep(1);
if(!(cnt--))
break;
}
cout<<"新线程退出…………"<<endl;
//return nullptr;
pthread_exit((void*)1111);
}
int main()
{
pthread_t tid;
int n=pthread_create(&tid,nullptr,startRoutine,(void*)"thread 1");
sleep(10);
pthread_join(tid,nullptr);
while(true)
{
printTid("main thread",pthread_self());
sleep(1);
}
return 0;
}
5.pthread_cancel
スレッドをキャンセルする
int pthread_cancel(pthread_t スレッド); スレッド:回線ID
スレッドが終了する方法は、スレッドにキャンセル要求を送信します。スレッドがキャンセルされた場合、終了結果は -1 になります。
pthread_cancel(tid); 終了
#include<cstdio>
#include<unistd.h>
#include<pthread.h>
#include<iostream>
using namespace std;
void printTid(const char* name,const pthread_t &tid)//引用
{
printf("%s 正在运行,thread id:0x%x\n",name,tid);
}
void* startRoutine(void* args)
{
const char* name=static_cast<const char*>(args);
int cnt=5;
while(true)
{
printTid(name,pthread_self());
sleep(1);
if(!(cnt--))
break;
}
cout<<"新线程退出…………"<<endl;
//return nullptr;
//pthread_exit((void*)1111);
}
int main()
{
pthread_t tid;
int n=pthread_create(&tid,nullptr,startRoutine,(void*)"thread 1");
sleep(10);
pthread_cancel(tid);
(void)n;
cout<<"new thread been canceled"<<endl;
void* ret=nullptr; //void* -> 64 -> 8byte ->空间
pthread_join(tid,&ret); //void **retval是 一个输出型参数
cout<<"main thread join success,*ret:"<<(long long)ret<<endl;
sleep(3);
return 0;
}
4. ユーザーレベルのスレッドの概念
オペレーティング システムのカーネルがスレッドを認識しているかどうかに応じて、スレッドはカーネル スレッドとユーザー スレッドに分類できます。ユーザーレベルのスレッドは、アプリケーションによってサポートされるスレッドによって実装され、カーネルはユーザーレベルのスレッドの実装を認識しません。カーネルレベルのスレッドは、カーネル (システム) によってサポートされるスレッドとも呼ばれます。
1. スレッドが異常な場合はどうすればよいですか? - スレッドの堅牢性の問題
スレッドが異常 --> プロセス全体が全体として異常終了します。スレッド例外 == プロセス例外
スレッドは他のスレッドの動作に影響します - 新しいスレッドはメインスレッドに影響します メインスレッド - 堅牢性/堅牢性が低い、
2. pthread_t を理解する
これはアドレスです
1. スレッドは独立した実行フローです
2. スレッドは、自身の実行プロセス中に一時データを生成します (関数の呼び出し、ローカル変数の定義など) 新しいスレッドでグローバル変数を変更した後、新しいスレッドとメインスレッド 変更された結果を確認できます
3. スレッドは独自の独立したスタック構造を持っている必要があります
3. スレッドスタック
(1) コードエリアには3種類のコードがあります
私たちが使用するスレッド ライブラリ、ユーザーレベルのスレッド ライブラリ、ライブラリの名前は pthread です
コード領域には 3 種類のコードがあります。
①自分で書くコード。
② ライブラリのインターフェースコード。(たとえば、ダイナミック ライブラリ libpthread.so はメモリに書き込まれ、ページ テーブルを通じてプロセスの共有領域にマッピングされます。コード領域内のライブラリ インターフェイス コードは、コードを実行するために共有領域にジャンプします)ライブラリ内で実行し、コード領域に戻って実行を続行します)
③ システムインターフェースコード。(IDによるユーザーの切り替え -> カーネル実行コード)
すべてのコード実行はプロセスのアドレス空間で実行されます。
(2) スレッドを作成する pthread_create の戻り値 pthread_t について説明する
ユーザーはスレッドを使用したいと考えていますが、OS にはスレッドの概念がなく、libpthread.so スレッド ライブラリが接続の役割を果たします。
共有エリア内:
スレッドの実装全体がすべて OS に反映されるわけではありませんが、OS が実行フローを提供し、特定のスレッド構造はライブラリによって管理されます。ライブラリは複数のスレッドを作成できます -> ライブラリはスレッドも管理します -> 管理: まず組織内で説明します
struct thread_ info
{
pthread_ t tidh .
void *stack; //私有栈
……
}
libpthread.so スレッド ライブラリは共有領域にマップされます。スレッドを作成するとき、スレッド。スレッド コントロール ブロック内には、スレッドを説明する情報があります。内部には、mm_struct ユーザー空間内のスペース (スレッド スタック) へのポインタがあります。 。スレッドが正常に作成されると、pthread_t 型のアドレスが返され、pthread_t 型のアドレスには、共有領域内の対応するユーザーレベルのスレッドのスレッド制御ブロックの開始アドレスが格納されます。
結論: メインスレッドの独立したスタック構造はアドレス空間のスタック領域を使用し、新規スレッドが使用するスタック構造はライブラリで提供されるスタック構造を使用します (このスレッドのスタックはライブラリによって保持され、空間は提供されます)のユーザー共有領域による)
Linux では、スレッド ライブラリ (ユーザーレベルのスレッド ライブラリ) とカーネルの LWP は 1:1 ( LWP (アナログ PID) - 軽量待機プロセス: 軽量プロセス番号です。LWP=PID の実行フローがメイン スレッドであり、通称として知られています)プロセス)
(3) スレッドローカルストレージ
スレッドライブラリの構造体 struct thread_info には内部にスレッドを記述する情報が含まれており、 struct thread_info にはスレッドローカルストレージと呼ばれる領域もあります。機能: グローバル変数をプライベート化できます。
通常の状況では、グローバル変数は複数のスレッドによって同時に変更できます。
__thread を追加し、グローバル変数を各プロセスにコピーし、グローバル変数をプライベートにし、独自のグローバル変数を変更します。
5.スレッドを分ける
1.コンセプト
2. 例
(1) pthread_ detach(pthread_ self()); 新しいスレッドは自己切り離されます。
sleep(1) がない場合、新しいスレッドがループし続ける状況が発生します。なぜなら、メインスレッドが最初に int n = pthread_join(tid1, nullptr); を実行する可能性があるため、このとき、新しいスレッドは pthread_detach によって分離されておらず、メインスレッドは常に pthread_join でブロックされて戻りません。
sleep(1) がある場合、新しいスレッドはまず pthread_detach を使用して自身を切り離します。メインスレッドの後に int n = pthread_ join(tid1, nullptr) を実行します; この時点で、新しいスレッドは pthread_detach によってデタッチされており、メインスレッドは pthread_join に直接エラー コードを返します。切り離された場合、再度接続することはできません
(2) pthread_detach(tid1); メインスレッドが新しいスレッドを分離します
メインスレッドは最初に新しいスレッドを分離し、次に pthread_join を行う必要があるため、メインスレッドで新しいスレッドを分離することをお勧めします。
3.スレッドの分離は、スレッドが終了する 4 番目の方法として理解できます。
(1) スレッドの分離には即時分離と遅延分離があり、スレッドが生きていることを確認する必要がある。スレッドの分離は、このスレッドの存続と消滅を気にしないことを意味します。スレッドの分離は、スレッド終了の 4 番目の方法 (遅延終了) として理解できます。
(2) メインスレッドの終了によってプロセスが終了したり、他のスレッドの動作に影響を与えたりすることはありません。プロセスは、プロセス内のすべてのスレッドが終了した場合にのみ終了します。—— 通常、スレッドを分離し、対応するメインスレッド(常駐メモリのプロセス)は終了しないようにします。