[Linux] マルチスレッド --- POSIX セマフォ + 遅延マン モードのスレッド プール + その他の一般的なロック

Linux システムは花を振りかける

ここに画像の説明を挿入



1. POSIX セマフォ

1. ブロックキューによって実装された生産および消費モデルのコードが不十分 (重要なリソースの準備状況を事前に知ることができない)

1.
以前の生産および消費モデル コードでは、スレッドがクリティカル リソースを操作したい場合、つまりクリティカル リソースを変更する場合、クリティカル リソースは変更する前に条件を満たしている必要があり、そうでない場合は変更できません。次のプッシュ インターフェイスでは、キューがいっぱいの場合、クリティカル リソース条件が現時点で準備ができておらず、プッシュを続行できないことを呼び出します。この時点でキューがいっぱいでない場合、スレッドは cond キューで待機する必要があります。つまり、クリティカルなリソース状態の準備が整った後、プッシュを続行し、_q のプッシュ インターフェイスを呼び出すことができます。
しかし、コードを見ればわかるように、重要なリソースの準備ができているかどうかを判断したい場合、最初にロックしてから判断する必要があるのでしょうか? 重要なリソースを自分で判断しているため、実際には重要なリソースにアクセスしていることになりますが、重要なリソースにアクセスしたいので、ロックする必要がありますか? もちろん必要ですよ!重要なリソースを保護する必要があるためです。
したがって、コードは次のようになります。クリティカル リソースの状態が準備完了であるかどうかを事前に知ることはできないため、最初にロックしてから、クリティカル リソースの準備完了状態を手動で判断する必要があります。状態を通じて、次のことができます。さらに、重要なリソースを待つか直接操作するかを判断します。
しかし、事前に知ることができれば、重要なリソースの準備完了状態が事前にわかっているため、ロックする必要はなく、重要なリソースの状態を手動で判断する必要もありません。したがって、カウンターがある場合、このカウンターはクリティカル リソース内の小さなリソースの数を表します。たとえば、キュ​​ー内の各スペースは小さなリソースです。スレッドがクリティカル リソースにアクセスする必要がある場合、最初にこのカウンターが適用されます。このカウンタは確かに 0 より大きいですが、これは現在のキューに空き位置があることを意味するのではないでしょうか? その後、データをキューに直接プッシュできます。カウンタが 0 に等しい場合は、現在のキューに空き領域がないことを意味します。キューにデータをプッシュすることはできませんが、データのプッシュを続ける前に、カウンタが再び 0 より大きくなるまでブロックして待つ必要があります。行列に。

ここに画像の説明を挿入

2. セマフォの理解

1.
セマフォとは何ですか? 実際、これは本質的にカウンターであり、重要なリソース全体の中の小さな重要なリソースの数を測定するカウンターです。したがって、このカウンタがあれば、パブリック リソースに再度アクセスする前にカウンタをロックする必要がなく、その後、クリティカル リソースの状態を判断し、その状態に応じてクリティカル リソースを操作できます。代わりに、セマフォを直接申請してください。セマフォの申請が成功した場合、それは重要なリソース状態の準備ができており、対応する生産および消費活動を実行できることを意味します。

2.
セマフォはクリティカル リソース内の小さなクリティカル リソースの数であるため、各スレッドによって適用される小さなクリティカル リソースは異なるため、実際には、複数のスレッドが異なるパブリック リソースに同時 + 並列でアクセスできます。
同時実行 + 並列処理に関しては、実際にはこれら 2 つは競合しません。特に会社のサーバーでは、同時実行 + 並列処理である必要があります。スレッドはセマフォを申請した後に動作し、他のスレッドがセマフォを申請しても影響はありません。もちろん、ここで述べた並行性と並列性は、重要なリソースを操作するときのプロデューサーとコンシューマーの間の関係です。これは、プロデューサーとコンシューマーのみが異なる小さなリソースにアクセスするためです。

3.
したがって、セマフォを取得した後、重要なリソースの準備ができているかを事前に知ることができ、その後、重要なリソースをどうするかを決定できます。
各スレッドがクリティカル リソース内の小さなリソースにアクセスする場合は、最初にセマフォを適用する必要があります。セマフォが正常に適用されると、その小さなリソースにアクセスできるようになります。他のスレッドがセマフォを申請できますか? そうである場合、セマフォは共有リソースですか? 共有リソースにアクセスしたい場合、共有リソース自体を保護する必要がありますか?
セマフォが小さな重要なリソースの数を測定するための単純な ++ または - - 操作である場合、++ および - - 操作はアトミックではなく、セマフォのアプリケーションとリリースにはセキュリティが適用されるため、それは間違っているはずです。質問。したがって、実際のセマフォの適用と解放は単純な ++ または - - ではなく、その適用と解放の操作はアトミックである必要があり、セマフォ - - は実際には P 操作に対応し、セマフォ ++ は V 操作に対応します。したがって、セマフォの中心となる操作は PV 操作、つまり PV プリミティブです。

3. セマフォの操作インターフェイスを事前に確認します。

1.
セマフォの操作インターフェイスは難しくありません。PV 操作は、それぞれセマフォの申請とセマフォの解放に使用される sem_wait インターフェイスと sem_post インターフェイスに対応し、sem_t は前の pthread_mutex_t と同じです。 pthread ライブラリによって維持され、データ型は です。

ここに画像の説明を挿入

4. リングキューによって実装される生産および消費モデル

1.
上記ではセマフォの原理と機能について説明してきましたが、セマフォの応用シナリオは何でしょうか? セマフォを使用して生産および消費モデルを実装する場合、どのように実装する必要がありますか?
クリティカルなリソースを操作する場合、クリティカルなリソース全体を操作する必要はなく、リソースの小さな部分のみを操作する必要がある場合があります。プロダクション スレッドとコンシューマ スレッドの両方がリソースの小さな部分で動作する場合、1 つのスレッドのみがアクセスします。この小さなリソースです。現時点では、重要なリソースへのマルチスレッド アクセスによるセキュリティの問題はありません。その後、実稼働スレッドと消費スレッドは、それぞれの小さなリソースに同時または並行してアクセスできます。重要なリソースはありません。 、相互干渉はなく、重要なリソースのセキュリティ上の問題は発生しません。

