C# 0MQ:如何以线程安全的方式使用ZeroMQ?

C# 0MQ:如何以线程安全的方式使用ZeroMQ?,c#,.net,multithreading,thread-safety,zeromq,C#,.net,Multithreading,Thread Safety,Zeromq,我读了这本书,无意中发现了以下内容: 您不能在两个服务器之间共享ØMQ套接字 线程。ØMQ套接字不可用 线程安全。从技术上讲这是可能的 要做到这一点,但它需要信号量, 锁或互斥锁。这将使您的 应用程序缓慢且脆弱。唯一的 在一个有点理智的地方 线程之间的共享套接字位于 需要执行的语言绑定 像魔法一样的垃圾收集 插座 后来: 记住:除非在创建套接字的线程中,否则不要使用或关闭套接字 我还了解ZeroMQ上下文是线程安全的 如果一个类注册另一个类(在.Net中)的事件,则该事件可能会从创建侦听器的线程

我读了这本书,无意中发现了以下内容:

您不能在两个服务器之间共享ØMQ套接字 线程。ØMQ套接字不可用 线程安全。从技术上讲这是可能的 要做到这一点,但它需要信号量, 锁或互斥锁。这将使您的 应用程序缓慢且脆弱。唯一的 在一个有点理智的地方 线程之间的共享套接字位于 需要执行的语言绑定 像魔法一样的垃圾收集 插座

后来:

记住:除非在创建套接字的线程中,否则不要使用或关闭套接字

我还了解ZeroMQ
上下文是线程安全的

如果一个类注册另一个类(在.Net中)的事件,则该事件可能会从创建侦听器的线程以外的其他线程调用

我认为只有两个选项可以通过ZeroMQ套接字从eventhandler中分派内容:

  • 将调用线程的eventhandler与创建ZeroMQ-
    套接字的线程同步
  • 使用线程安全ZeroMQ-
    上下文为eventhandler中的线程创建新的ZeroMQ-
    套接字
    /获取现有的ZeroMQ-
    套接字
似乎0MQ指南不鼓励第一个,我不认为为每个线程创建一个新的ZeroMq套接字是可行的

我的问题:
从eventhandler中通过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?
    }
}
公共类示例类
{
公共事件事件处理程序SomethinIsCalledFromAnotherThread;
}
公共类ByteEventArgs:EventArgs
{
公共字节[]BytesToSend;
}
公共类调度器
{
ZMQ.Context-ctx;
公共调度程序(ZMQ.Context mqcontext,ExampleClass exampleClassInstance)
{
this.ctx=mqcontext;
exampleClassInstance.SomethinIsCalledFromAnotherThread+=新事件处理程序(exampleClass\u SomethinIsCalledFromAnotherThread);
}
void exampleClass_SomethinIsCalledFromAnotherThread(对象发送器,ByteEventTargets e)
{
//这个方法可能被另一个线程调用。所以我必须得到一个新的套接字等等?
使用(var socket=ctx.socket(ZMQ.SocketType.PUSH))
{
//初始化套接字等…最后:
socket.Send(例如BytesToSend);
}
//这不是太多开销吗?
}
}

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

有几种传统模式,但我不知道它们是如何具体映射到.NET的:

  • 在使用套接字的线程中创建套接字,句号。在紧密绑定到一个进程的线程之间共享上下文,并在不紧密绑定的线程中创建单独的内容。在高级C API(czmq)中,这些线程称为连接线程和分离线程
  • 在父线程中创建套接字,并在线程创建时将其传递给连接的线程。线程创建调用将执行一个完整的内存屏障。从那时起,只在子线程中使用套接字。“使用”指接收、发送、设置sockopt、设置sockopt和关闭
  • 在一个线程中创建一个套接字,在另一个线程中使用,在每次使用之间执行自己的完整内存屏障。这是非常微妙的,如果你不知道什么是“完整的记忆障碍”,你不应该这样做

  • 别忘了看看inproc的交通工具。使用inproc://sockets进行线程间通信可能很有用,并且有一个线程打开套接字与其他进程/服务器通信


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

    在.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;
    
    sendQueue=newblockingcollection(new ConcurrentQueue());
    //并发队列可以安全地接受来自多个线程/处理程序的请求
    MyHandler+=(MyStuff stuffToSend)=>sendQueue.Add(stuffToSend);
    //启动单线程数据发送循环
    Task.Factory.StartNew(()=>{
    使用(var socket=context.socket()){
    MyStuff Stuff Tosend;
    //在调用CompleteAdding之前,此可枚举项将被阻塞
    foreach(sendQueue.GetConsumingEnumerable()中的var stuff)
    Send(stuff.Serialize());
    }
    });
    //完成后中断发送循环
    OnMyAppExit+=sendQueue.completedadding;
    
    在.NET中,有许多操作涉及线程池(例如任务),其中一个操作在下一个可用线程上运行;您不知道它将是哪个线程。有几个地方隐式使用了完整的内存屏障,但我怀疑测试这段代码会非常棘手。我今天早上刚读了问题中的引语,想看看是否有人解决了这个问题。背景如何?建议在线程之间共享还是为不同的线程创建上下文?@Pieter,Rest in Peace我最近使用了这种方法,我认为这是一种惯用的方法来处理.Net4中的“仅从一个线程发送”情况。然而,它并不能解决这些问题