≪ユーザー・ステータスの同時実行|User Status Sync|ldap≫:≪イベント・ドライバ|Event Drives|ldap≫


前編の末尾に間違ったところがあります.時間割スケジューリングのyieldプロセスは次のとおりです.
 
env.running_queue.add(this); 
this.stat = STAT_YIELD; 
return; 

これをrunning_に追加する必要がありますqueue、さもないとこのスレッドは死んでしまいます
 
時間分割スケジューリングがあれば、計算が密集したプログラムの同時実行を実現することができるが、ほとんどのプログラムは明らかにそうではない.プログラムは多少ブロック待ちに入る.例えば、IO、ロック、sleepなどである.これらはイベントと統一されている.ブロックは実際にはイベントを待っている.
 
簡単に言えば、ユーザー状態のイベント駆動は、イベントテーブルを作成することであり、各イベントには待機スレッドのリストがあり、仮想マシンのメインサイクルによってイベントがトリガーされるかどうかを検出し(前編コードの注釈部分にある)、トリガーされると待機スレッドにスケジューリングされ、具体的なスケジューリングは簡単でrunning_に移動する.Queueでいいです.具体的には、イベントは次のように分類されます.
 
一、仮想マシン内部で発生したイベント
 
このようなイベントは仮想マシンが実行するとき、内部のいくつかのメカニズムで発生します.このようなイベントは一般的に仮想マシンのメインサイクルで自発的に検出する必要はありません.発生するときはenvを直接操作し、スケジューリングが必要なスレッドをrunning_に追加します.Queueでいいです.最も典型的な例は前編で実現しました.スレッドのjoin待機は、待機しているスレッドが終了したときに、待機している他のすべてのスレッドをアクティブに通知することです.残りの類似イベントには、信号量などのカーネルオブジェクトもあります(このカーネルはもちろん仮想マシンを指します).
 
P.S.スレッドjoinについては、前編の実装に問題があります.例えば、Aスレッドが先に終了し、Bスレッドjoin Aが結果を得るべきです.そのため、前編の実装は簡単です.スレッドの結果は他のスレッドjoinに供給される場所があるはずです.joinの場合、joinのスレッドが終了したら、すぐに戻るべきです.そうしないと、ブロックがイベントを待つことになります.
 
二、タイマーイベント
 
スレッドが実行中に一時停止する必要がある場合は、タイマイベントを待つ必要があります.具体的には、タイマキュー(一般的には優先キュー、hashホイールなどのアルゴリズム)を作成し、スレッドが一時停止する必要がある場合は、タイマ、yieldを追加し、仮想マシンのメインサイクルで現在タイムアウトしているタイマを検出し、対応するスレッドにスケジューリングします.タイマーの1つの問題は、仮想マシン自体の動作が遅いため、時間割スケジューリングの粒度も大きい可能性があるため、正確ではありません.もちろんosのsleepも実際には正確ではありません.この問題は許容範囲内に制御すればいいです.
 
三、中断事件
 
一般的な意味の中断は一般的にハードウェア層からオペレーティングシステムを指すが、ここでは仮想マシンについて議論し、このようなイベントは1つしかない:信号.SIGINTなどの仮想マシンプロセスが信号を受信した場合、仮想マシン自身は信号の業務処理を担当するのではなく、ソース言語で信号handlerを登録するが、仮想マシンは信号が正常な動作を中断できないことを保証する必要がある.具体的には、信号を受信した後、仮想マシンは処理対象の信号テーブルに記録し、メインサイクルで検出し、前の仮想タイムスライスが信号を受信した場合、プライマリ・スレッドの実行をスケジュールし、プライマリ・スレッドに対応するhandlerを直ちに実行させる(handlerでプライマリ・スレッド環境が使用されるため、仮想マシンが直接handlerを調整することはできません).
 
なぜメインスレッドで実行しなければならないのか疑問に思う人もいるかもしれませんが、実際には異なるプラットフォームがマルチスレッドプログラムに対して信号を受信する実現は異なり、あるプラットフォームはメインスレッドを中断し、あるプラットフォームはランダムdeliverが1つのスレッドに与えられ、具体的な仮想マシンでは、メインスレッドの処理が比較的合理的であるように設計されています.
 
四、IOイベントをブロックする
 
あるスレッドが低速IO(高低速IOの概念参照APUE)を操作する必要がある場合、例えばsocketの読み書きなどがブロックされる可能性があります.この場合、スレッドがブロックされる可能性がある場合はyieldを先に行い、IOイベントが来てから実行を続ける必要があります.これは少し複雑です.
 
まず、socketからデータを受信するなど、すべてのブロック可能なIO操作を非ブロックに変更する必要があります.
 
data, error += recv_from_socket(s, need_len); 
if (error == EWOULDBLOCK) 
{ 
    // t , Thread  
    env.io_event_table.add(t, s, EVENT_TYPE_READ); //  
    ... //yield; 
} 

その後、仮想マシンのメインサイクルではすべてのIOイベントを監視する必要があります.IOイベントは最下位のオペレーティングシステムから来ており、一般的にselectまたはepollで監視することができます.イベントが到着すると、対応するスレッドにスケジューリングされます.
 