2.
このような小さなリソースを使用するシナリオでは、循環キューを使用して生産および消費モデルを実現するのが適しています。p は空の場所にデータを置き、c はデータのある空間的な場所からデータを取得します。 p と c の操作 位置が異なります。つまり、p は常に前方に実行され、すべての空の位置にデータを配置します。c は私の p を超えることはできません。超えても意味がありません。前の位置 pはまだデータを入れていないので、データを取得できますが、これも無効なデータです。そして、p は c を円で覆うことはできません。そうすると、特定の位置のデータ c が取り除かれていないように見え、p が再びデータを生成するようになり、データ範囲の問題が発生します。現時点では。
したがって、ほとんどの場合、p と c は異なる位置で動作します。異なる位置で動作する場合、p と c はデータを同時に生成および消費できます。本質的な理由は、p と c の動作が異なるためです。リソースの小さなブロックは、相互に影響を与えず、元のブロッキング キューが全体として使用され、p と c はこの全体的なリソースを直接使用します。あなたが生産するとき、私は消費することはできません。私が消費するとき、あなたは生産することができません。なぜなら、重要なリソースが一旦終了すると、生産と消費を同時に行うときに全体として使用すると、セキュリティ上の問題が発生しますが、p と c は異なる小さなリソースで動作するため、今日は心配する必要はありません。
ただし、ほとんどの場合に加えて、リング キューが最初は空で、p と c が同じキュー位置を指し、現時点では同じ小さなリソースを使用する場合など、少数のケースもあります。または、リング キューがいっぱいの場合、p と c も同じ場所を指し、同じ小さなリソースも使用します。この場合、同時アクセスと並列アクセスは不可能であり、相互排他アクセスのみが可能です。リングキューが空の場合、最初に p を生成する必要があり、この時点で c はブロックされます。リングキューがいっぱいの場合、c が最初に消費される必要があり、この時点で p はブロックされます。

ここに画像の説明を挿入

3.
したがって、リング キューの生成および消費モデルを維持したい場合、主なコア作業は 3 つのルールを維持することです。1 つはコンシューマがプロデューサを超えてはいけない、もう 1 つはコンシューマがサークル内に存在できないということです。 3 つ目は、キューが空のときまたはキューがいっぱいのとき、生産と消費の間の相互排他と同期関係を確保する必要があることです。相互排他とはどのスレッドが独立してアクセスするかを指し、同期とは両方のスレッドを指します。スレッドがアクセスしているため、飢餓の問題は発生しないはずです。
セマフォは重要なリソース内のリソースの数を測定するために使用されるカウンターであると述べましたが、プロデューサーにとって最も価値のある小さなリソースは何でしょうか? 宇宙資源です。消費者にとって彼が最も大切にしているのはデータリソースです。したがって、スペース リソースのセマフォとデータ リソースのセマフォを定義できます。

ここに画像の説明を挿入

5. リングキューのコード記述 (生産、消費、生産と消費の 3 つの関係を維持)

1.
リングキューのコードを記述するとき、実際には上記の 3 つのルールを維持します。セマフォが担当するため、最初の 2 つのルールを維持するのは非常に簡単です。セマフォが 0 の場合、P 操作は実行できません。満足するとブロックされます。最初の 2 つのルールに対応して、たとえば、キュ​​ーが空で、spaceSem が 0 より大きく、dataSem が 0 の場合、コンシューマーの P 操作は成功できず、データを消費することもできないため、この時点では c pを超えることはありません。逆も同様で、キューがいっぱいになったら自分で考えてください。生産と消費の排他関係と同期関係の維持は、最初のセマフォの違いに依存します 最初にセマフォを設定するときは、spaceSemをキューサイズに、dataSemを0に設定してから、初期セマフォを設定します, p が最初に行く必要があり、プロデューサーの P 操作は成功します。フルになると、当然 dataSem はキュー サイズになり、spaceSem は 0 になります。このとき、c が最初に行かなければならず、コンシューマーの P 操作は成功します。成功するだろう。このように、初期セマフォの設定の違いにより、キューが空の場合と満杯の場合のコンシューマとプロデューサ間の相互排他と同期関係を確保できます。

2.
元の説明ロジックは、実際には全員に対して 1 つの生産コードと 1 つの消費コードを作成することです。つまり、そのほとんどは生産と消費の間の同時アクセスであり、キューが空の場合の生産と消費の間の相互作用はごく一部です。または完全な反発力と同期。なぜなら、上記のすべてのトピックは単一生産および単一消費のモデルの下で記述されているため、321原則によれば、生産と消費の関係のみが存在し、生産と消費の関係がまだ不足しているからです。
このアイデアは、最初に int データを格納するリング キューを構築し、それを単一世代、単一消費モデルにすることです。より高度にするには、int データを CalTask​​ タスクに置き換えます。タスク オブジェクトを保存しますが、それでも単一の製造オーダーの消費です。最後に、タスク オブジェクトを格納するための複数生産および複数消費モデルのコードを実装します。
しかし、上記の説明は面倒すぎるので、結局のところ、ブロッキング キューの生成および消費モデルの基礎は前のブログにも記載されているため、以下ではそれほど冗長ではなく、マルチ生産およびマルチ消費モデルを直接アップロードします。 CalTask​​タスクオブジェクトを格納するリングキューのコード。コードの詳細をわかりやすく解説します。

3.
一般に、基礎となるコードをデザイン パターンと呼び、上位層によって呼び出されるコードをビジネス ロジックと呼びます。そのため、デザイン パターンはビジネス ロジックから切り離され、デザイン パターンはビジネス ロジックに基づいて生成される必要があります。 。それでは、最初に上位層によって呼び出されるコードについて説明しましょう。リング キューが記述されていると仮定して、上位層によって呼び出されるコードについて説明した後、必要に応じて基になる RingQueue.hpp のコードを実装します。上層。

上位層ではプロダクションスレッドとコンシューマスレッドのバッチを作成し、それぞれProductorRoutineとConsumerRoutineを実行させます.プロデューサーはコンピューティングタスクを構築して取得します.コンピューティングタスクの構築は乱数を生成することで実現します.
コンシューマは出力パラメータを通じてタスクを取得し、実行します。実際のところ、リング キューであってもブロッキング キューであっても、上位層でテストしたロジックは同じであることがわかります。したがって、上位層については何も言うことはありません。鍵は設計パターンにあります。最下層で使用されるデータ構造は321原則のトランザクションであり、場所の違いにより生産・消費モデルの実現が異なります。

