【问题标题】:Stopping a thread, ensuring that certain final code is run停止线程,确保运行某些最终代码
【发布时间】:2012-05-03 01:24:03
【问题描述】:

对于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”。

通过线程局部变量在循环结束时停止

第二次尝试导致有时打印“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

【问题讨论】:

    标签: ruby multithreading console


    【解决方案1】:

    与其杀死你的线程,为什么不翻转一个变量,让它在预定义的点停止?如果您让它循环并在循环结束时退出,您将不会遇到太多麻烦。

    例如:

    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 和 2 都试图做到这一点(尽管使用 break 而不是显式的 while 循环停止)。我不确定为什么您的有效而那些无效,但是+1,这有效:)
    • @Phrogz 你还需要spinner.join
    • @matt Right-o,这正是我所缺少的:p
    【解决方案2】:

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

    1. 设置停止变量后,您几乎立即结束 程序,所以停止线程的是程序退出,而不是 循环中的条件测试;使用 Thread#join 等待线程 正常退出,您将获得所需的一致输出。
    2. 同步块中的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"
      

    我会避免任何涉及 Thread#raise 和 Thread#kill 的解决方案,因为它们的行为永远无法预测和正确,请参阅 Charles Nutter's rant about the brokenness of these methods

    只有当父线程设置 var 时,如果您真的非常关心竞争条件周围的精确计时,则 Mutex#synchronize 仅对于这种简单的布尔翻转是必需的,在本示例中这不太可能,因此您可以避免开销,只需正常设置和读取stop

    【讨论】:

      【解决方案3】:

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

      另外,stopm.synchronize{ break if stop }中的break只退出内部块,不退出循环,所以你需要使用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 中使用。

      【讨论】:

      • @tadman 在提交按钮前击败了我,但我想我在您的尝试中发现了一些问题,所以我也登顶了。
      猜你喜欢
      • 2023-03-12
      • 2021-06-04
      • 1970-01-01
      • 2016-08-14
      • 2011-06-21
      • 2020-08-15
      • 1970-01-01
      • 2012-02-04
      相关资源
      最近更新 更多