前回、履歴書プロジェクトのリンクを書きましたが、履歴書プロジェクトが悪かった場合はどうすればよいですか? 悪いパフォーマンスをする方法を教えます。ある読者が後に続いた後、スーパーバイザーの地位を獲得しました。グループ内で宣伝したところ、最近多くの友人がサーバー プロジェクトに関連するナレッジ ポイントを更新するよう私に勧めるようになりました。
このまとめは、私が秋の採用活動の際に各面接で聞かれた内容をもとに、何度も確認と穴埋めを繰り返しました。新しい問題に遭遇するたびに、インターネット上で調べてまとめています。当時は主に自分で読んでおり、著作権の問題はありませんでしたが、今は WeChat 公式アカウントの公開プラットフォームに投稿したいと考えています, だから出典に遡って確認する必要があります。元のコンテンツの出典は、元の著者の業績を尊重します。出典を確認するよう最善を尽くしますが、侵害がある場合はお知らせください。
前に言った
サーバープロジェクトごとに実装される機能が異なり、拡張できる方向性も異なります。最初のセクションのプロジェクト自体の詳細は、個々の改良プロジェクトに限定されます。重複する内容のみを参照してください。あなた自身のものと一緒に。
面接官の質問は奇妙なものばかりですが、通常、質問する場合、以下のタイトルのように、特定の知識を正確に尋ねることはほとんどありません。通常、いくつかの一般的な質問が投げかけられる可能性がありますが、この時点では、彼が何に興味を持っているかをすぐに見つける必要があります。したがって、これを維持して、あなたが準備した内容に面接官を誘導する必要があります。
同時に、学習に終わりはないということも強調しておく必要があります。この記事の内容は、参考として紹介するものであり、ここで終わるわけではありません。引き続き学習を続ける必要があります。より深く、より詳細な学習~
もちろん、これは私の個人的な面接の質問にすぎず、あまり包括的ではない可能性があることも承知しています。後でこのメモをオープンソースにします。このプロジェクトについて学生が新しい質問を受けた場合、学生はそれを更新し、一緒に保守することができます!
1. プロジェクトの詳細
プロジェクト紹介
このプロジェクトの主な目的は、ブラウザのリンク リクエストを分析して処理し、テキスト、画像、ビデオなどの処理後にブラウザ クライアントに応答を返すことです。サーバーのバックエンドの処理方式はソケット通信を使用し、複数のリクエストを同時に処理するための複数IO多重化を使用し、リクエストの解析にはあらかじめ用意されたスレッドプールとリアクタモードを使用し、 IOの監視はメインスレッドが担当します。 IO リクエストを取得すると、リクエスト オブジェクトがリクエスト キューに入れられ、ワーカー スレッドに渡されます。リクエスト キューでスリープしているワーカー スレッドは、データの読み取りとロジック処理のために起動されます。==ステート マシン== アイデアを使用して HTTP メッセージを分析し、GET/POST リクエストをサポートし、ロング/ショート接続をサポートします。
小さなルート ヒープに基づくタイマーを使用してタイムアウト要求を閉じ、タイムアウトと接続システム リソースの占有の問題を解決します。
アーキテクチャの概要
メインスレッド:
-
メイン スレッドでは、epoll はソケットをリッスンし、接続されたクライアントの書き込み要求 (メッセージ送信) や新しいクライアントの接続要求を含む、準備完了ソケット上の外部 IO イベントを処理します。
-
Ready IO ソケットによって送信されたリクエストを requestData オブジェクトにカプセル化します。このオブジェクトには、Ready ファイル記述子、送信されたメッセージ データ、これらのデータの処理機能などが含まれます。
-
そして、requestData のタイマーを NULL に設定します。つまり、requestData の処理後にタイマーを削除します。デフォルトは短い接続です。メッセージが長い接続に解決された場合、タイマーは後で追加されます。次に、requestDataをスレッドプールのタスクキューに入れ、ワーカースレッドの処理を待ちます。
-
メイン スレッドには while ループもあり、タイマー ヒープを使用してタイマー ノードを管理し、タイムアウト イベントを削除します。
ワーカースレッド:
-
ワーカー スレッドは、条件変数とロックを使用してタスク キューからタスクをフェッチし、requestData の独自の処理関数を使用して http メッセージを分析し、http 応答を送信します。
-
メッセージ内にkeep-aliveオプションがあると解析された場合、requestDataは破棄されずにクリアされて保持されます。
-
キープアライブのロング接続は永続的に予約されているわけではありませんが、タイマー (つまり、キープアライブのタイマー メンバー) がタイムアウトするように設定されています。時間を超えると、接続は閉じられます。ここで、タイムアウト時間は500msに設定されています。
-
次に、それをタイマーの最小ヒープに追加します
-
最後に、最初は fd が epolloneshot モードになっているため、再度監視できるように epoll ctl で fd の状態を設定し直す必要があります。
このうち、スレッドプール内の作業スレッドはタスクキュー内のタスクにアクセスし、作業スレッドはrequestData内のhandleRequestを呼び出してステートマシンを使用してHTTPリクエストを解析します。
SIGPIPE信号をブロックアウトする
epoll 監視サイクル全体が開始される前に、まず SIGPIPE 信号をブロックする必要があります。
デフォルトでは、閉じられたソケットの読み取りと書き込みにより sigpipe 信号がトリガーされます。このシグナルのデフォルトのアクションはプロセスをシャットダウンすることですが、これは明らかに私たちが望んでいることではありません。したがって、無視操作などの sigpipe のシグナル コールバック操作関数をリセットして、デフォルトの操作が呼び出されないようにする必要があります。
//处理sigpipe信号
void handle_for_sigpipe(){
struct sigaction sa; //信号处理结构体
memset(&sa, '\0', sizeof(sa));
sa.sa_handler = SIG_IGN;//设置信号的处理回调函数 这个SIG_IGN宏代表的操作就是忽略该信号
sa.sa_flags = 0;
if(sigaction(SIGPIPE, &sa, NULL))//将信号和信号的处理结构体绑定
return;
}
ポート多重化
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval) を使用して、バインド中の「アドレスはすでに使用されています」エラーを排除します。つまり、SO_REUSEADDR ポート多重化を設定します。
epoll監修とEPOLLONESHOT
ソケットを監視する場合は epoll を使用するエッジ トリガー + EPOLLONESHOT + ノンブロッキング IO
エポロンショット イベント
エッジトリガーモードが使用できる場合でも、ソケット上の一定時間は依然として複数回トリガーされる可能性があります。たとえば、スレッドはソケット上のデータを読み取った後にデータの処理を開始しますが、データ処理中にソケット上に読み取る新しいデータがあり (EPOLLIN が再度トリガーされます)、この時点で別のスレッドが起動されて読み取りが行われます。新しいデータ。2 つのスレッドが同時にソケットを操作する状況が発生します。ソケット接続は常に 1 つのスレッドによってのみ処理されます。epoll を使用できます。エポロンショット成し遂げる。
EPOLLONESHOT イベントに登録されたファイル記述子の場合、オペレーティング システムは、登録されたスケール、書き込み可能、または異常イベントを最大 1 つトリガーします。トリガーは一度だけ。ファイル記述子に登録された EPOLLONESHOT イベントをリセットするために epoll_ctl 関数を使用しない限り。
このようなスレッドが特定のソケットを処理しているとき、他のスレッドがそのソケットを操作する機会を持つことは不可能です。ソケット上の EPOLLONESHOT イベントをただちにリセットします。,次回ソケットが確実に読み取り可能になるように、その EPOLLIN イベントをトリガーすることができます。これにより、他のスレッドがソケットを数回処理できるようになります。
マルチスレッドとスレッドプール
マルチスレッドを使用してマルチコア CPU を最大限に活用し、スレッド プールを使用してスレッドの頻繁な作成と破棄を回避し、システム オーバーヘッドの増加を回避します。
- マルチスレッドを管理するためにスレッド プールを作成します。スレッド プールには主にタスク キューとワーカー スレッドコレクションが含まれます。タスクをキューに追加し、スレッドの作成後にこれらのタスクを自動的に開始します。スレッド数が固定されたワーカー スレッドは、同時スレッドの最大数を制限するために使用されます。
- 複数のスレッドがタスク キューを共有するため、スレッド間の同期が必要であり、タスク キューに対するワーカー スレッド間の競合には、条件変数とミューテックス ロックの組み合わせが使用されます。
- ワーカー スレッドは最初にミューテックスを追加します。タスク キュー内のタスクの数が 0 の場合、条件変数でブロックされます。タスクの数が 0 より大きい場合、条件変数でブロックされたスレッドに条件によって通知されます。これらのスレッドはタスクを取得するために競合し続けます。
- タスクキュー内のタスクのスケジューリングには、先着順アルゴリズムが採用されています。
スレッド プール内のスレッド数を最も直接的に制限する要因は、CPU プロセッサの数です。
CPU がクアッドコアの場合、CPU 集中型タスクの場合、他の要因によるブロックの発生を防ぐために、スレッド プール内のスレッドの数は 4 または +1 にすることが望ましいです。
IO 集中型のタスクの場合、スレッド間の競合は CPU リソースではなく IO であり、IO の処理は一般的に遅くなるため、通常、CPU よりも多くのコアがあり、より多くのコアを持つスレッドは、より多くのタスクを求めて争うことになります。 CPU: 郡内で IO を処理するときに CPU アイドル状態が発生したり、リソースが無駄になったりすることはありません。
HTTPリクエストの解析
-
リアクター イベント処理モードを使用すると、メイン スレッドは IO の監視のみを担当します。IO リクエストを取得した後、リクエスト オブジェクトはリクエスト キューに入れられ、ワーカー スレッドに渡されます。ワーカー スレッドはデータの読み取りとロジックを担当します。処理。
プロアクター モードでは、すべての IO 読み取りおよび書き込み操作がメイン スレッドとカーネルに渡されて処理され、ワーカー スレッドはビジネス ロジックのみを担当します。
-
メインスレッドが読み書きソケットにメッセージがあることを周期的に監視した後、ワーカースレッドは requestData の handleRequest を呼び出し、ステートマシンを使用してHTTP リクエストを分析します。
-
http メッセージ解析プロセスとメッセージ応答解析プロセスのステート マシンを上の図に示します。
サイクル中、ステート マシンはまずデータ パケットを読み取り、次に現在の状態変数に従ってデータ パケットを処理する方法を判断します。データ パケットが処理された後、ステート マシンはターゲット状態値を現在の状態変数に渡すことによって状態遷移を実現します。その後、ステート マシンが次のサイクルを実行するときに、新しいステートに対応するロジックが実行されます。
-
GET および POST メッセージの解析
ここでは 2 種類の GET メッセージと POST メッセージの分析がサポートされていることは注目に値します。
```c++
//get报文:请求访问的资源。(客户端:我想访问你的某个资源)
GET /0606/01.php HTTP/1.1\r\n 请求行:请求方法 空格 URL 空格 协议版本号 回车符 换行符
Host: localhost\r\n 首部行 首部行后面还有其他的这里忽略
\r\n 空行分割
空 实体主体
```
```c++
//post报文:传输实体主体。(客户端:我要把这条信息告诉你)
POST /0606/02.php HTTP/1.1 \r\n 请求行
Host: localhost\r\n 首部行 首部行中必须有Contenr-length,告诉服务器我要给你发的实体主体有多少字节
Content-type: application/x-www-form-urlencoded\r\n
Contenr-length: 23\r\n
\r\n 空行分割
username=zhangsan&age=9 \r\n 实体主体
```
投稿メッセージの場合、ヘッダー行に Content-length フィールドが必要ですが、get にはそれがないため、このフィールドを取り出して、後でエンティティ本体を取得するときに使用する長さを調べます。次に、ダウンして、対応する http 応答メッセージを送り返します。
get メッセージの場合、エンティティ本文は空です。リクエスト行の URL データを直接読み取ってから、対応する http 応答メッセージを送り返します。
-
完全で正しい HTTP リクエストを取得したら、
analysisReques
コード部分に進みます。最初に GET リクエストとさまざまな POST リクエスト (ログイン、登録、写真、ビデオのリクエストなど) に対して異なる処理を実行し、次にプロパティを分析する必要があります。ターゲット ファイル のターゲットファイルが存在し、すべてのユーザーが読み取り可能でディレクトリではない場合、 == を使用してmmap
それをメモリ アドレスm_file_address
==にマップし、呼び出し元にファイルが正常に取得されたことを伝えます。 -
これは、長時間の接続キープアライブをサポートするためです。
最初の行のデータを読み取った後、リクエスタが長い接続を設定した場合、接続フィールドはベースとしてキープアライブになります。
このフィールドが読み取られると、メッセージが解析されてメッセージが返送された後に requestData がリセットされます。
次に、epoll_ctl でソケット属性をリセットし、epoll listen を再度追加します。
タイマーの最適化
タイムアウト要求を削除== に小さなルート ヒープタイマーを実装し、STL の優先キューを使用してタイマーを管理しました
-
最適化: 元々は昇順リンク リストに基づいたタイマーであり、昇順タイマー チェーンはタイムアウト時間に応じてタイマーを昇順に配置します。ただし、昇順リンクリストのタイマーではタイマーの追加効率が低いため、優先キュー管理タイマーが使用されます。優先キューの最下層は小さなルートヒープであり、時間タイマーの追加の時間計算量は O(log (n))、タイマーの削除の時間計算量は O(1)、タイマー タスクの実行の時間計算量は O(1) です。
アラーム機能は定期的に SIGALRM 信号をトリガーし、信号処理機能はパイプラインを使用してメイン ループに通知し、タイマー リンク リスト上のタイミング タスクを実行します。
デフォルトは短い接続ですが、タスク処理中に長い接続が検出された場合は、epoll に参加して応答を継続し、タイマー (mytimer) を設定して優先キューに入れます。
2つのロックの使用
1 つ目はリクエスト タスク キューの追加操作とフェッチ操作です。どちらもロックする必要があり、複数のスレッドにまたがる条件変数と連携する必要があります。
2 つ目はタイマー ノードの追加と削除です。これはロックする必要があり、メイン スレッドとワーカー スレッドの両方がタイマー キューを操作する必要があります。
2. 共通試験に関する知識のポイント
1. 優先キュー
プライオリティキューはO(1)時間で最大値を取得でき、O(log n)時間で最大値を取り出したり、任意の値を挿入したりできます。
優先キューはヒープを使用して実装されることがよくあります。ヒープは、各ノードの値が常にその子ノードの値以下である完全なバイナリ ツリーです。実際にヒープを実装するときは、通常、ポインターを使用してツリーを構築する代わりに配列を使用します。これは、ヒープが完全なバイナリ ツリーであるため、配列で表される場合、位置 i にあるノードの親ノードの位置は [(i-1)/2] でなければならず、その 2 つの子ノードの位置は次のとおりです。 2i+1 および 2i+2 である必要があります。
ヒープの実装方法は次のとおりです. 2 つのコア操作は、小さなルート ヒープなどのフローティングとシンクです: ノードが親ノードより小さい場合、2 つのノードを交換する必要があります; 交換後、新しい親ノードよりも小さい可能性があります ノードが大きいため、比較および交換操作を継続的に実行する必要があります (これをフローティングと呼びます)。同様に、ノードが子ノードよりも大きい場合は、比較および交換操作を下方向に継続的に実行する必要があります。それを私たちは沈下と呼んでいます。ノードに 2 つの子がある場合、常に最小の子を交換します。
vector<int> heap;
// 获得最小值
void top() {
return heap[0];
}
// 插入任意值:把新的数字放在最后一位,然后上浮
void push(int k) {
heap.push_back(k);
swim(heap.size() - 1);
}
// 上浮
void swim(int pos) {
//如果父节点大于当前插入点,则交换,即上浮
//除非父节点小于等于插入点,或者pos=0,即已经上浮到根节点最小值,停止循环
while (pos > 0 && heap[(pos-1)/2] > heap[pos])) {
swap(heap[(pos-1)/2], heap[pos]);
pos = (pos-1)/2;
}
}
//最小堆的删除指的是删除根节点的最小值,即数组的第一个值
//为了不破坏最小堆结构,y由于少了第一个元素,我们把把最后一个数字挪到开头,然后进行下沉
void pop() {
heap[0] = heap.back();
heap.pop_back();
sink(0);
}
// 下沉
//如果当前点 大于 子节点中较小的那个,则交换,即下沉
//除非当前点小于等于 子节点中较小的那个,或者当前点已经到达最底部,即已经下沉到最底部,退出循环
void sink(int pos) {
while (2 * pos + 1 < heap.size()) {
int i = 2 * pos + 1;
if (i+1 < heap.size() && heap[i] > heap[i+1]) ++i;//如果有两个子节点,找到较小的那个交换
if (heap[pos] <= heap[i]) break;
swap(heap[pos], heap[i]);
pos = i;
}
}
アルゴリズム内で大なり小なり記号を交換することにより、最小値を迅速に取得する優先キューを取得することもできます。
5つのIOモデル
ネットワークIOモデル
ネットワーク IO にはユーザー空間とカーネル空間が関係し、通常は次の 2 つの段階を経ます。
- フェーズ 1 :データの準備ができるまで待機します。つまり、ネットワーク データがカーネル バッファにコピーされるのを待機します。
- 第 2 段階:カーネル バッファからユーザー バッファにデータをコピーします。
上記のデータ準備完了はソケットにデータが到着することとして理解できます.上記 2 つの段階の違いに応じて、さまざまなネットワーク IO モデルが現れます. 次に、これら 2 つの段階に従って分析していきます。
1. ブロック I/O
呼び出し元は関数を呼び出し、関数が戻るのを待ち、その間は何もせず、関数が戻ったかどうかを確認し続けます。次のステップに進む前に、関数が戻るまで待つ必要があります。ユーザー プロセスは2 つの段階が完了するまでブロックされます。つまり、最初の段階ではデータを待機してブロックされ、2 番目の段階ではcopy
カーネルからユーザー空間へのデータがブロックされます。copy
データの完了後にカーネルが戻った場合のみです。 、ユーザープロセスは状態のブロックを解除し、再度実行します。
linux
socket
デフォルトでblocking
短所: 1 つの操作を同時にインテリジェントに処理するため、効率が低い
結論: IO をブロックすると、両方のステージがブロックされます。
2. ノンブロッキング I/O
ユーザープロセスのシステムコール時、データが無い場合はイベントの発生の有無に関わらず直接戻り、発生していない場合は-1を返します。したがって、1 フェーズのデータ準備でユーザーのプロセスがブロックされることはありません。ただし、ユーザー プロセスは、カーネル データの準備ができているかどうかを常に確認する必要があります (これにより、CPU がアイドル状態になり、リソースが浪費されるため、ほとんど使用されません)。データの準備ができると、データがカーネル空間からユーザー空間にコピーされ (第 2 段階)、カーネルが結果を返すまで、ユーザー プロセスはブロックされます。
( )にfcntl
設定すると、非ブロッキングにすることができます。socket
NON-BLOCKING
fcntl(fd, F_SETFL, O_NONBLOCK);
ここでは、戻り値 -1 を個別に判断する必要があります (recv
戻り値を例に挙げます) 戻り値が -1 の場合、最初に errno を判断する必要があります。errno はEAGAIN
、recv 操作が完了していないことを意味します。 errno が の場合はEWOULDBLOCK
、データがないことを意味します、これら 2 つのケースはどちらも実際のシステム エラーではなく、これら 2 つが除外されると、システム エラーが発生したことを意味します。意味:
- 0 より大きい場合、受信データは完了し、戻り値は受信したバイト数になります。
- 0 に等しい場合、接続は正常に切断されました
欠点: ビジーポーリングには CPU リソースが必要です。
結論: ノンブロッキング IO は、第 1 段階ではブロックされませんが、第 2 段階ではブロックされます。
3. IO 多重化 (select/poll/epoll)
Linux は関数を使用してselect/poll/epoll
IO 多重化を実装しますが、関数自体はブロックされています。ただし、IO のブロックとの違いは、これらの関数は複数の IO 操作を同時にブロックできることです。単一のプロセス/スレッドで複数のネットワーク接続を同時に監視できsocket fd
、イベントがトリガーされると、IO 操作関数が実際に呼び出され、それに応じて処理されます。
結論: どちらの段階もブロックされており、単一のスレッドで複数のネットワーク接続を同時に監視および処理できるという利点があります。
接続数がそれほど多くない場合、select/epoll を使用する Web サーバーのパフォーマンスがマルチスレッド + ブロッキング IO を使用する Web サーバーより必ずしも優れているとは限らず、遅延が大きくなる可能性があります。前者には 2 つのシステム コール (select/epoll + read) が必要ですが、後者には 1 つだけ (read) しかないためです。ただし、接続数が多い場合には、select/epoll の利点が強調されます。
4. 信号駆動型 IO
Linux はシグナル駆動型 IO にソケットを使用します。sigaction
システム コールを通じて、シグナル駆動型 IO 用のソケットが確立され、信号処理関数がバインドされます。sigaction
ブロックされずにすぐに戻ります。プロセスは引き続き実行されます。データの準備ができたら、カーネルはSIGIO
プロセス用の信号を生成し、信号処理関数で受信データを呼び出しますrecv
。
ノンブロッキング IO との違いは、メッセージ通知メカニズムを提供することです。これにより、ユーザー プロセスがポーリングとチェックを継続的に行う必要がなく、システム コールの数が減り、効率が向上します。
結論:1段階ノンブロッキング(非同期)、2段階ブロッキング(同期)
上記の 4 つのモデルにはすべて、2 段階のブロッキングという共通点があります。つまり、ユーザー プロセスは実際の IO 操作に参加する必要があるため、上記の 4 つのモデルはすべて同期 IO モデルと呼ばれます。
5. 非同期 I/O
Linux は非同期 IO インターフェイスを提供しておりaio_read
、aio_write
カーネルはユーザー プロセスを受信するとaio_read
ブロックせずにすぐに戻り、ファイル記述子、バッファ ポインタ、バッファ サイズ、ファイル オフセットaio_read
などをカーネルに渡します。データの準備ができたら、カーネルはデータをユーザー空間に直接送信し、ユーザー プロセスにシグナルを送信してユーザー データを非同期的に処理します ( )。したがって、非同期 IO のユーザー プロセスは、カーネル空間からユーザー空間へのデータ転送のプロセスに参加する必要はありません。つまり、第 2 ステージはブロックしません。copy
copy
aio_read
copy
結論: 両フェーズでブロッキングなし
3. ブロッキングとノンブロッキング、同期と非同期
ブロッキングと非ブロッキングの違いは、カーネル データの準備ができていないときの第 1 段階のデータ準備中にユーザー プロセスがブロックするかどうかです。同期と非同期の違いは、データがカーネル データから転送されるcopy
ときにユーザー プロセスがブロックするかどうかです。カーネルからユーザー空間へのフェーズ データの読み取りと書き込み。
同期とは、第 2 段階のデータの読み取りと書き込みが要求側自身によって行われることを意味します。非同期は、要求側がイベントの読み取りと書き込みには参加せず、何かが発生したときに要求されたイベントと通知メソッドのみを送信することを意味します。他の人がイベント処理の完了をリッスンすると、事前に指定された通知方法で処理結果を要求元に通知します。
4. IO多重化
IO多重化技術とは何ですか?
簡単に言うと、単一のスレッド/プロセスは複数のファイル記述子を監視でき、特定の fd の準備が完了すると、対応する読み取りおよび書き込み操作を実行できます。実行中のプロセスの数を減らすことで、コンテキスト切り替えの消費が効果的に削減されます。ただし、select、poll、および epoll は本質的にすべて同期 I/O であり、読み取りおよび書き込みイベントの準備が完了した後、つまりデータの読み取りおよび書き込みプロセスがブロックされた後に、読み取りおよび書き込みを実行する必要があります。
IO多重化テクノロジーが必要な理由
高性能サーバー向け。IOをブロックするインターフェースはデータのコピーが完了するまでブロックされrecvfrom
、シングルスレッドの場合はメインスレッドがブロックされ、プログラム全体が永久にロックされます。もちろん、マルチスレッド、つまり1 つの接続を 1 つのスレッドに割り当てて処理することで解決できますが、数百万の接続がある場合、何百万ものスレッドを作成することは不可能です。結局のところ、オペレーティング システムのリソースは限定。したがって、単一のスレッドが複数のネットワーク接続を監視する状況を解決するための IO 多重化テクノロジがあります。
IO多重化技術とは何ですか?
主な IO 多重化手法は次のとおりです。select
およびpoll
。epoll
これらのシステム コール自体はブロックされ、監視される設定socket
は であることに注意してくださいnon-blocking
。原理select
と似ています。poll
選択する
作業過程:
- まずファイル記述子のリストを作成し
readfds
、監視するファイル記述子をリストに追加し、対応するファイル記述子を 1 にFD_SET
設定します。readfds
- を呼び出し
select
、ファイル記述子リストをreadfds
カーネル空間にコピーし、リスト内のファイル記述子を監視します。轮询
関心のある fd は、データが来ていない場合、これらのファイル記述子の 1 つ以上が IO 操作を実行するまでブロックします。カーネルは、対応するファイル記述子を設定します。ビットを 1 に設定し、結果をユーザー空間に返します。 - ユーザー空間はファイル記述子リストを走査し
readfds
、FD_ISSET
対応する fd が設定されているかどうかを確認し、設定されている場合は read を呼び出してデータを読み取ります。
利点: 複数のファイル記述子をリッスンできる
短所:
- 最大リッスン可能なファイル記述子には上限があり、これは
fd_set
次のように決定されます (通常は 1024)。 fd_set
ユーザー状態とカーネル状態の間でコピーする必要があり、コストがかかります- どの fd が準備ができているかを正確に知ることは不可能であり、毎回すべての fd を走査する必要があります。
- ファイル記述子リストのコレクションは再利用できないため、毎回リセットする必要があります。
世論調査
poll
実装方法も似ておりselect
、効率も似ています。fd セットを記述する唯一の方法が異なり、poll では select の fd_set 構造体の代わりに、pollfd 構造体が使用されます。この構造には、カーネルに検出を委託するファイル記述子、ファイル記述子の検出のためにカーネルに委託するイベント、およびファイル記述子で実際に発生するイベントがstruct pollfd
含まれます。fd
events
revents
違いは、
- 基礎となる層はリンク リストを通じて実装されるため、監視できる最大 fd に制限はありません。
poll
カーネルはrevents
特定のイベントをトリガーするかどうかを設定するために使用されるため、毎回リセットする必要はありません
struct pollfd{
int fd;
short events;
short revents;
}
エポール
epoll
select
これは、 よりpoll
も効率的な IO 多重化テクノロジです。epoll にはepoll_create
、 、epoll_ctl
、の 3 つの重要なインターフェイスがありますepoll_wait
。
まず、カーネルepoll_create
内に新しい構造体を作成して構造体を作成します。eventpoll
カーネルで作成されるこの構造には2 つの重要なデータがあります。1 つは検出する必要があるファイル記述子情報、最下層は赤黒ツリー、 追加、削除、変更の時間計算量は logn です。もう 1 つは、IO イベントの到着とともにすべての fds (赤黒ツリーのノードを共有する) を格納する準備完了リストで、最下層は二重リンク リストです。
epoll_ctl
このインスタンスを管理するには、挿入、削除、更新の3 つの操作が含まれます。
挿入では、ソケット fd とそれに関連するイベントを使用して構造を構築し、eventpoll
その構造に挿入し、同時にコールバック関数がカーネル割り込みハンドラーに登録され、このハンドルの割り込みが到着した場合に、それをレディ・リストのリンク・リストに入れるようにカーネルに指示します。削除とはソケット fd に対応するノードをeventpoll
削除する、更新とはソケット fd が監視するイベントの変更など、ソケット fd に関連する情報を変更することです。
epoll_wait
検出機能としてはブロッキングインターフェイスとなっており、準備完了リストにイベントが到着すると、準備完了イベントを(構造体経由で)ユーザー空間にコピーしepoll_event
、イベント数を返します。データがなければスリープし、データがなくてもタイムアウト時間が経過するとリターンします。
epoll の動作モード: LT および ET
カーネルが読み取りイベントの検出と fd の読み取りバッファーの検出を委託されていると仮定します。
レベルトリガー LT (Level Trigger) : バッファーにデータがある限り、バッファーにデータがなくなるまでトリガーします。ユーザーがデータの一部のみを読み取る場合、またはユーザーがデータを読み取らず、バッファーにのみデータが残っている場合、常にトリガーされます。バッファ内のデータが読み取られない限り、通知は行われません。LT はデフォルトの動作方法であり、ブロック ソケットと非ブロック ソケットの両方をサポートします。(一度読んでください)
エッジ トリガー ET (エッジ トリガー) : バッファーがデータなしからデータに変化すると、epoll はそれを検出するとユーザーに通知します。ユーザーがデータを読み取らない場合、データは常にバッファー内にあるため、epoll は次の検出を通知しません。ユーザーがデータの一部のみを読み取った場合、epoll は通知しません。次回クライアントから新しいデータ パケットが到着するまで、epoll_wait
再びウェイクアップされることはありません。(ループ読み取り)
ET はブロックなしソケットのみをサポートします。このモードでは、記述子が準備ができていないカーネルはepoll を通じて通知し、ファイル記述子の準備ができていることがわかっていると想定し、そのファイルには再度書き込みません。ファイル記述子は、そのファイル記述子の準備ができなくなる何らかの処理が行われるまで、さらに準備完了通知を送信します。この fd に IO 操作がない場合 (ここで fd の準備ができなくなる)、カーネルはそれ以上の通知を送信しません。
ET は、epoll イベントが繰り返しトリガーされる回数を大幅に減らすため、LT よりも効率的です。epoll が ET モードで動作する場合、ファイル ハンドルの読み取り/書き込み操作のブロックによって複数のファイル記述子のタスクが枯渇するのを避けるために、非ブロッキング インターフェイスを使用する必要があります。
- ET モードでノンブロッキングを設定する必要があるのはなぜですか?
ET モードのため、データがある場合、トリガーされるのは 1 回だけなので、データが読み取られるたびに == はデータを一度に読み取る必要があります (データが返されるまで待機する必要がありますEWOULDBLOCK
(すべてのデータが読み取られていることを確認する必要があります)または書き込まれた) ) なので、データを読み取るために whlie ループを設定する必要があります== しかし、読み取りがブロッキング モードの場合、データがない場合はブロックされ、プログラムがスタックしてしまいます。ノンブロッキングモードを許可します。データがない場合は読み取ります。ループから抜け出し、他のプログラムの実行を続けます。
-
長所と短所
-
ETモード
短所: アプリケーション層のビジネス ロジックは複雑で、イベントが見落とされやすく、使いこなすのが難しいです。
利点: LT モードと比較して、効率が比較的高くなります。イベントがトリガーされたらすぐに処理します。
-
LTモード:
利点: プログラミングはユーザーの直感に沿っており、ビジネス層のロジックはよりシンプルです。
欠点: ET よりも効率が低い。
-
epollとpollの比較、選択
-
選択およびポーリングの場合、すべてのファイル記述子はユーザー モードでファイル記述子セットに追加され、各呼び出しでセット全体をカーネル モードにコピーする必要があります。epoll はファイル記述子が追加されるたびに、ファイル記述子セット全体をカーネル モードで維持します。場合は、システムコールを実行する必要があります。
システムコールのオーバーヘッドは大きく、存続期間の短いアクティブな接続が多数ある状況では、システムコールのオーバーヘッドが大きいため、epoll は select および Paul よりも遅くなる可能性があります。
-
select は線形テーブルを使用してファイル記述子のセットを記述し、ファイル記述子には上限があります。poll はリンク リストを使用して記述します。epoll の最下層は赤黒ツリーで記述され、準備ができているリンク リストを維持します。 , ここでイベント テーブルに準備完了イベントを追加します。epoll_wait で呼び出すときは、このリストにデータがあるかどうかだけを観察してください。
-
選択とポーリングの最大のオーバーヘッドは、ファイル記述子の準備ができているかどうかを判断するカーネルのプロセスから発生します。選択またはポーリング呼び出しが実行されるたびに、トラバーサルメソッドを使用してファイル記述子セット全体を走査し、各ファイルが適切かどうかを判断します。記述子はアクティブです。;epoll はこの方法でチェックする必要はありません。アクティビティがある場合、epoll コールバック関数が自動的にトリガーされ、epoll ファイル記述子に通知され、カーネルはこれらの準備完了ファイル記述子を準備完了リストに追加し、 epoll_wait が呼び出されるまで待ちます。
-
select と poll はどちらも比較的非効率な LT モードでのみ機能しますが、epoll は LT モードと ET モードの両方をサポートします。
-
要約すると、監視対象の FDS の数が少なく、各 FD が非常にアクティブである場合は、select と Paul を使用することをお勧めします。監視対象の FDS の数が多く、単位時間あたり一部の FD のみがアクティブである場合は、select と Paul を使用するのが明白です。 epoll パフォーマンスを向上させます。
5. イベント処理モード: リアクターとプロアクター
リアクター イベント処理モードでは、メイン スレッドは IO の監視のみを担当します。IO リクエストを取得した後、リクエスト オブジェクトはリクエスト キューに入れられ、ワーカー スレッドに渡されます。ワーカー スレッドはデータの読み取りとロジックを担当します。処理。
プロアクター モードでは、すべての IO 読み取りおよび書き込み操作がメイン スレッドとカーネルに渡されて処理され、ワーカー スレッドはビジネス ロジックのみを担当します。
通常、サーバー プログラムは、I/O イベント、信号、タイミング イベントの 3 種類のイベントを処理する必要があります。ネットワーク設計パターンの台頭により、Reactor および Proactor イベント処理パターンが登場しました。
- 同期 I/Oモデルは、 Reactor パターンの実装によく使用されます。
- 非同期 I/Oモデルは、 Proactor パターンの実装に使用されます。
リアクターモード
リアクター モード: メイン スレッドは、ファイル記述子でイベントが発生したかどうかを監視することのみを担当し、イベントが発生した場合は直ちにワーカー スレッド (論理ユニット) に通知します。それ以外に、メインスレッドは他の実質的な作業を行いません。データの読み取りと書き込み、新しい接続の受け入れ、クライアント要求の処理はすべてワーカー スレッドで行われます。
- 同期 I/O モデルを使用して実装された Reactor モードのワークフロー (例として epoll_wait を使用):
-
メインスレッドは、ソケットの読み取り準備完了イベントを epoll カーネル イベント テーブルに登録します。
-
メインスレッドは epoll_wait を呼び出して、ソケット上でデータが読み取られるのを待ちます。
-
ソケット上に読み取り可能なデータがある場合、epoll_wait はメインスレッドに通知します。メインスレッドは、ソケット読み取り可能なイベントを要求キューに入れます。
-
リクエスト キュー上でスリープしているワーカー スレッドが目覚め、ソケットからデータを読み取り、クライアント リクエストを処理して、ソケット上の書き込み準備完了イベントを epoll カーネル イベント テーブルに登録します。
-
メインスレッドは epoll_wait を呼び出して、ソケットが書き込み可能になるのを待ちます。
-
ソケットが書き込み可能になると、epoll_wait がメインスレッドに通知します。メインスレッドはソケット書き込み可能なイベントを要求キューに入れます。
-
リクエストキュー上でスリープしていたワーカープロセスが目覚め、サーバーがクライアントリクエストを処理した結果をソケットに書き込みます。
ワーカー スレッドはリクエスト キューからイベントを取り出した後、イベント タイプに応じてイベントの処理方法を決定します。読み取り可能なイベントの場合は、データの読み取りとリクエストの処理の操作を実行します。書き込み可能なイベントの場合は、データの書き込み操作を実行します。したがって、Reactor モードでは、いわゆる「読み取りワーカー スレッド」と「書き込みワーカー スレッド」を区別する必要はありません。
プロアクターモード
プロアクター モードでは、すべての I/O 操作は処理のためにメイン スレッドとカーネルに渡され、ワーカー スレッドはビジネス ロジックのみを担当します。
- 非同期 I/O モデルを使用して実装された Proactor モードのワークフロー (例として aio_read と aio_write を取り上げます):
- メインスレッドは aio_read 関数を呼び出して、ソケット上の読み取り完了イベントをカーネルに登録し、ユーザーの読み取りバッファの場所と、読み取り操作が完了したときにアプリケーションに通知する方法をカーネルに伝えます (ここでは、シグナルを受け取ります)例として)、メインスレッドは他のロジックの処理を続けます
。 - ソケット上のデータがユーザー バッファーに読み込まれると、カーネルはアプリケーションに信号を送信して、データが利用可能であることをアプリケーションに通知します。
- アプリケーションの事前定義された信号処理関数は、クライアント要求を処理するワーカー スレッドを選択します。ワーカー スレッドはクライアント リクエストを処理した後、 aio_write 関数を呼び出してソケット上の書き込み完了イベントをカーネルに登録し、ユーザーがバッファに書き込む場所と、書き込み操作が完了したときにアプリケーションに通知する方法をカーネルに伝えます。 (信号を例として取り上げます)
- メインスレッドは他のロジックの処理を継続します
- ユーザー バッファ内のデータがソケットに書き込まれた後、カーネルはアプリケーションに信号を送信して、データが送信されたことをアプリケーションに通知します。
- アプリケーションの事前定義された信号処理関数は、ソケットを閉じるかどうかの決定などの後処理を行うワーカー スレッドを選択します。
接続ソケット上の読み取りおよび書き込みイベントは、aio_read/aio_write を通じてカーネルに登録されるため、カーネルは接続ソケット上の読み取りおよび書き込みイベントをシグナルを通じてアプリケーションに報告します。したがって、メイン スレッドの epoll_wait 呼び出しは、リッスンしているソケット上の接続要求イベントを検出するためにのみ使用でき、ソケット上の読み取りおよび書き込みイベントを検出するためには使用できません。
違いとメリット・デメリット
-
Reactor は、読み取り可能および書き込み可能なイベントを認識するノンブロッキング同期ネットワーク モードです。イベント (読み取り可能なイベントなど) が検出されるたびに、アプリケーション プロセスは、データの読み取りを完了するために read メソッドをアクティブに呼び出す必要があります。つまり、アプリケーション プロセスは、ソケット受信バッファ内のデータをアプリケーション プロセスにアクティブに読み取る必要があります。メモリ では、このプロセスは同期であり、アプリケーション プロセスはデータを読み取った後にのみデータを処理できます。
-
長所と短所:
Reactor は実装が比較的簡単で、短時間かかるシナリオの処理に効率的です。イベントのシリアル化はアプリケーションに対して透過的で、ロックせずに順次かつ同期的に実行できます。
Reactor の処理時間のかかる操作は、イベント配布のブロックを引き起こし、後続のイベントの処理に影響を与えます。
-
-
Proactor は、完了した読み取りおよび書き込みイベントを認識する非同期ネットワーク モードです。非同期の読み取りおよび書き込みリクエストを開始するときは、システム カーネルがデータの読み取りおよび書き込み作業を自動的に完了できるように、データ バッファー (結果データの保存に使用される) のアドレスなどの情報を渡す必要があります。ここでの読み取りおよび書き込み作業全体は、オペレーターによって実行されます。システムは、 Reactor のように、データの読み取りおよび書き込みのために読み取り/書き込みをアクティブに開始するアプリケーション プロセスを必要としません。オペレーティング システムが読み取りおよび書き込み作業を完了すると、アプリケーションに通知されます。データを直接処理するプロセス。
-
長所と短所
Proactor の方がパフォーマンスが高く、複数のタスクを同時に実行できるように設計されているため、スループットが向上し、時間のかかるタスクを実行できます (各タスクは互いに影響しません)。
Proactor は複雑なロジックを実装していますが、これはオペレーティング システムの非同期サポートに依存します。
-
Reactorはイベント オペレーティング システムがアプリケーション プロセスに通知してアプリケーション プロセスに処理させるものとして理解でき、Proactor はイベント オペレーティング システムがイベントを処理し、処理後にアプリケーション プロセスに通知するものとして理解できます。
該当シーン
Reactor: 複数のサービス リクエストを同時に受信し、それらのイベント ドライバーを順次かつ同期的に処理します。
Proactor: 複数のサービス要求を非同期に受信して処理するイベント ドライバー。
スレッドプール
1. スレッドプールの設計
マルチスレッドを使用してマルチコア CPU を最大限に活用し、スレッド プールを使用してスレッドの頻繁な作成と破棄を回避し、システム オーバーヘッドの増加を回避します。
- マルチスレッドを管理するためにスレッド プールを作成します。スレッド プールには主にタスク キューとワーカー スレッドコレクションが含まれます。タスクをキューに追加し、スレッドの作成後にこれらのタスクを自動的に開始します。スレッド数が固定されたワーカー スレッドは、同時スレッドの最大数を制限するために使用されます。
- 複数のスレッドがタスク キューを共有するため、スレッド間の同期が必要であり、タスク キューに対するワーカー スレッド間の競合には、条件変数とミューテックス ロックの組み合わせが使用されます。
- ワーカー スレッドは最初にミューテックスを追加します。タスク キュー内のタスクの数が 0 の場合、条件変数でブロックされます。タスクの数が 0 より大きい場合、条件変数でブロックされたスレッドに条件によって通知されます。これらのスレッドはタスクを取得するために競合し続けます。
- タスクキュー内のタスクのスケジューリングには、先着順アルゴリズムが採用されています。
2. 同時実行量とタスクの実行時間に応じてスレッド プールを使用する
1. 同時実行性が高く、タスクの実行時間が短いビジネス向けにスレッド プールを使用するにはどうすればよいですか?
2. 同時実行性が低く、タスクの実行時間が長いビジネス向けにスレッド プールを使用するにはどうすればよいですか?
3. 同時実行性が高く、実行時間が長いビジネス向けにスレッド プールを使用するにはどうすればよいですか?
スレッド プールは本質的にプロデューサーおよびコンシューマーモデルであり、次の 3 つの要素が含まれます。
- タスクをスレッド プール キューに配信するプロデューサー。
- タスクキュー;
- タスク キューからタスクの実行を取得するワーカー スレッド (コンシューマ)。
スレッド プールのサイズを合理的に構成するには、スレッド プール タスクの特性を分析する必要があります。これは次の側面から分析できます。
-
タスクの性質に応じて、CPU 集中型タスク、IO 集中型タスク、混合タスクに分類されます。
-
タスクの優先度に応じて: 高、中、低
-
タスクの実行時間に応じて: 長い、中程度、短い
異なる性質のタスクは、異なる構成のスレッド プールによって実行できます。
3. スレッドプール内のスレッドの数
最も直接的な制限要因は、CPU プロセッサの数です。
-
CPU に 4 つのコアがある場合、CPU 集中型タスクの場合、他の要因によるブロックの発生を防ぐために、スレッド プール内のスレッドの数は 4 または +1 にすることが望ましいです。
-
IO 集中型のタスクの場合、IO 操作は CPU を占有せず、スレッド間の競合は CPU リソースではなく IO であるため、一般に CPU よりも多くのコアが含まれます。IO 処理は一般に遅くなり、スレッドのコア数が多くなります。スレッドが IO を処理するときに CPU アイドル状態が発生してリソースが無駄にならないように、CPU はより多くのタスクを実行します。
-
混合タスクの場合、分割できる場合は、実行時間が同程度であれば IO 集中型タスクと CPU 集中型タスクに分割できますが、処理時間が大きく異なる場合は分割する必要はありません。
タスクの実行時間が長く、ワーカースレッドの数が限られている場合、すぐにワーカースレッドがタスクに占有されてしまい、後続のタスクの処理が間に合わなくなります。逆に、タスクの実行時間が短い場合は、ワーカー スレッドの数をあまり多くする必要はありませんが、ワーカー スレッドが多すぎると、スレッド コンテキストの切り替えに多くの時間が浪費されます。
質問に戻りますが、ここでいう「同時実行性が高い」とは、プロデューサーが比較的速い速度でタスクを生成することを意味するはずですが、このとき、タスクキューの上限を適切に増やす必要があります。
ただし、3 番目の高い同時実行性と長いビジネス実行時間の問題については、スレッド プール ソリューションに単純に依存することは適切ではなく、サーバーのリソース構成が高くても、各タスクが長時間リソースを占有するため、最終的にサーバーのリソースは枯渇してしまいますが、すぐに枯渇してしまうため、この場合はビジネスの切り離しと連携し、モジュールの分割を行ってシステム全体の構造を最適化する必要があります。
3. 拡張子の問題
1. この Web サーバーは申請したドメイン名ですか? ドメイン名とは何ですか?
サーバーは同じネットワーク セグメント上の仮想マシンに配置され、ローカル ブラウザーでアクセスされるため、アプリケーションはありません。
または、同じ LAN 内の異なるホストで実験し、同じ LAN 内のプライベート IP + ポート番号を介してアクセスすることもできます。
または、サーバー プログラムをローカルに直接配置し、ローカル ループバック アドレス 127.0.0.1 を使用することもできます。
ローカル ループバック アドレスには 2 つの主な機能があります。1 つはマシンのネットワーク構成をテストすることです。1 つは 127.0.0.1 の PING が成功した場合、マシンのネットワーク カードと IP プロトコルのインストールに問題がないことを意味します。もう 1 つは、一部のサーバー/クライアント アプリケーションが実行中である場合です。サーバー上のリソースは動作中に呼び出す必要があり、サーバーの IP アドレスを一般的に指定する必要があります。これは 127.0.0.1 でも機能します。
3. スレッド プール内のワーカー スレッドは常に待機していますか?
スレッド プール内のワーカー スレッドは、常にブロックして待機するモードになっています。最初にスレッド プールを作成したときに、pthread_create を循環的に呼び出してスレッド プール内に 5 つのワーカー スレッドを作成しました。また、ワーカー スレッド処理関数インターフェイスは、pthread_create 関数プロトタイプの 3 番目のパラメーター関数ポインターが指すワーカー関数であるためです (カスタム関数 ) を呼び出してから、スレッド プール クラスのメンバー関数 run (custom) を呼び出します。
3 番目のパラメータを run 関数に直接指すだけではなく、ワーカーにオブジェクトを渡して run を呼び出すのはなぜでしょうか? その理由は、ワーカーを静的メンバー関数として設定しており、静的メンバー関数は静的メンバー変数にのみアクセスできることを誰もが知っているため、クラス内の非静的メンバー変数にアクセスできるようにするには、次のように呼び出すことができます。この要件を達成するには、ワーカー内で実行されるメンバー変数を使用します。
run 関数では、高い同時実行性の問題を処理できるようにするために、スレッド プール内のワーカー スレッドをブロックし、リクエスト キューが空ではないという条件で待機するように設定します。プロジェクトは常に待機モードでブロックされます。
4. タスクの処理後のスレッド プールのワーカー スレッドの状態はどうなっていますか?
ここで考慮すべきケースは2 つあります
(1) タスク処理後にリクエストキューが空の場合、スレッドはブロック待ち状態に戻ります。
(2) タスクの処理後にリクエスト キューが空でない場合、このスレッドは他のスレッドとリソースを競合する状態になり、ロックを取得した人がイベントを処理する資格を得ます。
5. 1,000 のクライアントが同時にアクセス要求を出し、スレッドの数がそれほど多くない場合、各クライアントにタイムリーに応答するにはどうすればよいでしょうか?
このプロジェクトは、IO 多重化に基づく同時モードです。1 つのクライアント接続が 1 つのスレッドに対応するわけではないことに注意してください。クライアント接続に処理する必要のあるイベントがある場合、epoll はそのイベントを通知し、対応するタスクをリクエスト キューに追加して、ワーカー スレッドが実行を競合するのを待ちます。それでも速度が遅い場合は、スレッド プールの容量を増やすか、クラスター分散アプローチを検討するしかありません。
6. クライアント リクエストがスレッドを長時間占有する必要がある場合、次のクライアント リクエストに影響しますか? 適切な戦略は何ですか?
スレッド プール内のスレッドの数は限られているため、次の顧客リクエストに影響します。顧客リクエストがスレッドを占有する時間が長すぎると、リクエストの処理効率に影響します。リクエスト キューは処理を待機しています。 、その結果、次のクライアントリクエストに影響を与えます。
予防策:
- スレッド処理リクエスト オブジェクトの処理タイムアウトを設定し、時間を超過する前にスレッド処理タイムアウトを通知するシグナルを送信し、再度チェックする時間間隔を設定できます。この時点でリクエストがまだスレッドを占有している場合は、直接切断されます。
- タスクを処理する各スレッドの時間しきい値を設定します。特定のクライアント リクエストに時間がかかりすぎる場合、そのリクエストはタスク リクエストの最後に配置されるか、切断されます。
7. Webbench とは何か、原理を紹介
親プロセスは複数の子プロセスをフォークし、各子プロセスはユーザーが要求した時間内またはデフォルトの時間内にターゲット Web ループに実際のアクセス要求を送信します。親プロセスと子プロセスはパイプラインを通じて通信し、子プロセスはパイプライン書き込みポートを介した親プロセスへのアクセス要求、完了後に記録される合計情報、親プロセスはパイプライン リーダーを介して子プロセスから送信された関連情報を読み取り、時間が経過すると子プロセスは終了し、親プロセスすべての子プロセスが終了した後、最終的なテスト結果をカウントしてユーザーに表示し、その後終了します。
9. 生産者と消費者の紹介
プロデューサーとコンシューマーは主にデータの同期使用に使用されます。プロデューサーはデータを生成し、共有バッファーに置きます。コンシューマーはバッファーにデータがなくなるまでブロックして待機します。プロデューサーがデータを生成するときは、シグナル関数 Wake を使用します。ブロッキングを開始し、データの消費を開始し、データ生成によってバッファーがいっぱいになると、プロデューサーはブロックして待機します。すべてのブロッキングでは条件変数が使用されます。
参考文献
『新受サーバープロジェクト講義録』
https://blog.csdn.net/a15929748502/article/details/90269859
https://github.com/KyleAndKelly/MyWebServer
https://zhuanlan.zhihu.com/p/364044293
https://zhuanlan.zhihu.com/p/269247362
著作権に関する声明: コンテンツはインターネットからのものであり、著作権はオリジナルの作成者に属します。確認できない場合を除き、作者と出典を明記させていただきますが、侵害等がありましたらご一報ください、即削除しお詫び申し上げます、よろしくお願いいたします!
整理するのは簡単ではありませんが、いいね、視聴、リポスト、スターマークが最大の励みです!誰もが満足のいくオファーを得られることを願っています!