ここに画像の説明を挿入
以下は、タスク クラス CalTask​​ のクラス実装です。実際、言うことはありません。このタスク クラスがキューをブロックしているときはすでに見たので、ここでは繰り返しません。
ここに画像の説明を挿入

4.
重要なポイントについて話しましょう。リングキューは論理構造的には循環していますが、実際にはモジュロ演算+配列で実現されるリングキューなので、クラスメンバ変数にはベクトルが必要です。ベクトルのサイズ、つまりストレージ タスクの上限を簡単に変更するために、ベクトルに格納されるデータの最大数を表す容量である _cap を作成します。セマフォに加えて、プロデューサーまたはコンシューマーは、生産と消費の前に独自のセマフォを申請する必要があります。セマフォのアプリケーションが成功すると、逆方向に実行し続けることができるため、セマフォの役割は実際にはハングして待機することです。ロックの。したがって、プロデューサーとコンシューマーにそれぞれ適用するには 2 つのセマフォが必要です。同時に、ほとんどの場合、生産者と消費者は異なる小さなリソースにアクセスすると前述しましたが、アクセスされる小さなリソースが異なることを確認するにはどうすればよいでしょうか? 実際には、配列の添字によって行われるため、生産場所と消費場所に対応する 2 つの添字を定義します。321 原則を通じて生産と消費のモデルを見てみましょう。また、生産と消費の間の相互排他関係を維持する必要があるため、1 つのプロデューサーと 1 つのコンシューマーのみがリング キューに入ることができるようにするために 2 つのロックが必要です。これは、基本的なクラス メンバー変数の設計です。
なぜセマフォを 2 つ作成する必要があるのか​​と疑問に思う人もいるかもしれません。spaceSem セマフォはスペース リソースと別のデータ リソースを表します。_cap から spaceSem を直接減算できますか? なぜ 2 つのセマフォを定義する必要があるのでしょうか。あなたが正しいです!ただし、減算操作はアトミックではなく、_cap から spaceSem を減算するプロセスはスレッドセーフではないため、セキュリティ上のリスクがあります。セマフォの元の PV 操作のみがアトミックで安全であるため、多くの操作を自分で増やすと非アトミックになる可能性が高く、すべてのセマフォがあり、他のセマフォ自体の操作はアトミックであるため、操作は次のようになります。スレッドセーフなので、さらにいくつかのセマフォを定義してはどうでしょうか? それは有益であり、無害です!

5.
セマフォを初期化するとき、最初に spaceSem をリング キューのサイズに設定し、dataSem を 0 に設定します。sem_init の 2 番目のパラメーターはスレッド間の共有を表します。つまり、spaceSem セマフォは実稼働スレッド間で共有されます。消費 dataSem セマフォはスレッド間で共有されます。
2 つの最も重要なインターフェイスは、Push と Pop です。Push を例に挙げます。まず、P 操作を実行し、spaceSem セマフォを適用します。アプリケーションが成功した後、ロック操作を実行する必要があります。プロデューサーはリングキューに相互に排他的にアクセスし、_productorStep の位置にタスク オブジェクトを挿入します。_productorStep は _cap を超える可能性があるため、%=_cap が必要です。その後、ロックを解放する必要があります。最後に、V が操作されると、データを生成した後はデータ リソースがないため、V は dataSem セマフォを操作することに注意してください。その後、dataSem が増加するはずです。Pop の動作は Push の動作とまったく逆で、最初に dataSem セマフォを申請し、最後に spaceSem セマフォを解放します。

以下の問題はインターフェース実装時に発生した問題で、図のコードはすでに最適化されています。
ここに画像の説明を挿入

ここに画像の説明を挿入

6.
以下は、単一生産および単一消費での動作状況です。単一生産および単一消費の場合、その演算結果は条件変数とよく似ていることがわかります。プロデューサーが sleep(1) の場合、印刷結果は非常にシーケンスがあるのですが、これはなぜですか?
実際、セマフォの実装原理は条件変数と同じですが、条件変数はウェイトとシグナルによってスレッド間の同期と相互排他を実現し、セマフォはウェイトによってスレッド間の同期と相互排他を実現します。 post 、 wait 、 post は実際にはセマフォの PV 操作であり、PV プリミティブでもあります。
したがって、セマフォは実際には条件変数 + リソースの準備完了状態の手動判断です。条件変数は他のスレッドをウェイクアップすることでハングの問題を解決し、セマフォは他のスレッドを間接的にウェイクアップすることでハングの問題を解決しますが、ここでのセマフォはウェイクアップする代わりに、他のスレッドのセマフォを解放します。つまり、V は他のスレッドのセマフォを操作します。V が他のスレッドのセマフォを操作すると、他の P 操作がまだスレッドをブロックしている限り、ブロックされません。彼らはすぐにセマフォを正常に申請し、ロックをめぐって競合し、クリティカル セクションに入ることができます。
ただし、以前の条件変数によって実装されたブロッキング キューとは異なり、以前のブロッキング キューはロックを使用していたため、いつでもシリアルにのみアクセスできますが、今日のリング キューは 2 つのロックを使用し、生成と消費の間に相互影響はありません。同時にロックを使用する理由がないため、効率が高くなります。生産と消費は同時に + 並列で実行できます。つまり、消費はロック cmutex をめぐって競合し、クリティカル状態になります。ゾーンからデータを取得する際、プロデューサーは pmutex を獲得するために競合し、クリティカル ゾーンに入ってデータを生成することもできます。生産と消費の間には唯一の相互排他が必要です。

ここに画像の説明を挿入

7.
以下は多重生産・多重消費の条件での印刷結果ですが、当然何も見えず、大量の消費スレッドと生産スレッドがそれぞれの生産・消費促進情報を必死に印刷しているだけです。
しかし、プロダクション間では _pmutex によってロックされていることは心の中で明確に認識できるため、プロダクションは相互排他的アクセスであり、消費についても同様です。さらに、セマフォを介して単一のプロダクションと単一の消費の間の同期を実現できます。排他関係により、データの競合、デッドロックなどの問題を回避できます。
(実際、私自身も当時、いくつかの問題を抱えていました。たとえば、生産者間でロックを奪い合った場合、飢餓の問題は発生しませんか?実際には可能ですが、飢餓の問題が発生する確率は非常に小さいので、これを無視してください。スターベーションの問題は、オペレーティング システムのスケジューリング ポリシーにより、一部のスレッドの実行時間が増加する可能性があるため、私たちが作成したコードではプロデューサー スレッドが適切にスケジュールされることが完全には保証されませんが、これはこのコードが直接の原因ではありません。言い換えれば、プロデューサー スレッドの飢餓問題は、私たちが作成するコードでは発生する可能性は低いですが、スレッド スケジューリングの公平性について厳密な要件がある場合は、条件変数やその他のより高度な同期メカニズムを使用して、条件を実現することができます。変数は、実際には非常に公平な同期メカニズムです。これにより、他のスレッドがウェイクアップするまで、すべてのスレッドがキュー内の条件変数で待機することができ、その後、ウェイクアップしたスレッドがロックを適用します。スターベーションの問題は発生しません。上で書いたコードでは、当分の間、運用スレッド間またはコンシューマ スレッド間の飢餓問題を考慮する必要はありません。)

