【问题标题】:Marshal (Ruby) pipes: sending serialized object to child processesMarshal(Ruby)管道:将序列化对象发送到子进程
【发布时间】:2012-08-10 09:07:49
【问题描述】:

我需要用 Marshal 序列化 Ruby 中的对象,并通过管道将其发送到子进程。我该怎么做?

我的代码如下所示,我的问题在 cmets 中:

data = Marshal.dump(data)
#call sub-process
`ruby -r a_lib -e 'a_method'` #### how to send the stdout to the subprocess?

a_method 看起来像:

def a_method
  ...
  data = Marshal.load(data) #### how to load the stdout of the parent process?
  ...
end

【问题讨论】:

    标签: ruby marshalling pipe


    【解决方案1】:

    是的,您可以通过管道在不同的 ruby​​/非 ruby​​ 进程之间发送序列化对象!

    让我告诉你我是怎么做的。

    在这个例子中,一个主进程启动一个子进程,然后子进程使用 Marshal 序列化传输一个简单的 Hash 对象。

    掌握源码:

    首先,在 Process 类中声明一些辅助方法 run_ruby 会很有用:

    #encoding: UTF-8
    require 'rbconfig'
    module Process
      RUBY = RbConfig::CONFIG.values_at('bindir', 'BASERUBY').join('/')
    # @param [String] command
    # @param [Hash] options
      def Process.run_ruby(command, options)
        spawn("#{Process::RUBY} -- #{command}", options)
      end
    end
    

    此代码只是定位 ruby​​ 可执行文件并将完整路径保存到 RUBY 常量中。

    重要提示:如果您要使用 Jruby 或其他一些可执行文件 - 你应该重写这段代码并提供执行它的路径!

    接下来,我们应该启动子进程。
    此时我们可以覆盖 STDINSTDOUTSTDERR 以获得新进程。
    让我们创建一个 管道 并将孩子的 STDOUT 重定向到这个管道:

      rd, wr = IO.pipe
      Process.run_ruby("./test/pipetest.rb param1 param2", {:out => wr})
      wr.close
    

    请注意选项哈希:{:out => wr} - 它告诉 spawn 命令将 STDOUT 重定向到 wr 流描述符。

    此外,您可以在命令行中指定参数(参见 param1param2)。

    请注意,我们之所以调用 wr.close,是因为我们没有在本示例的父进程中使用它。

    master 将如何接收对象:

    message = rd.gets         # read message header with size in bytes
    cb = message[5..-1].to_i  # message is in form: "data <byte_size>\n"
    data = rd.read(cb)        # read message with binary object
    puts "Parent read #{data.length} from #{cb} bytes:"
    obj = Marshal::load(data) # unserialize object
    puts obj.inspect
    

    子源代码:

    现在,序列化的对象将如何传输?
    起初孩子会序列化对象,
    然后它将以以下形式发送父消息:"data &lt;byte_size&gt;\n"
    之后它会自己发送序列化对象。
    子进程会将对象发送到 STDOUT,因为我们已指定将此通道用作管道。

    #encoding: UTF-8
    
    # obj is an example Hash object to be transmitted
    obj = {
        1 => 'asd',
        'data' => 255,
        0 => 0.55
    }
    
    data = Marshal::dump(obj)             # serializing object (obj)
    $stdout.puts "data #{data.length}"    # sending message header
    $stdout.write data                    # sending message itself
    $stdout.flush                         # Important: flush data!
    

    在上面的代码中,子进程简单地输出一个序列化对象并终止。
    但是,当然,您可以编写更复杂的行为。
    例如,我启动了许多子进程,每个子进程在 STDOUT 处与父进程共享同一个管道。为了避免两个孩子同时写入管道的问题,我必须使用系统级互斥锁(不是 Ruby 互斥锁)来控制对该管道的访问。

    【讨论】:

      【解决方案2】:

      你可以使用IO::pipe方法。

      我认为您选择的不是创建子进程的最佳方式。反引号在幕后执行forkexecruby 命令也执行forkexec。这意味着您的命令:

      `ruby -r a_lib -e 'a_method'`
      

      执行以下操作:fork 当前进程,将其转换为 shell 进程,fork shell 进程,将其转换为 ruby​​ 进程。

      我建议使用fork 方法:

      data = Marshal.dump(data)
      
      reader, writer = IO.pipe
      reader.close # parent process will be on the writing side of the pipe
      writer.puts data
      
      #call sub-process
      fork do
        writer.close # child process can only read from the pipe
        data = reader.gets
        # whatever needs to be done with data
      end
      

      【讨论】:

      • 感谢您的回答,但是我需要子进程由jruby运行,而父进程由ruby运行。我还能用叉子吗?
      • 不,在这种情况下您不能使用fork。你也不能像这样将管道对象传递给子进程:ruby -r a_lib -e 'a_method(#{reader})'。您可以尝试使用套接字或将数据写入文件。
      • 我真的希望我可以使用管道。但无论如何,非常感谢您的回答。我想,我只是写入一个文件。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-26
      • 2020-06-14
      • 2011-10-28
      • 1970-01-01
      • 1970-01-01
      • 2014-09-25
      相关资源
      最近更新 更多