【问题标题】:0MQ: How to use ZeroMQ in a threadsafe manner?0MQ:如何以线程安全的方式使用 ZeroMQ?
【发布时间】:2017-06-27 14:24:42
【问题描述】:

我阅读了ZeroMq guide 并偶然发现了以下内容:

您不得在它们之间共享 ØMQ 套接字 线程。 ØMQ 套接字不是 线程安全。技术上是可以的 做到这一点,但它需要信号量, 锁或互斥锁。这将使您的 应用程序缓慢而脆弱。唯一的 远程理智的地方 线程之间的共享套接字在 需要做的语言绑定 像垃圾收集一样的魔法 插座。

及以后:

记住:除了创建它们的线程之外,不要使用或关闭套接字。

我还了解到 ZeroMQ Context 是线程安全的。

如果一个类注册另一个类的事件,在 .Net 中,该事件可能会从与创建侦听器的线程不同的线程调用。

我认为只有两个选项可以通过 ZeroMQ-Sockets 从事件处理程序中调度某些内容:

  • 将eventhandler-invoking-thread同步到ZeroMQ-Socket创建的线程
  • 使用线程安全的 ZeroMQ-Context 为事件处理程序中的线程创建一个新的 ZeroMQ-Socket / 获取现有的 ZeroMQ-Socket

似乎 0MQ-Guide 不鼓励第一个,我不认为为每个线程创建一个新的 ZeroMq-Socket 是高性能的/要走的路。

我的问题
在事件处理程序中通过 0MQ 发布消息的正确模式(它的本意)是什么?

此外,该指南的作者在写作时是否考虑到了用于 .Net 的 ZeroMQ 绑定:

唯一的 远程理智的地方 线程之间的共享套接字在 需要做的语言绑定 像垃圾收集一样的魔法 插座。 ?

这里有一些示例代码来强调我的问题/疑问:

public class ExampleClass
{
    public event EventHandler<ByteEventArgs> SomethinIsCalledFromAnotherThread;
}

public class ByteEventArgs : EventArgs
{
    public byte[] BytesToSend;
}


public class Dispatcher
{
    ZMQ.Context ctx;

    public Dispatcher(ZMQ.Context mqcontext, ExampleClass exampleClassInstance)
    {
        this.ctx = mqcontext;
        exampleClassInstance.SomethinIsCalledFromAnotherThread += new EventHandler<ByteEventArgs>(exampleClass_SomethinIsCalledFromAnotherThread);
    }

    void exampleClass_SomethinIsCalledFromAnotherThread(object sender, ByteEventArgs e)
    {
        // this method might be called by a different thread. So I have to get a new socket etc?
        using (var socket = ctx.Socket(ZMQ.SocketType.PUSH))
        {
            // init socket etc..... and finally: 
            socket.Send(e.BytesToSend);
        }
        // isn't that too much overhead?
    }
}

【问题讨论】:

    标签: c# .net multithreading thread-safety zeromq


    【解决方案1】:

    您可以创建许多 0MQ 套接字,当然与您拥有的线程一样多。如果您在一个线程中创建套接字,并在另一个线程中使用它,则必须在两个操作之间执行完整的内存屏障。其他任何事情都会导致 libzmq 出现奇怪的随机故障,因为套接字对象不是线程安全的。

    有一些常规模式,虽然我不知道这些模式是如何专门映射到 .NET 的:

    1. 在使用它们的线程中创建套接字,句号。在紧密绑定到一个进程中的线程之间共享上下文,并在未紧密绑定的线程中创建单独的内容。在高级 C API (czmq) 中,这些被称为附加和分离线程。
    2. 在父线程中创建一个套接字并在线程创建时传递给附加线程。线程创建调用将执行完整的内存屏障。从那时起,在子线程中使用套接字only。 “使用”是指 recv、send、setsockopt、getsockopt 和 close。
    3. 在一个线程中创建一个套接字,并在另一个线程中使用,在每次使用之间执行您自己的完整内存屏障。这是非常微妙的,如果您不知道什么是“完全内存屏障”,则不应该这样做。

    【讨论】:

    • 在 .NET 中,有很多操作涉及线程池(例如任务),其中操作在下一个可用线程上运行;你不知道它会是哪个线程。
    • 有几个地方隐式使用了完整的内存屏障,但我怀疑测试这段代码会非常棘手。我今天早上刚刚阅读了问题中的引用,想看看是否有人解决了这个问题。
    • 上下文怎么样?是否建议在线程之间共享或为不同线程创建上下文?
    • @Pieter,安息吧
    【解决方案2】:

    在 .net framework v4 及更高版本中,您可以使用并发收集来解决此问题。即生产者-消费者模式。多个线程(处理程序)可以将数据发送到线程安全队列,并且只有单个线程使用队列中的数据并使用套接字发送。

    这是一个想法:

    sendQueue = new BlockingCollection<MyStuff>(new ConcurrentQueue<MyStuff>());
    // concurrent queue can accept from multiple threads/handlers safely
    MyHandler += (MyStuff stuffToSend) => sendQueue.Add(stuffToSend);
    
    // start single-threaded data send loop
    Task.Factory.StartNew(() => {
        using(var socket = context.Socket()) {
            MyStuff stuffToSend;
            // this enumerable will be blocking until CompleteAdding is called
            foreach(var stuff in sendQueue.GetConsumingEnumerable())
                socket.Send(stuff.Serialize());
        }
    });
    
    // break out of the send loop when done
    OnMyAppExit += sendQueue.CompleteAdding;
    

    【讨论】:

    • 我最近使用了这种方法,在我看来,这是处理 .Net4 中这种“仅从一个线程发送”情况的惯用方法。但是,它并不能解决可能需要多个发送者的情况,而我目前正在尝试使用 [ThreadStatic] 属性和一些工作线程。我目前唯一的问题是在不再需要套接字时正确关闭它们。
    • tobsen:为什么不能有多个发送者将对象放入队列?这就是我过去所做的。
    • @tobsen,上面的示例明确针对多个并发发件人。代码中的注释明确表示“并发队列可以安全地接受来自多个线程/处理程序”。你是什​​么意思“它不能解决那些可能希望有多个发件人的情况”,因为上面代码的唯一目的就是做到这一点?
    • @bj0 在我的评论“发件人”中的意思是“队列中的消费者,而不是通过 0mq 发送”。换句话说:当我写评论时,我意识到我可以有来自不同线程的多个生产者但是。但是,当时我不知道我也可以拥有多个 BlockingCollection 消费者。
    • @tobsen,ZMQGuide 指的是“朴素”锁,当消费者长时间锁定队列时,会导致发送者线程阻塞,整个应用程序停止,因为发送正在进行中。如果你不能使用框架 4,你仍然可以使用生产者/消费者的方法,但你的代码会更复杂。
    【解决方案3】:

    不要忘记查看 inproc 传输。使用 inproc:// 套接字进行线程间通信并有一个线程打开套接字以与其他进程/服务器通信可能很有用。

    您仍然需要每个线程至少一个套接字,但 inproc 完全不涉及 IP 网络层。

    【讨论】:

    • Michael,如果在inproc 上连接了一个套接字,我是否可以在没有显式同步的情况下跨不同线程重用它,或者应该像在tcp 上连接的套接字一样小心处理inproc 套接字,pgm,等等?
    • 这个问题的答案能回答你的吗? stackoverflow.com/questions/5476308/…
    • 不是真的,我想知道inproc 套接字是否可以在线程之间共享;我知道常规的 zqm 套接字绝对不是线程安全的,但无论如何谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多