ここに画像の説明を挿入

8.
最後のトピックは古いルーチンです。これは、当時ブロック化キューによって実装されていた生産および消費モデルによって提起された最後の疑問と同じであり、ここでもう一度復習することに相当します。リングキューに入るスレッドは、ほとんどの場合、1 つの生産と 1 つの消費にしか入ることができないため、複数の生産と複数の消費を作成する意味は何でしょうか? 実は理由は似ていて、タスクの配置とタスクの取得にはそれほど時間がかかりませんが、マルチタスクの場合はタスクの取得とタスクの実行に非常に時間がかかります。コンピューターでは、マルチタスクのシナリオが非常に一般的であるため、複数のスレッド間の調整が非常に必要です。生産消費モデルは効率的であり、タスクの取得と実行を行うスレッドが連携して複数のタスクを処理する場合、データの競合やデッドロックなどのセキュリティ上の問題が発生せず、同時にスレッドがタスクを消費したり生成したりすることもありません。タスクの取得や実行は他のスレッドに影響を与えるため、一般的にはマルチスレッドでも同時実行+並列でタスクの取得や実行を行いますが、マルチスレッドの安全性を確保するため、トレーディングプレイスを追加しました。共有リソースの安全性 マルチスレッドをマルチタスク シナリオに適切に適用できるように、マルチスレッドの相互排他関係と同期関係を維持します。

ここに画像の説明を挿入

2 番目に、スレッド プール

1. プーリングテクノロジーとスレッドプールモデル

1.
コンピュータはほとんどの場合マルチタスクの問題に直面しており、マルチタスク シナリオのマルチスレッド調整処理が一般的であるため、実際のスレッド プールを理解するのは難しくありません。タスク処理に応答する場合、スレッドを作成するということはpthreadライブラリのコードを実行することになるため、今からマルチスレッドを作成してタスクを処理すると手遅れになってしまいますか?Linux では、pthread ライブラリのコードが基礎となるシステム コールをカプセル化するため、ページ テーブルをカーネル レベルのページ テーブルに切り替え、コードをカーネル空間にジャンプしてカーネル コードを実行し、プロセッサ レベルを切り替え、など、どれも時間がかかりませんか?顧客が厳しいパフォーマンス要件を持っており、迅速に対応する必要がある場合、上記のスレッドを作成する方法は今からでは少し遅れます。したがって、スレッド プールのようなテクノロジーの本質は、実際には事前にスレッドのバッチを作成し、これらのスレッドにタスク キューにタスクがあるかどうかを継続的に検出させ、タスクがある場合は、特定のスレッドをウェイクアップして、このタスクを取得して実行させることです。そうでない場合は、スレッドをハングさせて待機させます。オペレーティング システムは常にサポートし、タスクがあるときにウェイクアップして、それを実行させます。
このようなプーリング技術の本質は、将来のニーズに応え、タスク処理の効率を向上させることです。

実生活では、このようなプール技術が不足することはありません。たとえば、疫病の流行中、誰もが物資を買いだめしました。なぜでしょうか? これは将来、誰もが外出できない、日用品を売る人がいなくなる、備蓄している物資を持ち出せるという厳しい感染状況への対応でもあるのではないでしょうか?では、物資を備蓄せず、感染症のロックダウンが最悪の状態になるまで野菜や肉を買いに出かけるのを待ったとしたら、手遅れではないでしょうか。または、いくつかのレストランに食事に行き、トマト入りスクランブルエッグが食べたいと上司に言うと、上司は問題ない、しばらく待ってください、村の入り口にある菜園に収穫に行くと言いました。トマトをいくつか食べて、それから私は鶏をスクワットするために養鶏場に行きます、彼女が卵を産んだ後、私はトマトと卵であなたのために料理します。ボスが料理を終えたら、あなたはずっと前にお腹が空いているでしょう!では、上司がこれをするのは遅すぎませんか?上司があらかじめトマトや卵を買いだめしておき、注文したら上司が直接取り出して調理してくれるというのが正しいやり方で、実は私たちの生活の中にテクノロジーが蓄積されているのです。
ここに画像の説明を挿入

2.
メモリプールもプーリング技術の現れであり、ヒープ領域を申請するために malloc や new を呼び出すと、実際の最下層は brk や mmap などのシステムコールを呼び出すことになり、システムコールの実行に時間がかかります。メモリ プールは、プログラムが必要に応じてメモリを迅速に割り当てたり解放したりできるように、一定数のメモリ ブロックを事前に割り当ててプールに保存します。これにより、プログラムのパフォーマンスが向上し、メモリの断片化が軽減されます。

ここに画像の説明を挿入
3.
スレッド プール モデルは、実際には生産および消費モデルです。スレッド プール内にスレッドのバッチを事前に準備して作成します。その後、上位層が対応するタスクをタスク キューにプッシュします。休止状態のスレッドが検出した場合、タスク キューにタスクがあると、オペレーティング システムによって直接起動され、タスクを消費して処理します。スレッドを起動するコストは、スレッドを作成するコストよりもはるかに小さくなります。
実際、次のスレッド プール モデルは、私たちが学習してきた生産および消費モデルではありません。これらのタスク スレッドはプロデューサー、タスク キューは取引場所、処理スレッドはコンシューマーです。つまり、ハイエンド スレッド プールの本質は、私たちが学習してきた生産および消費モデルからまだ分離されていないように思えます。そのため、スレッド プールの実装は、せいぜいスキルや詳細の点で以前よりも要求が厳しくなりますが、原理的には、生産モデルと消費モデルは同じではありません。

ここに画像の説明を挿入

2. 空腹と怠惰のための 2 つのシングルトン モード

