Luaでプリエンプトマルチスレッドを実現
2929 ワード
Lua 5.2の開発進捗は2010年1月にさかのぼることができる.長い流れは今日で2年近く経ち、ついにbeta版まで待った.2011年中に正式に発表されることを期待しています.この何度も振り回された2年間、多くの新しい特性が5.2版に押し込もうとしたが、最終的に拒否された.
改善リストを見ると、新しいものはあまり見えないようです.しかし、ソースコードをよく読むと、表面的にはあまり修正されていないように見えるように、ほとんどの場所が再実現されていることがわかります.Luaを十分に理解すれば、今回の最も感動的な改善は「yieldable pcall and metamethods」であることがわかります.公式にもMain changesの第1条とされている.言語上の重大な新しい特性gotoは末尾に列挙されている.
もちろん、これは私の浅い理解にすぎません.実践を経て5.2をしばらく使わなかったが、このような論断は少し軽率すぎる.しかし、この改善が私たちの開発に何をもたらすかについてお話ししたいと思います.
coroutineのyieldは今ではほとんどどこでも使用できます.私がほとんど使ったのは、まだいくつかの制限があるからです.これらの制限は簡単には言えませんが、その制限を理解するために、lua 5.2 beta版のソースコードを読むのに一日中かかりました.この話題は今度また別のblogを書いてまとめます.今日は応用についてだけ話します.
coroutineは協同マルチスレッドモデルを実現できることを知っています.すなわち、各スレッド(coroutine)は、ユーザの所望の場所でのみ飛び出し、後から跳ね返すことができる(当初のジャンプ状態を維持する).これにより、多くのプリエンプトマルチスレッドのトラブルが解決されます.luaの発明者はインタビューでcoroutineが同時の問題を解決することについて話した.しかし、ユーザーが嫌がらずにyieldを書くのは嫌なことです.フレームワークはyield呼び出しを隠すことが多い.もし私が間違っていなければ、私が読んだ初期のkeplerフレームワークはyieldをhtmlの出力に隠していました.もっときれいな方法はlua debug hookを作成し、hookでyieldを呼び出すことです.これによりlua vmが何行か走るたびにlua byte codeが自動的にyieldを作ることができます.
残念なことに、lua 5.1以前のバージョンではこれはサポートされていません.yieldの制限が多すぎるからです.ちょうどyieldがmetamethod呼び出しの内部またはpcallの内部で発生した場合、失敗します.これはC/Lua関数のネストと関係がある.lua vmはpcallおよびmetamethodメカニズムを実現する際に,C関数およびlua関数間で絶えずジャンプする.yieldが多層C関数とlua関数のネスト呼び出しの内部で発生すると.longjmpで返されるyieldメカニズムでは、C関数呼び出しのstack frameが失われます.すなわち,lua vm自体の状態はLに保持できるが,C関数の状態は失われ,正しく戻ることができない.Lua 5.2はこの問題を解決するために、新しいapiを導入し、新しいドキュメントの4.7-Handling Yields in Cを読むことに興味を持っています.しかし、本当にすべてを明らかにしたいので、ソースコードを読むことをお勧めします.
システムが持参したdebugライブラリだけでは足りません.luaのdebugでsethookはlua関数をhookとして設定し、coroutineを呼び出すことはできません.yield .これはlua実現の制限を受けている.正しい方法は、C api
何行かのluaコードyieldごとに発生させたいのか、関数呼び出しのときに発生させたいのかは、好み次第です.しかし、設定に成功すると、luaコードの一部が透明な定期yieldで出てくるようになります.
これは何の役に立つのですか.2つの使い方を想定しましたが、
一、luaでスケジューラを作り、プリエンプト式のマルチスレッドライブラリをシミュレートする.本質的に、このライブラリはcoroutineに基づいて実現されます.ただし,切替スレッドは利用するdebug hookが定期的に強制的に切替する.このライブラリに基づいて、以前書いたこの小物を改善することができます.もっと使いやすいです.
二、1つのosプロセス内で複数の独立したlua stateを起動し、各lua stateは複数のcoroutineを含まず、1つのmain threadしか使用しない.debug hookを設定し、main threadが1段階走るたびにyieldを出て、制御権を上位のCコードに返すことができるようにします.C階層に有効なスケジューラを書きます.lua stateでは、それぞれ独立しており、zeromqのようなライブラリで通信することができます.lua stateはまた、複数のos threadに配備され、1つのM:Nのスレッドモデルを実現することもできる.そのスケジューラはosよりも効率的です.(言語レベルでM:NスレッドモデルとOSレベルでM:Nラインプログラムモデルを実現する性能の違いについて、先月7月12日にgoogle+で検討しました.残念ながら限られた輪の中でやったので、しばらく方法が見つからず公開されていません)しかも大量のスタックリソースを占有することは少ないです.
というか、ここまで来てみて、実はこれがエランのモデルじゃないですか?:)
ps.実はluaの初期バージョンもlua cocoによって無制限のyield操作を実現することができる.しかし、cocoはOSのfiberライブラリを使用しており、5.2版のlua実装よりも追加のスタックオーバーヘッドが発生しています.
まだ実現していないアイデア:ゲームサーバーのnpc aiなどは独立したlua stateの中で完成することができますか?それらは互いに影響を及ぼさず、メッセージで対話します.新しいAIユニットを動的にバインドするためにstateプールを最初に割り当てることができます(初期化を開始するコストを削減します).タスクを独立した小さな単位に切り分け、独立したlua stateで行うことは、luaのgc操作にも極めて有利だと思います.
改善リストを見ると、新しいものはあまり見えないようです.しかし、ソースコードをよく読むと、表面的にはあまり修正されていないように見えるように、ほとんどの場所が再実現されていることがわかります.Luaを十分に理解すれば、今回の最も感動的な改善は「yieldable pcall and metamethods」であることがわかります.公式にもMain changesの第1条とされている.言語上の重大な新しい特性gotoは末尾に列挙されている.
もちろん、これは私の浅い理解にすぎません.実践を経て5.2をしばらく使わなかったが、このような論断は少し軽率すぎる.しかし、この改善が私たちの開発に何をもたらすかについてお話ししたいと思います.
coroutineのyieldは今ではほとんどどこでも使用できます.私がほとんど使ったのは、まだいくつかの制限があるからです.これらの制限は簡単には言えませんが、その制限を理解するために、lua 5.2 beta版のソースコードを読むのに一日中かかりました.この話題は今度また別のblogを書いてまとめます.今日は応用についてだけ話します.
coroutineは協同マルチスレッドモデルを実現できることを知っています.すなわち、各スレッド(coroutine)は、ユーザの所望の場所でのみ飛び出し、後から跳ね返すことができる(当初のジャンプ状態を維持する).これにより、多くのプリエンプトマルチスレッドのトラブルが解決されます.luaの発明者はインタビューでcoroutineが同時の問題を解決することについて話した.しかし、ユーザーが嫌がらずにyieldを書くのは嫌なことです.フレームワークはyield呼び出しを隠すことが多い.もし私が間違っていなければ、私が読んだ初期のkeplerフレームワークはyieldをhtmlの出力に隠していました.もっときれいな方法はlua debug hookを作成し、hookでyieldを呼び出すことです.これによりlua vmが何行か走るたびにlua byte codeが自動的にyieldを作ることができます.
残念なことに、lua 5.1以前のバージョンではこれはサポートされていません.yieldの制限が多すぎるからです.ちょうどyieldがmetamethod呼び出しの内部またはpcallの内部で発生した場合、失敗します.これはC/Lua関数のネストと関係がある.lua vmはpcallおよびmetamethodメカニズムを実現する際に,C関数およびlua関数間で絶えずジャンプする.yieldが多層C関数とlua関数のネスト呼び出しの内部で発生すると.longjmpで返されるyieldメカニズムでは、C関数呼び出しのstack frameが失われます.すなわち,lua vm自体の状態はLに保持できるが,C関数の状態は失われ,正しく戻ることができない.Lua 5.2はこの問題を解決するために、新しいapiを導入し、新しいドキュメントの4.7-Handling Yields in Cを読むことに興味を持っています.しかし、本当にすべてを明らかにしたいので、ソースコードを読むことをお勧めします.
システムが持参したdebugライブラリだけでは足りません.luaのdebugでsethookはlua関数をhookとして設定し、coroutineを呼び出すことはできません.yield .これはlua実現の制限を受けている.正しい方法は、C api
lua_sethook
を直接使用して、このようなhook関数を設定することです.
static void
hook(lua_State *L, lua_Debug *ar)
{
lua_yield(L,0);
}
何行かのluaコードyieldごとに発生させたいのか、関数呼び出しのときに発生させたいのかは、好み次第です.しかし、設定に成功すると、luaコードの一部が透明な定期yieldで出てくるようになります.
これは何の役に立つのですか.2つの使い方を想定しましたが、
一、luaでスケジューラを作り、プリエンプト式のマルチスレッドライブラリをシミュレートする.本質的に、このライブラリはcoroutineに基づいて実現されます.ただし,切替スレッドは利用するdebug hookが定期的に強制的に切替する.このライブラリに基づいて、以前書いたこの小物を改善することができます.もっと使いやすいです.
二、1つのosプロセス内で複数の独立したlua stateを起動し、各lua stateは複数のcoroutineを含まず、1つのmain threadしか使用しない.debug hookを設定し、main threadが1段階走るたびにyieldを出て、制御権を上位のCコードに返すことができるようにします.C階層に有効なスケジューラを書きます.lua stateでは、それぞれ独立しており、zeromqのようなライブラリで通信することができます.lua stateはまた、複数のos threadに配備され、1つのM:Nのスレッドモデルを実現することもできる.そのスケジューラはosよりも効率的です.(言語レベルでM:NスレッドモデルとOSレベルでM:Nラインプログラムモデルを実現する性能の違いについて、先月7月12日にgoogle+で検討しました.残念ながら限られた輪の中でやったので、しばらく方法が見つからず公開されていません)しかも大量のスタックリソースを占有することは少ないです.
というか、ここまで来てみて、実はこれがエランのモデルじゃないですか?:)
ps.実はluaの初期バージョンもlua cocoによって無制限のyield操作を実現することができる.しかし、cocoはOSのfiberライブラリを使用しており、5.2版のlua実装よりも追加のスタックオーバーヘッドが発生しています.
まだ実現していないアイデア:ゲームサーバーのnpc aiなどは独立したlua stateの中で完成することができますか?それらは互いに影響を及ぼさず、メッセージで対話します.新しいAIユニットを動的にバインドするためにstateプールを最初に割り当てることができます(初期化を開始するコストを削減します).タスクを独立した小さな単位に切り分け、独立したlua stateで行うことは、luaのgc操作にも極めて有利だと思います.