使用队列工作时,互斥锁无法正常工作。为什么?

时间:2014-05-15 13:02:37

标签: ruby concurrency synchronization mutex

在此示例中,我希望以输出为puts的方式同步两个ababab...,而不会有任何双ab在输出上。

我有三个例子:使用队列,在内存中使用互斥锁并将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)}

3 个答案:

答案 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版本更容易理解)你可以看到它是使用MutexesConditionVariables实施。当您在队列示例中的$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解释的那样,有一些微妙的事情正在发生,例如自动解锁互斥锁以及你看不到的静默异常。

常用互斥体的方式
互斥锁用于访问特定的共享资源,如变量或文件。变量和文件的同步因此允许多个线程同步。互斥体本身并不真正同步线程。

例如:

  1. thread_athread_b可以通过共享的布尔变量进行同步,例如true_a_false_b
  2. 每次使用时都必须访问,测试和切换该布尔变量 - 一个多步骤过程。
  3. 必须确保这个多步骤过程以原子方式发生,即不被中断。这是你使用互斥锁的时候。一个简单的例子如下:
  4. 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)

从不阻止。