1.
IT 業界では、大物と初心者の間に深刻な二極化が生じています。優秀な人は本当に素晴らしく、くだらない人は本当にくだらないです。そのため、大物は、古典的で一般的なアプリケーション向けのソリューションを考え出しました。要約すると、このような対象を絞ったソリューションはデザイン パターンです。
シングルトン パターンは、上司によって要約された、古典的で一般的に使用され、頻繁にテストされる設計パターンです。
シングルトン モードは、インスタンス化されたオブジェクトを 1 つだけ持つことができるクラスであり、このクラスをシングルトンと呼ぶことができます。通常、シングルトンを実装するには 2 つの方法があります。1 つは怠惰な人々の実装、もう 1 つは空腹の人々の実装です。
視覚的な例を挙げると、怠け者は食べ終わったらボウルを置き、次の食事まで皿を洗うのを待ちます。これが怠け者のやり方です。そして、お腹が空いた人は食べたらすぐに皿を洗うので、次の食事のために皿を洗う必要はなく、ボウルを手に取って直接食べる、これがお腹を空かせた人が実現する方法です。
怠惰な人は、人生が乱雑でずさんなので、まだ人生においてあまり良いとは言えません。しかし、コンピュータでは、怠惰な方法が依然として有効です。怠惰な方法の中心的なアイデアは、読み込みを遅らせることです。この方法により、サーバーの速度を最適化できます。つまり、必要なときにサーバーを割り当てます。場合今は必要ないので、最初に割り当てないでください。これは遅延読み込みです。

2.
Hungry Man のような方法は、実際には非常に一般的です。遅延読み込みの管理アイデアは、実際にはソフトウェアおよびハードウェア リソース マネージャー OS の優れた管理方法であるためです。通常の malloc や new と同様に、オペレーティング システムの最下層がスペースを空けるとき、実際には飢えた人々のためにスペースを開くのではなく、怠惰な人々のためにスペースを開きます。アプリケーションが適用されるメモリ空間にプログラムが実際にアクセスして使用すると、ページ フォールト割り込みがトリガーされ、オペレーティング システムがこれを認識した後、物理メモリ上に対応するアプリケーション サイズの領域が実際に開かれます。仮想および物理マッピング関係を再構築し、対応する仮想アドレスを返します。

ここに画像の説明を挿入

3.
Hungry Man が従う原則の 1 つは、ロード時間は開発時間であるということですが、これは何を意味しますか? つまり、クラスがロードされるとき、そのクラスのシングルトン オブジェクトは仮想アドレス空間にすでに存在しており、物理メモリ内にはシングルトン オブジェクトが占有するメモリ空間もあります。実装も比較的簡単で、事前にクラス内に静的オブジェクトを作成してプライベート化するだけです。もちろん、この静的オブジェクトは、このシングルトン クラスの唯一のオブジェクトでもあります。オブジェクトの一意性を実現するには、次のことが必要です。コンストラクターをプライベート化するには、コピー構造を削除し、代入によってメンバー関数がオーバーロードされます。シングルトン オブジェクトをクラス外で使用する場合は、静的メソッド名にクラス名を加算することでシングルトン オブジェクトのアドレスを取得し、クラスの他のメンバー メソッドにアクセスします。
怠け者が従う原則の 1 つは、必要なときはそれが開発されるということです。これは何を意味しますか? つまり、クラスがロードされるときに、クラスのシングルトン オブジェクトは自動的に作成されませんが、GetInstance() インターフェイスを呼び出すと、シングルトン オブジェクトのヒープ領域が実際に割り当てられます。これは典型的な遅延実装です。 。(右側の怠惰な人によるシングルトン モードの実装はスレッド安全ではありません。この安全でない問題を解決するには、遅延バージョンを実装するスレッド プールにそれを入れます。スレッドセーフ バージョンの怠け者が実装されました。)

ここに画像の説明を挿入

3. シングルトン モードのスレッド プール コード (スレッド セーフな遅延実装バージョン)

1.
以下で実装したスレッド プールは、実際には独自のタスク キューを持つスレッド プールであり、内部に多数のスレッドが作成され、Push インターフェイスを呼び出すことで外部タスクをスレッド プール内のタスク キューにプッシュできます。タスク実行中は常に独自の条件変数で待機し、上位層がプッシュ インターフェイスのプッシュ タスクを呼び出すと、スレッド プールによって実装されたプッシュ インターフェイスがシグナルを呼び出し、プッシュ タスクの後に条件変数で待機しているスレッドをウェイクアップします。スレッドが目覚めると その後、タスクキュー内のタスクが飛び出して実行されますが、これが実際の消費処理となります。また、スレッド プールのシングルトン バージョンを実装したいため、シングルトン オブジェクトのアドレスを取得する getInstance インターフェイスも提供する必要があります。また、外部はオブジェクト ポインターを介して ThreadPool クラスのプッシュ インターフェイスを呼び出して、タスク。
作成したスレッドの管理にはvectorを使用し、タスクキューにはqueueを使用しますが、タスクキューはコンシューマーとプロデューサーの両方からアクセスされるため、タスクキューも保護する必要があるため、タスクキューを確保するためにミューテックスを使用します。 , さらに、スレッド プール内のスレッド数を表す変数 num を定義します。スレッドはタスクがない場合は待機する必要があるため、cond も必要です。スレッドセーフな遅延シングルトン モードを実現するには、静的ポインタ tp を定義するだけで済みますが、複数のスレッドが同時に getInstance に入り、複数のオブジェクト インスタンスを作成する可能性があるため、静的ポインタの安全性を確保するためにミューテックス singleLock も必要です。ただし、このミューテックスには pthread ネイティブ スレッド ライブラリのミューテックスは使用されなくなり、C++11 スレッド ライブラリのミューテックスを使用してミューテックスを定義します。

