在此示例中,我希望以输出为puts
的方式同步两个ababab...
,而不会有任何双a
或b
在输出上。
我有三个例子:使用队列,在内存中使用互斥锁并将mutex与文件一起使用。队列示例工作得很好,但互斥锁不会。
我没有找工作代码。我想了解为什么使用它运行的队列,并且使用互斥量不是。根据我的理解,它们应该是等价的。
队列示例:工作。
def a
Thread.new do
$queue.pop
puts "a"
b
end
end
def b
Thread.new do
sleep(rand)
puts "b"
$queue << true
end
end
$queue = Queue.new
$queue << true
loop{a; sleep(rand)}
Mutex文件示例:不能工作。
def a
Thread.new do
$mutex.flock(File::LOCK_EX)
puts "a"
b
end
end
def b
Thread.new do
sleep(rand)
puts "b"
$mutex.flock(File::LOCK_UN)
end
end
MUTEX_FILE_PATH = '/tmp/mutex'
File.open(MUTEX_FILE_PATH, "w") unless File.exists?(MUTEX_FILE_PATH)
$mutex = File.new(MUTEX_FILE_PATH,"r+")
loop{a; sleep(rand)}
Mutex变量示例:不能工作。
def a
Thread.new do
$mutex.lock
puts "a"
b
end
end
def b
Thread.new do
sleep(rand)
puts "b"
$mutex.unlock
end
end
$mutex = Mutex.new
loop{a; sleep(rand)}
答案 0 :(得分:2)
在您的互斥锁示例中,方法b
中创建的线程会休眠一段时间,打印b
然后尝试解锁互斥锁。这是不合法的,一个线程无法解锁互斥锁,除非它已经拥有该锁,并且如果你尝试就引发一个ThreadError:
m = Mutex.new
m.unlock
结果:
release.rb:2:in `unlock': Attempt to unlock a mutex which is not locked (ThreadError)
from release.rb:2:in `<main>'
您的示例中不会看到这一点,因为by default Ruby silently ignores exceptions raised in threads other than the main thread。您可以使用Thread::abort_on_exception=
进行更改 - 如果您添加
Thread.abort_on_exception = true
到文件的顶部,您会看到类似的内容:
a
b
with-mutex.rb:15:in `unlock': Attempt to unlock a mutex which is not locked (ThreadError)
from with-mutex.rb:15:in `block in b'
(您可能会看到多个a
,但只会有一个b
。
在a
方法中,您创建获取锁定的线程,打印a
,调用另一个方法(创建新线程并立即返回),然后终止。它似乎没有很好地记录,但是当一个线程终止它时会释放它所拥有的任何锁,因此在这种情况下几乎立即释放锁,允许其他a
线程运行。
总的来说,锁没有太大作用。它根本不会阻止b
个线程运行,虽然它确实阻止了两个a
线程同时运行,但只要线程持有它就会被释放。
我认为你可能会想到semaphores,而Ruby文档说“Mutex implements a simple semaphore”时他们是not quite the same。
Ruby不提供标准库中的信号量,但确实提供了condition variables。 (该链接转到较旧的2.0.0文档。默认情况下,在Ruby 2.1+中需要thread
标准库,此举似乎导致当前文档无法使用。还要注意Ruby也是有一个单独的monitor库(我认为)以更加面向对象的方式添加相同的功能(互斥和条件变量)。)
使用条件变量和互斥锁可以控制线程之间的协调。 Uri Agassi’s answer显示了一种可能的方法(虽然我认为他的解决方案如何开始存在竞争条件)。
如果你看一下source for Queue(这也是2.0.0的链接 - 在最近的版本中线程库已经转换为C并且Ruby版本更容易理解)你可以看到它是使用Mutex
es和ConditionVariables
实施。当您在队列示例中的$queue.pop
主题中致电a
时,您最终会以与他的方法{{1}中的Uri Agassi的回答调用$cv.wait($mutex)
相同的方式呼叫wait
on the mutex }。同样地,当您在a
主题中调用$queue << true
时,您的结尾calling signal
on the condition variable的方式与Uri Agassi在其b
主题中调用$cv.signal
的方式相同。
文件锁定示例不起作用的主要原因是文件锁定为多个进程提供了一种相互协调的方式(通常只有一个尝试同时写入文件)时间)并没有帮助协调进程中的线程。您的文件锁定代码的结构与互斥锁示例类似,因此可能会遇到相同的问题。
答案 1 :(得分:2)
简短回答
您对互斥锁的使用不正确。使用Queue
,您可以使用一个线程填充,然后使用另一个线程填充pop
,但是您无法使用一个线程锁定Mutex
,然后使用另一个线程解锁。
正如@matt解释的那样,有一些微妙的事情正在发生,例如自动解锁互斥锁以及你看不到的静默异常。
常用互斥体的方式
互斥锁用于访问特定的共享资源,如变量或文件。变量和文件的同步因此允许多个线程同步。互斥体本身并不真正同步线程。
例如:
thread_a
和thread_b
可以通过共享的布尔变量进行同步,例如true_a_false_b
。
require 'thread'
Thread.abort_on_exception = true
true_a_false_b = true
mutex = Mutex.new
thread_a = Thread.new do
loop do
mutex.lock
if true_a_false_b
puts "a"
true_a_false_b = false
end
mutex.unlock
end
end
thread_b = Thread.new do
loop do
mutex.lock
if !true_a_false_b
puts "b"
true_a_false_b = true
end
mutex.unlock
end
sleep(1) # if in irb/console, yield the "current" thread to thread_a and thread_b
答案 2 :(得分:1)
基于文件的版本的问题尚未正确排序。
它不起作用的原因是f.flock(File::LOCK_EX)
在多次调用同一文件f
时不会阻止。
这可以通过这个简单的顺序程序来检查:
require 'thread'
MUTEX_FILE_PATH = '/tmp/mutex'
$fone= File.new( MUTEX_FILE_PATH, "w")
$ftwo= File.open( MUTEX_FILE_PATH)
puts "start"
$fone.flock( File::LOCK_EX)
puts "locked"
$fone.flock( File::LOCK_EX)
puts "so what"
$ftwo.flock( File::LOCK_EX)
puts "dontcare"
打印除dontcare
以外的所有内容。
因此基于文件的程序不起作用,因为
$mutex.flock(File::LOCK_EX)
从不阻止。