开发者

Ruby Semaphores?

开发者 https://www.devze.com 2023-02-20 11:14 出处:网络
I\'m working on an implementation of the \"Fair Barbershop\" problem in Ruby. This is for a class assignment, but I\'m not looking for any handouts.I\'ve been searching like crazy, but I cannot seem t

I'm working on an implementation of the "Fair Barbershop" problem in Ruby. This is for a class assignment, but I'm not looking for any handouts. I've been searching like crazy, but I cannot seem to find a Ruby implementation of Semaphore开发者_运维知识库s that mirror those found in C.

I know there is Mutex, and that's great. Single implementation, does exactly what that kind of semaphore should do.

Then there's Condition Variables. I thought that this was going to work out great, but looking at these, they require a Mutex for every wait call, which looks to me like I can't put numerical values to the semaphore (as in, I have seven barbershops, 3 barbers, etc.).

I think I need a Counting Semaphore, but I think it's a little bizarre that Ruby doesn't (from what I can find) contain such a class in its core. Can anyone help point me in the right direction?


If you are using JRuby, you can import semaphores from Java as shown in this article.

require 'java'

java_import 'java.util.concurrent.Semaphore'

SEM = Semaphore.new(limit_of_simultaneous_threads)
SEM.acquire #To decrement the number available
SEM.release #To increment the number available


There's http://sysvipc.rubyforge.org/SysVIPC.html which gives you SysV semaphores. Ruby is perfect for eliminating the API blemishes of SysV semaphores and SysV semaphores are the best around -- they are interprocess semaphores, you can use SEM_UNDO so that even SIGKILLs won't mess up your global state (POSIX interprocess semaphores don't have this), and you with SysV semaphores you can perform atomic operations on several semaphores at once as long as they're in the same semaphore set.

As for inter-thread semaphores, those should be perfectly emulatable with Condition Variables and Mutexes. (See Bernanrdo Martinez's link for how it can be done).


I also found this code: https://gist.github.com/pettyjamesm/3746457

probably someone might like this other option.


since concurrent-ruby is stable (beyond 1.0) and is being widely used thus the best (and portable across Ruby impls) solution is to use its Concurrent::Semaphore class


Thanks to @x3ro for his link. That pointed me in the right direction. However, with the implementation that Fukumoto gave (at least for rb1.9.2) Thread.critical isn't available. Furthermore, my attempts to replace the Thread.critical calls with Thread.exclusive{} simply resulted in deadlocks. It turns out that there is a proposed Semaphore patch for Ruby (which I've linked below) that has solved the problem by replacing Thread.exclusive{} with a Mutex::synchronize{}, among a few other tweaks. Thanks to @x3ro for pushing me in the right direction.

http://redmine.ruby-lang.org/attachments/1109/final-semaphore.patch


Since the other links here aren't working for me, I decided to quickly hack something together. I have not tested this, so input and corrections are welcome. It's based simply on the idea that a Mutex is a binary Semaphore, thus a Semaphore is a set of Mutexes.

https://gist.github.com/3439373


I think it might be useful to mention the Thread::Queue in this context for others arriving at this question.

The Queue is a thread-safe tool (implemented with some behind-the-scenes synchronization primitives) that can be used like a traditional multi-processing semaphore with just a hint of imagination. And it comes preloaded by default, at least in ruby v3:

#!/usr/bin/ruby
# hold_your_horses.rb
q = Queue.new
wait_thread = Thread.new{
  puts "Wait for it ..."
  q.pop
  puts "... BOOM!"
}

sleep 1
puts "... click, click ..."
q.push nil

wait_thread.join

And can be demonstrated simply enough:

user@host:~/scripts$ ruby hold_your_horses.rb
Wait for it ...
... click, click ...
... BOOM!

The docs for ruby v3.1 say a Queue can be initialized with an enumerable object to set up initial contents but that wasn't available in my v3.0. But if you want a semaphore with, say, 7 permits, it's easy to stuff the box with something like:

q = Queue.new
7.times{ q.push nil }

I used the Queue to implement baton-passing between some worker-threads:

class WaitForBaton
  def initialize
    @q = Queue.new
  end
  def pass_baton
    @q.push nil
    sleep 0.0
  end
  def wait_for_baton
    @q.pop
  end
end

So that thread task_master could perform steps one and three with thread little_helper stepping in at the appropriate time to handle step two:

baton = WaitForBaton.new
task_master = Thread.new{
  step_one(ARGV[0])
  baton.pass_baton
  baton.wait_for_baton
  step_three(logfile)
}

little_helper = Thread.new{
  baton.wait_for_baton
  step_two(ARGV[1])
  baton.pass_baton
}

task_master.join
little_helper.join

Note that the sleep 0.0 in the .pass_baton method of my WaitForBaton class is necessary to prevent task_master from passing the baton to itself: unless thread scheduling happens to jump away from task_master right after baton.pass_baton, the very next thing that happens is task_master's baton.wait_for_baton - which takes the baton right back again. sleep 0.0 explicitly cedes execution to any other threads that might be waiting to run (and, in this case, blocking on the underlying Queue).

Ceding execution is not the default behavior because this is a somewhat unusual usage of semaphore technology - imagine that task_master could be generating many tasks for little_helpers to do and task_master can efficiently get right back to generating tasks right after passing a task off through a Thread::Queue's .push([object]) method.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号