2.
A.コンストラクターでは、スレッド数を初期化し、対応する数のスレッドを作成し、各スレッド オブジェクトのアドレスをベクトルにプッシュバックする必要があります。また、 cond と mutex はローカルであるため、初期化する必要があります。C++11 のように各スレッド オブジェクトを管理するために、以前にカプセル化された RAII スタイルのスレッド クラスを使用することに注意してください。そのため、スレッド プール オブジェクトが構築されると、スレッド オブジェクトの構築中に各スレッド オブジェクトも構築されます。スレッドが実行され、対応するスレッド関数が実行されます。これは RAII スタイルのスレッド作成です。オブジェクトが作成されるとスレッドが実行され、オブジェクトが破棄されるとスレッドも破棄されます。つまり、オブジェクトの作成時にリソースが取得および初期化され、リソースはオブジェクトが破壊されると解放され、リサイクルされます。
B.デストラクターについては、スレッド プール オブジェクトが破棄されるときに、destroy cond と mutex も破棄する必要があり、他のメンバー変数コンパイラーはそれぞれのデストラクターを呼び出すため、心配する必要はありません。
C.したがって、スレッド関数を実装する必要があります。スレッド プール オブジェクトが初期化されると、スレッドはスレッド関数を実行するために実行されるためです。スレッド関数は実際にタスクを実行するためのものであるため、スレッド関数の名前は handler_task であり、 handler_task の実装には、実際にパラメータを渡す必要があります。解決すべき最初の問題は、実際にパラメータを渡すことです。handler_task がクラス メンバ関数の場合、そのパラメータ リストは this ポインタを暗黙的に示すため、RAII スタイルのスレッド コンストラクタを呼び出すと、パラメータ不一致エラーが発生します。方法も非常に簡単で、handler_taskを静的メンバー関数として設定しておけばパラメータを渡す手間が省けます。
handler_task を実装する最初のことは、実際にロックすることです。タスク キューへのアクセスのセキュリティを確保する必要があるため、ロックする必要があります。また、タスク スレッドと処理スレッド間の同期を実現するために、次のことも必要です。条件変数で待機し、ウェイクアップされると、タスク キュー内のタスクを取得して実行しますが、上記のすべての操作はクラス メンバー変数にアクセスする必要があり、handler_task は静的メソッド、静的メンバーです。非静的メンバーにアクセスできず、スレッド オブジェクト内でリターンがある スレッド名インターフェイスは threadname() と呼ばれます スレッドがタスクを実行しているときに、どのスレッドがタスクを実行しているかを確認したいので、タスクを実行する前に threadname() インターフェイスを呼び出します。上記の操作を実現するには、スレッド関数に構造体 threadText を渡す必要があります。この構造体には、スレッド オブジェクト ポインターとスレッド プール オブジェクト ポインターが含まれています。これら 2 つのポインターを含む構造体を渡すことにより、以上で一連の操作が完了します。クリティカル セクションの粒度が十分に小さいことを確認する必要があるため、タスクの実行、つまり呼び出し可能なタスク オブジェクト CalTask​​ の () オーバーロード関数の呼び出しは、クリティカル セクションの外側に配置する必要があります。クリティカル セクションは次のような理由からです。タスクが取得されたので、タスクキューを保護します。 タスクキューが出てきた場合は、ロック保護を追加し続ける必要がないため、t() はクリティカルセクションの外側に配置する必要があります。ロック操作に関しては、クラス内の一連のインターフェイスをカプセル化する独自の使用に加えて、LockGuard.hpp を直接呼び出すこともできます。これも RAII スタイルのロックです。つまり、オブジェクトの作成時に初期化されます。 、オブジェクトが破壊されると自動的に解除されます。
D.次に、プッシュ インターフェイスがあります。プッシュ インターフェイスでは、RAII スタイルのロックを使用していることがわかります。コード ブロックを離れると、ロック オブジェクト lockGuard が破棄されます。このとき、ミューテックスは自動的に解放されます。タスクがキューにプッシュされた後、処理スレッドを起動することができ、スレッドは cond 待機キューから起動し、プロデューサによって生成されたタスク
Eを実行するように再スケジュールされます。実装する必要がある最後のインターフェイスはシングルトン モードのみです。 getInstance() は複数のスレッドによって再入力される可能性があり、シングルトン モードに準拠しない 2 つのオブジェクトが構築される可能性があるため、それが破棄される可能性があります。また、メモリ リークの問題もあるため、getInstance() インターフェイスをロックして、1 つのスレッドだけが getInstance に入ってシングルトン オブジェクトをインスタンス化できるようにする必要があります。特定のスレッドがシングルトン オブジェクトをインスタンス化した後、残りのスレッドはすべてのスレッドが getInstance に入ったときに、 if 条件は満たされませんが、後続のスレッドが getInstance に入った場合、if 条件を判断する前にロックを適用する必要があるため、効率が少し低くなります。効率を向上させるために、null ポインタを直接二重判断します。判断の際、後続のスレッドはロックを適用せずにシングルトン オブジェクトのアドレスを直接取得できます。これにより効率は向上しますか?
さらに、シングルトン オブジェクトのアドレスを宣言するときは、 volatile キーワードを使用してアドレスを変更する必要があります。一部のコンパイラ最適化シナリオでは、値の読み取りのみが原因である可能性があるため、volatile キーワードはメモリの可視性を維持するために使用されることがわかっています。レジスタの値を読み取らないと判断エラーが発生し、予期せぬ問題が連続して発生するため、このような問題を回避するために、volatile キーワードを使用してシングルトン オブジェクトの静的ポインタを変更することにしました。(10 個のスレッドすべてがシングルトン オブジェクトのアドレスを取得したいと仮定すると、_tp はコード内で使用されていないため、コンパイラーは _tp から始まる値を nullptr としてレジスター、つまり、現在の CPU スレッド このうち、スレッドが以前に新しいシングルトン オブジェクトを渡したことがある場合、現在の CPU は _tp が nullptr かどうかを判断するときに、物理メモリの値を取得せず、レジスタの値を判断することを選択します。オブジェクトは初めてインスタンス化されるため、_tp を変更するには volatile キーワードを使用する必要があります。)
さらに、コピー構築とコピー代入の 2 つのメンバー関数などのメンバー関数を削除する必要があります。単一インスタンス オブジェクトの 2 番目のインスタンス化が発生する可能性があります。

ここに画像の説明を挿入

3.
以下は、RAII スタイルのカプセル化されたスレッドの作成、結合、および破棄の小さなコンポーネント Thread.hpp です。pthread_create を呼び出すときに、this ポインターがパラメーターと一致しないという問題も発生しました。現在でも次のメソッドを使用しています。これを解決するには、クラス メンバーを静的に変更します。はい、もちろん、静的メソッドでも同じ問題が発生します。つまり、this ポインターがないと他のクラス メンバー関数に転送できないため、同じです。 this ポインタとスレッド関数のパラメータを保存し、その構造体を配置するための構造体。ポインタはスレッド関数に渡され、スレッド関数は実際にそれを解凍し、this ポインタを取り出し、メソッド内で handler_task メソッドをコールバックします。スレッドの実行を処理するためにラッパー _func によってラップされたスレッド プール。
構造体と start_routine を除く他の関数は、pthread ライブラリのネイティブ インターフェイスを単純にカプセル化したものです。この RAII スタイルのスレッド管理ウィジェットは実装が比較的簡単です。

