停止一个线程,确保运行某些最终代码

时间:2012-04-21 22:16:41

标签: ruby multithreading console

对于this answer我编写的代码如下:

def show_wait_spinner
  dirty = false
  spinner = Thread.new{
    loop{
      print "*"
      dirty = true
      sleep 0.1
      print "\b"  
      dirty = false
    }    
  }
  yield
  spinner.kill
  print "\b" if dirty
end

print "A"
show_wait_spinner{ sleep rand }
puts "B"

目标是确保最终输出为"AB" - 如果线程尚未打印,则打印最终"\b"。在begin/rescue/ensure存在的Ruby中,该代码对我来说似乎很混乱。所以我尝试了show_wait_spinner的其他一些实现;所有这些都无法确保"AB"始终是输出,而不是"A*B""AB*"

是否有更简洁,更Ruby的方式来实现这种逻辑?

通过Mutex

停止循环结束
def show_wait_spinner
  stop = false
  stopm = Mutex.new
  spinner = Thread.new{
    loop{
      print "*"
      sleep 0.1
      print "\b"
      stopm.synchronize{ break if stop }
    }    
  }
  yield
  stopm.synchronize{ stop = true }
  STDOUT.flush
end

...但我的逻辑必须关闭,因为这总是导致“A * B”。

通过Thread-local变量

停止循环结束

第二次尝试导致有时打印“A * B”,有时“AB”:

def show_wait_spinner
  stop = false
  spinner = Thread.new{
    Thread.current[:stop] = false
    loop{
      print "*"
      sleep 0.1
      print "\b"
      stopm.synchronize{ break if Thread.current[:stop] }
    }    
  }
  yield
  spinner[:stop] = true
  STDOUT.flush
end

杀死并确保线程

def show_wait_spinner
  spinner = Thread.new{
    dirty = false
    begin
      loop{
        print "*"
        dirty = true
        sleep 0.1
        print "\b"
        dirty = false
      }    
    ensure
      print "\b" if dirty
    end
  }
  yield
  spinner.kill
  STDOUT.flush
end

提升和拯救线程

def show_wait_spinner
  spinner = Thread.new{
    dirty = false
    begin
      loop{
        print "*"
        dirty = true
        sleep 0.1
        print "\b"
        dirty = false
      }    
    rescue
      puts "YAY"
      print "\b" if dirty
    end
  }
  yield
  spinner.raise
  STDOUT.flush
end

3 个答案:

答案 0 :(得分:4)

为什么不翻转导致它停在预定点的变量而不是杀死你的线程?如果让它循环并在循环结束时退出,那么你将不会遇到太多麻烦。

例如:

def show_wait_spinner
  running = true

  spinner = Thread.new do
    while (running) do
      print "*"
      sleep 0.1
      print "\b"  
    end
  end

  yield
  running = false
  spinner.join
end

print "A"
show_wait_spinner{ sleep rand }
puts "B"

当你调用Thread#kill时,你不知道线程在哪里,线程没有机会清理它正在做的事情。如果不遵守礼貌的“停止运行”请求,您可以随时终止该线程。

答案 1 :(得分:3)

我更喜欢你的同步停止条件方法,但你有几个错误:

  1. 设置停止变量后,几乎立即结束 程序,所以停止线程的东西是程序退出,而不是 循环中的条件测试;使用Thread#join等待线程 正常退出,你将得到你想要的一致输出。
  2. 同步块中的
  3. break会突破块,而不是 循环。

    def show_wait_spinner
      stop = false
      stopm = Mutex.new
      spinner = Thread.new{
        loop{
          print "*"
          sleep 0.1
          print "\b"
          break if stopm.synchronize{ stop }
        }
      }
      yield
      stopm.synchronize{ stop = true }
      spinner.join
      STDOUT.flush
    end
    
    print "A"
    show_wait_spinner{ sleep rand }
    puts "B"
    
  4. 我会避免任何涉及Thread#raise和Thread#kill的解决方案,因为他们的行为永远无法预测和纠正,请参阅Charles Nutter's rant about the brokenness of these methods

    Mutex#synchronize仅对于这个简单的布尔翻转是必要的,如果你真的非常关心在父线程设置var的情况下围绕竞争条件的精确定时,在这个例子中不太可能,所以你可以避免开销,只需设置并正常读取stop

答案 2 :(得分:2)

在您的互斥锁示例中,您需要在退出方法之前等待Thread完成。目前您将stop设置为true,然后退出方法,打印B并在微调器线程能够唤醒并打印最后一个退格键以删除*字符之前结束脚本。

此外,break中的stopm.synchronize{ break if stop }仅退出内部块,而不是循环,因此您需要使用catch / throw等。

但是,您不需要互斥锁。这在1.9.3中适用于我:

def show_wait_spinner
  exit = false
  spinner = Thread.new{
    loop{
      print "*"
      sleep 0.1
      print "\b" 
      break if exit
    }    
  }
  yield
  exit = true
  spinner.join
end

在顶部添加$stdout.sync = true使其在1.8.7中有效。