如何处理ZeroMQ + Ruby中的线程问题?

时间:2016-08-04 17:15:14

标签: ruby multithreading sockets zeromq celluloid

在阅读有关线程安全的ZeroMQ FAQ时偶然发现。

  

我的多线程程序在ZeroMQ库中的奇怪位置不断崩溃。我做错了什么?

     

ZeroMQ套接字是线程安全的。 “指南”中对此进行了详细介绍。

     

简短版本是不应在线程之间共享套接字。我们建议为每个线程创建一个专用套接字。

     

对于每个线程的专用套接字不可行的情况,当且仅当每个线程在访问套接字之前执行完整的内存屏障时,才可以共享套接字。大多数语言都支持Mutex或Spinlock,它将代表您执行完整的内存屏障。

我的多线程程序在ZeroMQ库中的奇怪位置不断崩溃 我究竟做错了什么?

以下是我的以下代码:

Celluloid::ZMQ.init
module Scp
    module DataStore
    class DataSocket
        include Celluloid::ZMQ 
            def pull_socket(socket)
                @read_socket = Socket::Pull.new.tap do |read_socket|
                    ## IPC socket
                    read_socket.connect(socket)
                end
            end

            def push_socket(socket)
                @write_socket = Socket::Push.new.tap do |write_socket|
                    ## IPC socket
                    write_socket.connect(socket)
                end
            end

            def run
                pull_socket and push_socket and loopify!
            end

            def loopify!
                loop {
                   async.evaluate_response(read_socket.read_multipart)
                }
            end

            def evaluate_response(data)
                return_response(message_id,routing,Parser.parser(data))
            end

            def return_response(message_id,routing,object)
                data = object.to_response
                write_socket.send([message_id,routing,data])
            end
        end
    end
end  

DataSocket.new.run 

现在,有一些我不清楚的事情:

1)假设async产生一个新的Thread(每次)并且write_socket在所有线程之间共享,ZeroMQ说它们的套接字不是线程安全的。我当然看到write_socket遇到线程安全问题 (顺便说一句,到目前为止,还没有在所有端到端测试中遇到过这个问题。)

问题1 :我的理解是否正确?

要解决这个问题,ZeroMQ要求我们使用Mutex,Semaphore实现这一目标。

导致问题2

2)上下文切换。

鉴于线程应用程序可以随时切换上下文。 查看ffi-rzmq代码Celluloid::ZMQ .send() 会在内部调用send_strings(),内部调用send_multiple()

问题2:上下文切换可以在任何地方发生(甚至在关键部分)(这里)[https://github.com/chuckremes/ffi-rzmq/blob/master/lib/ffi-rzmq/socket.rb#L510]

这也可能导致数据排序问题。

我的观察结果是否正确?

注意:

Operating system ( MacOS, Linux and CentOS )  
Ruby - MRI 2.2.2/2.3.0

1 个答案:

答案 0 :(得分:0)

这个答案对你的问题不是一个很好的解决方案,而且肯定与user3666197的建议一致。我认为这种解决方案有可能发挥作用,但由于互斥拥塞,可能会出现大规模的性能成本。

  

问题1:假设异步生成新线程(每次)和write_socket在所有线程之间共享,并且zeromq说它们的套接字没有线程安全。我当然看到write_socket运行到线程安全问题。 (到目前为止,在所有端到端测试中都没有遇到过这个问题。)我的理解是否正确?

根据我对文档的理解,是的,这可能是一个问题,因为套接字不是线程安全的。即使您没有遇到此问题,也可能会在以后弹出。

  

问题2:上下文切换可以在任何地方发生(甚至在关键部分)

是的,所以我们可以解决这个问题的一种方法是使用互斥锁/信号量来确保我们不会在错误的时间进行上下文切换。

我会做这样的事情,但根据被调用的方法不是线程安全的,可能会有更好的方法:

Celluloid::ZMQ.init
module Scp
  module DataStore
    class DataSocket
      include Celluloid::ZMQ

      def initialize
        @mutex = Mutex.new
      end

      def pull_socket(socket)
        Thread.new do
          @mutex.synchronize do
            @read_socket = Socket::Pull.new.tap do |read_socket|
              ## IPC socket
              read_socket.connect(socket)
            end
          end
        end.join
      end

      def push_socket(socket)
        Thread.new do
          @mutex.synchronize do
            @write_socket = Socket::Push.new.tap do |write_socket|
              ## IPC socket
              write_socket.connect(socket)
            end
          end
        end.join
      end

      def run
        # Missing socket arguments here
        pull_socket and push_socket and loopify!
      end

      def loopify!
        Thread.new do
          @mutex.synchronize do
            loop {
              async.evaluate_response(read_socket.read_multipart)
            }
          end
        end.join
      end

      def evaluate_response(data)
        return_response(message_id,routing,Parser.parser(data))
      end

      def return_response(message_id,routing,object)
        data = object.to_response
        write_socket.send([message_id,routing,data])
      end
    end
  end
end

DataSocket.new.run