我想要使用"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
,因为我无法告诉不透明的事件管理器它应该为我传递一些状态,尽管我知道它仅使用一个线程,因此可能被用作单子变换器栈的基础。 - 对于读取操作,我必须显式创建分界限延续(甚至可能要为写入操作这样做;在这种情况下,我不知道哪种方法更明智)。
现在,这就呼吁使用大量的单子变换器等。我希望能够像这样轻松地完成:
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中发生的同步IO调度变化(我使用的是7.4.1),这样做值得吗?
- 如果我想实现协作并发,例如有一个函数始终处理来自套接字的读取,但是将此函数与写“线程”共享相同的状态单子,可以使用来自问题1的任何解决方案吗?
transformers
版本中包含它。您可以在此处查看我的实现(https://github.com/Gabriel439/Haskell-Pipes-Library/blob/master/Control/Monad/Trans/Free.hs)。我在我的`pipes`库中使用它。 - Gabriella GonzalezforkIO
这样的功能呢?"单子堆栈"如何在IO单子中被"序列化"?那部分对我来说毫无意义。 - dflemstrit
的类型是IO Trace
,并且它是通过forkIO
线程传递的。简而言之,它允许您仅将代码的IO
部分通过forkIO
线程化。 - Gabriella Gonzalez