IOイベントについては、実際には上記のように実現するのは面倒ですが、別の角度から考えると、IO操作はシステム呼び出しであり、仮想マシン上を走るプログラムにとって、「システム」とは仮想マシンです.そのため、仮想マシン上で世代を作ることができます.アプリケーション層のIO要求はエージェント層によって完了し、アプリケーション層に戻ります.これにより、エージェント層は合理的なパッケージを作ることができます.例えば、
 
send(s, data); 

これがosでのシステム呼び出しであると仮定すると、現在どのくらいのデータを送信できるかはまずどのくらいのデータを送信することができ、sendの戻り値は実際の送信量であり、実際にはdataを送信することを望んでいます.一時的に送信できないと、ブロックされます.sendallが必要です.
 
void sendall(Socket s, String data) 
{ 
    while (data.size() > 0) 
    { 
        int send_len = send(s, data); 
        data = data.substr(send_len, data.size()); 
    } 
} 

エージェント層はこれを行うことができ、手動でsendallを実装するのとは異なり、仮想マシンはsendがブロックされる可能性がある場合に自分でスケジューリングします.同様にrecvallなどのニーズがあり、ソース言語のプログラム設計に便利さをもたらすことができます.具体的には、1つのスレッドが仮想マシンにxxxバイトのデータを送信(または受信)することを伝え、仮想マシンがこのことを完了した(または途中でエラーが発生した)後に結果をスレッドに通知し、仮想マシンエージェントIOによって、もちろんエージェントの方式は自由に設計することができ、例えば別の真のスレッドを開いてやることができる.別のIO真スレッドを開くことで、仮想マシン設計の結合度を低減し、データ送受信のタイムアウトなども容易に実現でき、仮想マシン自身のタイマと混同することはありません.
 
上記の4つのイベント駆動を解決すると、機能は基本的に完全になりますが、いくつかの重要な詳細な問題があります.
 
一、仮想マシン自体もブロックする必要があります.そうしないと、1つのプログラムがCPUを走ると100%になります.ほとんどの時間はポーリングしてイベントをチェックしています.受け入れられないに違いありません.仮想マシンのブロックはソース言語のブロック論理と一致する必要があります.具体的には、すべてのスレッドが待機イベント状態に入ったときだけ、仮想マシンがブロックされます.ブロックの実現は一般的にselectやepollで、ブロック時間は最近のタイマーによって決まります.
 
二、スレッドjoin、信号量などのメカニズムは仮想マシン層でオペレーティングシステムをシミュレートする(効率を高めるため)ため、対応するいくつかのデッドロック検出も仮想マシンで行わなければならないので、検出アルゴリズムはオペレーティングシステムの原理を参考すればよい.
 
三、驚きの群集効果、複数のスレッドが同じイベントを待つ場合、具体的な状況に応じてすべて起動するか、それとも起動するかを決定する必要がある.例えば、スレッド終了イベントはjoin中のすべてのスレッドにブロードキャスト通知すべきであり、反発ロックのロック解除イベントは待機中の1つのスレッドにしか通知できない.
 
四、上の第三条とは逆に、一つのスレッドが同時に複数のイベントを待つ場合があり、最も典型的な例は、一つのスレッドrecvやsendの場合、タイムアウト時間を指定したり、ロックを待っている場合にタイムアウト時間を指定したり、ソース言語がselectなどのインタフェースを提供したりして、複数のIOイベントを監視したりすることである.
 
複数のイベントが競合している場合は、ロックタイムアウトの要件などのイベント関連付けによって、ロックオブジェクトの待機とタイマを登録し、イベントマネージャで1つのグループで2つを関連付けることができます.そのうちの1つがトリガーされると、別の待機キュー列から対応するスレッドを削除し、問題を回避します.
 
selectなどの複数のイベントが競合しない場合、selectが戻ってきたときに現在準備されているIOイベントをできるだけ多く知りたい場合は、すべてのイベントを検出した後に要約し、イベント関連付けに基づいてスレッドをスケジュールする必要がある場合があります.
 
しかし、言語がユーザ状態でスケジューリングされている場合、selectは一般的に使用されることも少ない.IOイベントごとにスレッドを開いて監視することができるため、どうせユーザ状態のスレッドは非常に安価であり、複数のIOイベント間に関連があれば、スレッド間通信によって実現することもできる(仮想マシンは類似のブロックパイプを実現し、IOイベントと結合することができる)
 
ここの3、4はまた組み合わせて、例えば1つのスレッドがsocket sa、sbの読み取り可能なselect、もう1つのsbを読んでいる場合、sa、sbが同時に読める場合、どのように通知すればいいのか、これは実現に関する問題かもしれないが、1つのsocketが2つのスレッドで同時に操作されること自体が未定義の行為を生む可能性があり、一般的には避ける必要がある.
 
実は私はもともと同時関連の内容を書くつもりはありません.大部分がオペレーティングシステムに関連しているので、オペレーティングシステムの原理を見て、GILなどの技術を使って、言語の実現の中でマルチスレッドを実現するのも難しくありません.しかしマルチスレッド環境はGCのようなメカニズムの複雑さを招く可能性があり,同様の点は単一スレッドのみを前提として議論するつもりであるため,ユーザ状態の同時化について述べ,言語実装の同時化は宿主のマルチスレッド環境に完全に依存せず,言語実装の簡略化案といえる.