ここに画像の説明を挿入

4.
以下は旧知の話ですが、私たちが実装したブロッキングキュー版とリングキュー版の生産モデルと消費モデルは、このタスクコンポーネントを使用しています。このタスクコンポーネントは、タスクオブジェクトを構築し、タスク名を返す関数と、呼び出し可能なオブジェクトの () オーバーロードについては、詳細には触れませんので、見てみましょう。

ここに画像の説明を挿入
5.
以下は、RAII スタイルでロックとロック解除を行うための小さなコンポーネント LockGuard.hpp です。ロックは外部から渡され、コンポーネントはロックとロック解除の作業を実行します。次の実装は多層を実行しています。カプセル化の 1 層だけで、ロックとロック解除の RAII スタイルの操作も実現できます。

ここに画像の説明を挿入

6.
以下は、シングルトン オブジェクトのアドレスを取得し、そのアドレスを使用して Push インターフェイスを呼び出し、タスクをプッシュする上位レベルの呼び出しロジックです。特に言うことはありませんが、ビルドする方法を実装しました。コマンドラインを使用したタスク。

ここに画像の説明を挿入

3. スピンロック

1. スピン ロックと保留中の待機ロック

1.
先ほど説明したミューテックス、セマフォ、条件変数などの相互排他と同期のメカニズムに加えて、悲観的ロックや楽観的ロックなど、さまざまなロックがありますが、これらのロックはロックの役割の一部にすぎません。この概念的な用語は非常に一般的です。さらに、悲観的ロックの実装: CAS 操作とバージョン番号メカニズムについては、実際には少し知っておくだけで十分です。主にミューテックス セマフォと条件変数を使用します。現時点では、これらで十分です。
しかし、スピン ロックと読み書きロックについては、さらに詳しく知る必要があります。通常、このようなロックはあまり使用しませんが、これらは習得する必要があるカテゴリに属します。スピン ロックと読み書きロックを理解した後、それらは基本的には十分です。

ここに画像の説明を挿入
2.
以前に学習したミューテックスとセマフォについては、アプリケーションが失敗すると、スレッドはブロックされ、一時停止されます。スレッドは PCB によって維持される待機キューで待機する必要があるため、このようなロックを一時停止待機ロックと呼びます。ロックが解除されるまで。
スピン ロック アプリケーションが失敗した場合、スレッドはハングして待機せず、スピンすることを選択し、ロック ステータスがループ内で解放されるかどうかを確認します。この方法では、スレッド コンテキストの切り替えによって引き起こされるパフォーマンスのオーバーヘッドを軽減できますが、同時にtime スレッドが常にロックをポーリングしている CPU のステータスを占有しており、CPU は他のスレッドをスケジュールできないため、CPU リソースの無駄が発生します。
したがって、一時停止された待機ロックとスピン ロックを使用する主な基準は、スレッドが待機する必要がある時間の長さ、またはロックを適用したスレッドがクリティカル セクションに留まる時間の長さです。時間が長い場合は、待機中のロックを一時停止することを選択します。重要なリソースをロックして保護する方が適切です。時間が短い場合は、スピン ロックを選択してロックの状態を継続的にポーリングし、スピン ロックを使用して重要なリソースを保護する方が適切です。資力。

3.
次の質問は、時間の長さをどのように測定するかです。より適切なロック方式を選択するにはどうすればよいですか?
実際には、時間の長さに対する答えはありません。時間の長さは比較する必要がある結論であり、実際にどのようなロック スキームを選択するかは特定のシナリオの要件に依存するためです。
一般的に、複雑な計算や IO 操作を実行し、クリティカル セクションで特定のソフトウェア条件が準備できるのを待ちたい場合は、保留中のロックを使用する可能性が高くなります。チケット取得ロジックなどの非常に単純な操作のみが実行され、クリティカル セクションのコードを迅速に実行できる場合は、スピン ロックを使用する方が適切です。
しかし、実際には、スピン ロックの方が高速に見えるため、ほとんどの場合、まだ保留中のロックを使用していますが、一度時間の評価が間違っていると、スピン ロックを適用するスレッドが大量の CPU リソースを消費することになります。タスク処理の効率が低下します。さらに、スピン ロックがデッドロックになった場合、問題はハングしてロックを待機するよりも深刻です。スレッドがミューテックスを適用するときにデッドロックが発生した場合、実行フローがブロックされて実行されなくなるという大きな問題がありますが、CPU は正常です。スピン ロックでデッドロックが発生すると、スピン ポーリング ロックの状態が永続的にスピン ポーリングされ、CPU から剥奪されないため、CPU リソースが常に占有されて解放できなくなるという問題があります。とても深刻です!
もちろん、どのソリューションを選択すればよいかわからない場合は、まずデフォルトで一時停止待機ロックを使用し、次に一時停止待機ロックとスピン ロックの効率を比較して、より高い方のソリューションを選択できます。

4.
スピン ロックの操作は難しくありません。これらのロックは POSIX 標準を使用しているため、取扱説明書を指示するだけで非常に簡単に使用できます。

ここに画像の説明を挿入

2. スマート ポインターと STL コンテナーはスレッドセーフですか?

ここに画像の説明を挿入

4. 読み書きロック

1. リーダライタモデル(321原則)

