2010-03-03 89 views
5

我需要将目录中的一堆文件上传到S3。由于超过90%的上传时间需要等待http请求完成,我想以某种方式立即执行其中的几个。如何使用红宝石光纤以避免阻塞IO

Can Fibers可以帮我解决这个问题吗?他们被描述为解决这类问题的一种方式,但我无法想象任何可以在http呼叫阻止时做任何工作的方式。

任何方式我可以解决这个问题没有线程?

+0

所以,任何人都可以在纤维本身有何评论?假设纤维没有“在背景中做东西”的权力,我是否正确? – 2010-03-03 22:20:15

回答

3

我不起来纤维1.9,但是从1.8.6定期线程可以解决这个问题。尝试使用队列http://ruby-doc.org/stdlib/libdoc/thread/rdoc/classes/Queue.html

查看文档中的示例,您的使用者是执行上载的部分。它'消耗'一个URL和一个文件,并上传数据。生产者是你的程序的一部分,可以继续工作并找到要上传的新文件。在您的“生产者”代码

t = Thread.new do 
    upload_file(param1, param2) 
end 
@all_threads << t 

然后,后来就(其中,记住,不必:

如果你想一次上传多个文件时,只需启动一个新的线程为每个文件可能是主程序):

@all_threads.each do |t| 
    t.join if t.alive? 
end 

队列可以是@member_variable或$ global。

+1

虽然我纤维questino似乎在https://github.com/igrigorik/em-http-request/wiki/Parallel-Requests已经没有回音 – 2010-03-05 03:01:51

1

你可以使用单独的流程,这个代替线程:

#!/usr/bin/env ruby 

$stderr.sync = true 

# Number of children to use for uploading 
MAX_CHILDREN = 5 

# Hash of PIDs for children that are working along with which file 
# they're working on. 
@child_pids = {} 

# Keep track of uploads that failed 
@failed_files = [] 

# Get the list of files to upload as arguments to the program 
@files = ARGV 


### Wait for a child to finish, adding the file to the list of those 
### that failed if the child indicates there was a problem. 
def wait_for_child 
    $stderr.puts " waiting for a child to finish..." 
    pid, status = Process.waitpid2(0) 
    file = @child_pids.delete(pid) 
    @failed_files << file unless status.success? 
end 


### Here's where you'd put the particulars of what gets uploaded and 
### how. I'm just sleeping for the file size in bytes * milliseconds 
### to simulate the upload, then returning either +true+ or +false+ 
### based on a random factor. 
def upload(file) 
    bytes = File.size(file) 
    sleep(bytes * 0.00001) 
    return rand(100) > 5 
end 


### Start a child uploading the specified +file+. 
def start_child(file) 
    if pid = Process.fork 
     $stderr.puts "%s: uploaded started by child %d" % [ file, pid ] 
     @child_pids[ pid ] = file 
    else 
     if upload(file) 
      $stderr.puts "%s: done." % [ file ] 
      exit 0 # success 
     else 
      $stderr.puts "%s: failed." % [ file ] 
      exit 255 
     end 
    end 
end 


until @files.empty? 

    # If there are already the maximum number of children running, wait 
    # for one to finish 
    wait_for_child() if @child_pids.length >= MAX_CHILDREN 

    # Start a new child working on the next file 
    start_child(@files.shift) 

end 


# Now we're just waiting on the final few uploads to finish 
wait_for_child() until @child_pids.empty? 

if @failed_files.empty? 
    exit 0 
else 
    $stderr.puts "Some files failed to upload:", 
     @failed_files.collect {|file| " #{file}" } 
    exit 255 
end 
2

为了回答您的实际问题:

纤维可以帮助我呢?

不,他们不能。 JörgW Mittag explains why best

不,你不能做并发性纤维。纤维不是一个并发构造,它们是一个控制流构造,就像Exceptions一样。这就是Fibers的重点:它们永远不会平行运行,它们是合作的,它们是确定性的。纤维是协同程序。 (实际上,我从来不明白为什么它们不是简单的叫做协程)。

Ruby中唯一的并发结构是Thread。

当他说在Ruby中唯一的并发contruct为主题,记得有红宝石的许多不同implimentations,他们在自己的线程实现而不同。 Jörg再次provides a great answer这些差异;并正确地断定只有像JRuby(使用映射到本地线程的JVM线程)或分叉你的进程的东西是你如何实现真正的并行。

什么办法可以解决这个问题,而线程?

除了分叉的过程中,我也建议你看看EventMachine和类似em-http-request。这是一个事件驱动的,非阻塞的,基于reactor pattern的HTTP客户端,它是异步的,不会产生线程开销。

+0

请参阅同步的多接口 – Sairam 2014-01-15 06:39:43

1

亚伦帕特森(@tenderlove)使用的例子几乎完全像你描述究竟为什么你可以应该使用线程在您的情况实现并发。

大多数I/O库现在有足够的智慧来释放GVL(全球VM锁,或者大多数人都知道它的GIL或全局解释器锁)做的IO时。 C中有一个简单的函数调用来执行此操作。你并不需要担心的C代码,但你这意味着大多数IO库称职将要释放GVL和允许其他线程执行的同时,执行的IO线程等待数据返回。

如果我刚才说的是混乱的,你不需要担心太多。你需要知道的主要事情是,如果你正在使用一个体面的库来执行你的HTTP请求(或者任何其他的I/O操作......数据库,进程间通信等等),Ruby解释器(MRI)足够聪明,能够释放解释器上的锁,并允许其他线程在一个线程等待IO返回时执行。如果下一个线程拥有自己的IO,Ruby解释器将执行相同的操作(假设IO库的构建是为了利用Ruby的这一特性,我认为这些功能最近都是这样)。

所以,总结一下我说的话,使用线程!你应该看到性能优势。如果没有,请检查您的http库是否使用C中的rb_thread_blocking_region()函数,如果没有,请查明原因。也许有一个很好的理由,也许你需要考虑使用一个更好的图书馆。

链接到亚伦帕特森视频是在这里:http://www.youtube.com/watch?v=kufXhNkm5WU

这是值得的手表,即使只是为了笑,亚伦帕特森是最有趣的人在互联网上的一个。