Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/delphi/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Multithreading 跨线程存储任意函数调用_Multithreading_Haskell_Types_Signals Slots - Fatal编程技术网

Multithreading 跨线程存储任意函数调用

Multithreading 跨线程存储任意函数调用,multithreading,haskell,types,signals-slots,Multithreading,Haskell,Types,Signals Slots,我正试图编写一个库来重现Qt的线程语义:信号可以连接到插槽,所有插槽都在一个已知线程中执行,因此绑定到同一线程的插槽彼此之间是线程安全的 我有以下API: data Signal a = Signal Unique a data Slot a = Slot Unique ThreadId (a -> IO ()) mkSignal :: IO (Signal a) mkSlot :: ThreadId -> (Slot a -> a -> IO ()) ->

我正试图编写一个库来重现Qt的线程语义:信号可以连接到插槽,所有插槽都在一个已知线程中执行,因此绑定到同一线程的插槽彼此之间是线程安全的

我有以下API:

data Signal a = Signal Unique a
data Slot a = Slot Unique ThreadId (a -> IO ())

mkSignal :: IO (Signal a)
mkSlot   :: ThreadId -> (Slot a -> a -> IO ()) -> IO (Slot a)

connect :: Signal a -> Slot a -> IO ()

-- callable from any thread
emit :: Signal a -> a -> IO ()

-- runs in Slot's thread as a result of `emit`
execute :: Slot a -> a -> IO ()
execute (Slot _ _ f) arg = f arg
问题是从
emit
execute
。该参数需要以某种方式存储在运行时,然后执行IO操作,但我似乎无法通过类型检查器

我需要的东西:

  • 类型安全:信号不应连接到需要不同类型的插槽
  • 类型独立性:对于任何给定类型,都可以有多个插槽(可能可以使用newtype和/或TH来放宽)
  • 易用性:由于这是一个库,信号和插槽应该很容易创建
  • 我尝试过的事情:

    • :使整个事情变得非常脆弱,而且我还没有找到一种方法在
      动态
      上执行正确键入的IO操作。有,但它是纯的
    • :我需要执行传递给
      mkSlot
      的函数,而不是基于类型的任意函数
    • 当前位置我不够聪明,弄不明白

    我缺少什么?

    首先,您确定插槽真的要在特定线程中执行吗?在Haskell中编写线程安全的代码很容易,而GHC中的线程非常轻量级,因此将所有事件处理程序执行绑定到特定的Haskell线程并不会获得太多好处

    另外,
    mkSlot
    的回调不需要指定插槽本身:您可以使用在回调中绑定插槽,而无需将打结问题添加到
    mkSlot

    无论如何,你不需要像这些解决方案那样复杂的东西。我希望当你谈论存在类型时,你会考虑通过
    TChan
    (你在评论中提到过)发送类似
    (a->IO(),a)
    的东西,并将其应用到另一端,但是你希望
    TChan
    接受任何a的这种类型的值,而不仅仅是一个特定的a。这里的关键洞察是,如果您有
    (a->IO(),a)
    并且不知道a是什么,那么您唯一能做的就是将函数应用于值,给您一个
    IO()
    ——因此我们可以通过通道发送它们

    下面是一个例子:

    import Data.Unique
    import Control.Applicative
    import Control.Monad
    import Control.Concurrent
    import Control.Concurrent.STM
    
    newtype SlotGroup = SlotGroup (IO () -> IO ())
    
    data Signal a = Signal Unique (TVar [Slot a])
    data Slot a = Slot Unique SlotGroup (a -> IO ())
    
    -- When executed, this produces a function taking an IO action and returning
    -- an IO action that writes that action to the internal TChan. The advantage
    -- of this approach is that it's impossible for clients of newSlotGroup to
    -- misuse the internals by reading the TChan or similar, and the interface is
    -- kept abstract.
    newSlotGroup :: IO SlotGroup
    newSlotGroup = do
      chan <- newTChanIO
      _ <- forkIO . forever . join . atomically . readTChan $ chan
      return $ SlotGroup (atomically . writeTChan chan)
    
    mkSignal :: IO (Signal a)
    mkSignal = Signal <$> newUnique <*> newTVarIO []
    
    mkSlot :: SlotGroup -> (a -> IO ()) -> IO (Slot a)
    mkSlot group f = Slot <$> newUnique <*> pure group <*> pure f
    
    connect :: Signal a -> Slot a -> IO ()
    connect (Signal _ v) slot = atomically $ do
      slots <- readTVar v
      writeTVar v (slot:slots)
    
    emit :: Signal a -> a -> IO ()
    emit (Signal _ v) a = atomically (readTVar v) >>= mapM_ (`execute` a)
    
    execute :: Slot a -> a -> IO ()
    execute (Slot _ (SlotGroup send) f) a = send (f a)
    
    如果这可能是一个瓶颈,您可能需要类似于
    Map Unique(Slot a)
    的东西,而不是
    [Slot a]

    因此,这里的解决方案是:(a)认识到你有一些基本上基于可变状态的东西,并使用可变变量来构造它;(b) 要意识到函数和IO操作与其他操作一样都是一流的,所以在运行时构建它们不需要做任何特殊的工作:)


    顺便说一下,我建议不要从定义它们的模块中导出它们的构造函数,从而保持
    Signal
    Slot
    的实现是抽象的;毕竟,有很多方法可以在不改变API的情况下解决此问题。

    主要思想是,在同一线程中执行的插槽彼此之间是线程安全的。不管是什么样的线。是的,我有自己的事件循环,它什么也不做,只监听TChan并执行通过的任何操作。@GyörgyAndrasek:啊,我明白了:每个插槽都在同一个线程中运行,而不是每个插槽都在自己的线程中运行。我怀疑这是否是一件好事——Haskell有很好的线程支持,而且像STM这样的东西通常比线程安全代码更难编写线程不安全的代码——但我会适当地修改我的答案。不完全正确。您有多组插槽,在其组的线程中执行。在Qt中,这是通过
    对象完成的。moveToThread(thread)
    ,其中slot是对象的方法。其效果是,您可以在GUI线程和递归目录爬虫线程之间进行干净的通信,而不会在整个代码库中抛出线程问题。这做得很好,将问题重新表述为关键思想,并勾勒出实现和所有方面。:]@bdonlan:大概他们希望在插槽代码中产生副作用,就像事件处理程序经常做的那样;GHC尚未实现所需的时间机器语义,以允许插槽组上的锁可以工作,但可能并不比读取TChan的专用线程简单@C.A.麦肯:谢谢!:)
    disconnect :: Signal a -> Slot a -> IO ()
    disconnect (Signal _ v) (Slot u _ _) = atomically $ do
      slots <- readTVar v
      writeTVar v $ filter keep slots
      where keep (Slot u' _) = u' /= u