1.
生産と消費のモデルに加えて、リーダー/ライター モデルという非常に古典的なモデルもあります。リーダー/ライター モデルを実現する本質は、実際には 321 原則を維持することです。リーダーとライター間、およびライター間、および 1 つの取引場所。この取引場所は通常、配列、キュー、またはその他のデータ構造などです。
リーダーはコンシューマーに相当し、ライターはプロデューサーに相当しますが、リーダーとコンシューマーの最も基本的な違いは、リーダーはデータを持ち去らない、つまりデータを消費しないことであるため、リーダーは相互に排他的ではありません。はデータを読み取り、変更操作を実行しないため、複数の読み取り者が共有リソースを読み取っても、共有リソースにはセキュリティ上の問題が発生せず、誰も共有リソースに触れることがありません。したがって、リーダー間に関係はありません。また、コンシューマーが相互に排他的な関係を持つことは望ましくありません。各コンシューマーが共有リソースを変更する必要があるためです。しかし、私のリーダーはこれを行わず、私は変更せずにただ読むだけです。
また、ライターはすべて共有リソースに書き込むため、ライター間には相互排他的な関係がなければなりません。したがって、それらは相互に排他的であってはなりません。そうしないと、共有リソースに問題が発生した場合はどうなるでしょうか。リーダーとライターは相互に排他的であり、同期されます。リーダーが読み取り中にライターは書き込みを行うべきではありません。そうしないと、リーダーが読み取ったデータがライターによって上書きされます。ライターが書きに来たら、読者は読みに来ないでください、あなたが読んだデータは不完全だから、読んでみませんか! したがって、これらは相互に排他的ですが、リーダーが書き込みを終了したときに、データを更新したい場合は、ライターにそれを書き込ませる必要があります。同様に、ライターが書き込みを終了したら、リーダーが を読み込む必要があります。したがって、リーダーとライターの関係は相互排他的かつ同期的です。相互排他により重要なリソースの安全性が確保され、タスク全体の処理を完了するには同期調整が必要です。

2.
一般に、リーダー/ライター モデルの使用に適しているのはどのようなシナリオですか? 例えば、一度データを公開すると、そのデータは長期間変更されず、ほとんどの時間読まれることになります。時間?ブログが間違っていた場合に限り、ブログを再編集することもありますが、ほとんどの場合、ブログは読まれています。あるいは、メディアがニュースを発表しますが、ニュースが発表されると、ほとんどの場合、そのニュースも読まれますが、掲載されたニュースが修正されることはごく一部であり、修正されないこともあります。したがって、このようなシナリオでは、リーダー/ライター モデルを使用する方が適切です。

3.
生産および消費モデルを実装する場合、通常、cond mutex セマフォを介してブロックキューまたはリングキューの生産および消費モデルを実装します。
リーダーライターモデルを実装する場合、対応するメカニズムが必要ですか? pthread ライブラリは、読み取り/書き込みロックの初期化と破棄スキームを実装し、リーダー スレッドとライター スレッドのそれぞれに対するロックの実装と、リーダーとライターの統合ロック解除の実装も実装します。

ここに画像の説明を挿入

—これまでに学習した mutex cond sem pin rwlock は、ほとんどのニーズを満たすことができます。

2. リードロックとライトロックの適用原理(リードロック共有、ライトロック相互排他)

1.
以下の表は、読み取りおよび書き込みロックが要求されたときの他のスレッドの動作をまとめたものです。
複数のリーダーが同時に読み取りロックを取得でき、読み取り操作を同時に + 並行して実行できることは注目に値します。これは、読み取り/書き込みロックのセマンティクスを設計するときの設計です。これにより、複数のリーダーが読み取り/書き込みロックを共有できるようになります。読み取り操作を並行して実行します。これは、読み取り/書き込みロックの設計セマンティクスです。
書き込みロックの場合、これは一般的なミューテックス セマンティクスです。

ここに画像の説明を挿入

2.
読み取り/書き込みロックの原理は次のとおりです。1 つのリーダーだけがロックを申請すると、ライターのロック wrlock が奪われます。したがって、複数のリーダーが読み取りを行うと、reader_count カウンタは常に増加します。がデータを読み取っている場合、wrlock を適用できないため、ライターは常にブロックされます。ライターは、書かれたコード ロジックを実行できず、自身のロック (&wrlock) コードでブロックされます。ただし、rdlock はリーダー間で共有できます。すべてのリーダーが読み取りを終了した後、つまり、reader_count が 0 になったときに、リーダー スレッドは wrlock を解放します。このとき、ライターは書き込み用の wrlock を申請できます。
ライターが最初にロックを申請した場合、リーダーはそれを読み取ることができますか? もちろん違います!ライターが書き込みコードを実行するためにロックを申請すると、最初のリーダーはロック (&wrlock) コードでブロックされます。これは、この時点で wrlock がライターによって奪われているためです。リーダーがそれを取得しようとすると、掴めないならブロックするしかない。
ただし、wrlock は rdlock とは異なり、wrlock は共有されないため、wrlock を申請したい場合は、リーダーと同様にブロックされます。
これは、リーダーが読み取るとき、ライターは書き込むことができず、リーダーは読み取りロックを共有できる、ということです。ライターが書いている間、リーダーも他のライターも独自のコードを実行し続けることはできません。

ここに画像の説明を挿入

ここに画像の説明を挿入

3. リーダー/ライターの優先順位 (デフォルトのリーダー優先順位)

1.
最後にお話しするのは、読み手と書き手の優先順位です。上記で実装した疑似コードは、実際にはデフォルトでリーダーファーストです。
次に、リーダーが多すぎると仮定して、コードロジックの実行時に最初のリーダーがすでに書き込みロックを取得しているため、後から何人のリーダーが来ても、ライターは書き込みを実行できないのではないかと誰かが尋ねるでしょう。書き込み者が rwlock を適用できない場合、自分のコードのクリティカル セクションに入ることができなくなります。書き込み者のスレッドが飢餓状態になる可能性はありますか?
もちろん可能です!しかし、ライター スレッドの枯渇の問題はごく普通のことです。リーダー/ライター モデル自体は、ほとんどの時間を読み取りと書き込みに費やしているのはほんの一部であるため、ライター スレッドの枯渇はすでに普通のことです。
デフォルトでは読者を優先しますが、読者が次々に来ると、ライターであるあなたも待ち続けることになります。

2.
ライターを優先したい場合はどうすればよいですか? たとえば、10 人のリーダーが読み取りを希望し、現在 5 人のリーダーが読み取りのクリティカル セクションでコードを実行している場合、ライター スレッドは、次の 5 つのスレッドが読み取りのクリティカル セクションでコードを実行し続けるのを防ぎます。最初の 5 個を読んだ後、reader_count は 0 になります。このとき、ライターは rwlock を適用したいと考えています。私が最初に書き込みます。書き終わると、次の 5 人のリーダーが読みに来ます。
原則はおそらく上記と同じですが、書き手優先戦略は書くのがより難しいため、ここでは書きません。このトピックではまず読み手と書き手が存在することを知っておいてください。

以下は、読み取りおよび書き込みの優先順位を設定するためのインターフェイス pthread_rwlockattr_setkind_np() です。np は、非移植性と非移植性を指します。

ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/erridjsis/article/details/130547353