epoll
「 」スタイルのイベント管理を使用して、効率的なシングルスレッドソケット通信を実装したいと思います。
非常に命令型のプログラムを「ゼロから」作成する場合、基本的には次のようにします(入力したばかりの疑似っぽいコードだけで、おそらくコンパイルされません)。
import Control.Concurrent
import Data.ByteString (ByteString)
import qualified Data.ByteString as ByteString
import qualified GHC.Event as Event
import Network
import Network.Socket
import Network.Socket.ByteString
main = withSocketFromSomewhere $ \ socket -> do
let fd = fromIntegral . fdSocket $ socket
-- Some app logic
state <- newMVar "Bla"
-- Event manager
manager <- Event.new
-- Do an initial write
initialWrite socket state manager
-- Manager does its thing
Event.loop manager
write manager socket bs =
-- Should be pretty straight-forward
Event.registerFd manager theWrite fd Event.evtWrite
where
fd = fromIntegral . fdSocket $ socket
theWrite key _ = do
Event.unregisterFd manager key
sendAll socket bs
read manager socket cont =
-- Ditto
Event.registerFd manager theRead fd Event.evtRead
where
fd = fromIntegral . fdSocket $ socket
theRead key _ = do
Event.unregisterFd manager key
bs <- recv socket 4096
cont bs
initialWrite socket state manager = do
msg <- readMVar state
write manager socket msg
read manager socket $ \ bs -> do
ByteString.putStrLn bs
putMVar state msg
マネージャーにタイムアウトイベントを追加する関数などもあると想像してください。
ただし、いくつかの理由から、このコードは特に優れているわけではありません。
- イベントマネージャーを手動で持ち歩きます。
- アプリケーションロジックにを使用する必要
MVar
があります。これは、不透明なイベントマネージャーに、ある状態を渡す必要があることを伝えることができないためです。これは、スレッドを1つしか使用しないため、モナド変換子スタック。 - 読み取り用に明示的に区切られた継続を作成する必要があります(書き込み用にこれを実行する必要がある場合もあります。この状況で何が賢明かわかりません)。
さて、これはモナド変換子などを大量に使用することを叫ぶだけです。私はこれを実行できるようにしたいと思います。
main =
withSocketFromSomewhere $ \ socket ->
runEvents . flip runStateT "Bla" $ initialWrite socket
initialWrite socket = do
msg <- lift get
write socket msg
resp <- read socket
liftIO $ ByteString.putStrLn resp
lift $ put msg
このコードは、上記のコードと同じ動作をする必要があります。たとえば、読み取りが回線で受信されるまで計算を一時停止resp <- read socket
し、同じスレッド/マネージャーで複数のソケットを管理できるようにします。
質問:
- ユーザーにもう少しパワーを与えるGHCイベントAPI/libevent /相当へのより高レベルのインターフェースはありますか?最近のGHC(私は7.4.1を使用しています)で発生した同期IOスケジューリングの変更を考慮することは価値がありますか?
- たとえば、ソケットからの読み取りを常に処理する1つの関数を持ち、この関数が書き込み「スレッド」と同じ状態モナドを共有するなど、協調的な同時実行性を実装したい場合はどうなりますか?これは、(1)のどのソリューションでも実行できますか?