同步线程启动

时间:2018-12-30 01:34:30

标签: ruby multithreading

我正在运行一些代码(经过简化,但仍在下面破坏版本),该代码在〜1/3000次执行中等待第一个开关失败。应该发生的是:

  • threads[0]启动并获取互斥锁
  • threads[0]通知cond_main,以便主线程可以创建thread[1]
  • thread[1] / thread[0]做一些工作,等待对方的信号

不幸的是,它在thread[0]中失败-cond.wait以超时结束并引发异常。我该如何同步它,以确保cond_main不会太早收到信号?

理想情况下,我想从主线程传递一个锁定的互斥锁,然后在第一个生成的线程中将其解锁,但是Ruby要求在同一线程中解锁互斥锁-因此这是行不通的。

自包含的复制器(本身并没有多大意义,但实际的工作被剥离了):

def run_test
  mutex     = Mutex.new
  cond      = ConditionVariable.new
  cond_main = ConditionVariable.new
  threads   = []

  t1_done = false
  t2_done = false

  threads << Thread.new do
    mutex.synchronize do
      # this needs to happen first
      cond_main.signal
      cond.wait(mutex, 2)
      raise 'timeout waiting for switch' if !t2_done

      # some work
      t1_done = true
      cond.signal
    end
  end
  cond_main.wait(Mutex.new.lock, 2)

  threads << Thread.new do
    mutex.synchronize do
      cond.signal
      # some work
      t2_done = true
      cond.wait(mutex, 2)
      raise 'timeout waiting for switch' if !t1_done
    end
  end

  threads.map(&:join)
end

5000.times { |x|
  puts "Run #{x}"
  run_test
}

在Ruby 2.5.3上测试

1 个答案:

答案 0 :(得分:1)

设置一个while块,以在第二个线程完成时停止等待(请参见更多here):

def run_test
  mutex     = Mutex.new
  cond      = ConditionVariable.new
  cond_main = ConditionVariable.new
  threads   = []

  spawned = false

  t1_done = false
  t2_done = false

  threads << Thread.new do
    mutex.synchronize do
      while(!spawned) do
        cond.wait(mutex, 2)
      end
      raise 'timeout waiting for switch' if !t2_done

      # some work
      t1_done = true
      cond.signal
    end
  end

  threads << Thread.new do
    mutex.synchronize do
      spawned = true
      cond.signal
      # some work
      t2_done = true
      cond.wait(mutex, 2)
      raise 'timeout waiting for switch' if !t1_done
    end
  end

  threads.map(&:join)
end

50000.times { |x| 
  puts x 
  run_test 
}

或者,使用counting semaphore,我们可以为线程分配一些优先级:

require 'concurrent-ruby'

def run_test
  mutex     = Mutex.new
  sync      = Concurrent::Semaphore.new(0)
  cond      = ConditionVariable.new
  cond_main = ConditionVariable.new
  threads   = []

  t1_done = false
  t2_done = false

  threads << Thread.new do
    mutex.synchronize do
      sync.release(1)
      # this needs to happen first
      cond.wait(mutex, 2)
      raise 'timeout waiting for switch' if !t2_done

      # some work
      t1_done = true
      cond.signal
    end
  end

  threads << Thread.new do
    sync.acquire(1)
    mutex.synchronize do
      cond.signal
      # some work
      t2_done = true
      cond.wait(mutex, 2)
      raise 'timeout waiting for switch' if !t1_done
    end
  end

  threads.map(&:join)
end

50000.times { |x| 
  puts x 
  run_test 
}

我更喜欢第二种解决方案,因为它可以让您控制线程的顺序,尽管感觉有点脏。

出于好奇,在Ruby 2.6上,您的代码似乎没有引发异常(经测试,运行次数超过10M)。