5

この答えのために私は次のようなコードを書きました:

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"そのコードは、存在するRubyでは厄介に思えbegin/rescue/ensureます。そこで、他のいくつかの実装を試しました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」になるので、私のロジックはオフでなければなりません。

スレッドローカル変数を介してループの終わりで停止します

この2回目の試行では、「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
4

3 に答える 3

6

スレッドを強制終了する代わりに、事前定義されたポイントで停止する変数を反転させてみませんか?ループを繰り返してループの最後で終了すると、それほど問題は発生しません。

例えば:

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と、スレッドがどこにあるのかわからず、スレッドには、スレッドが実行していることをクリーンアップする機会が与えられません。丁寧な「実行の停止」要求が尊重されない場合は、いつでもスレッドを強制終了できます。

于 2012-04-21T23:23:12.630 に答える
3

私はあなたの同期停止条件アプローチを好みますが、いくつかのバグがあります:

  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を含むソリューションは避けます。これらの動作は予測可能で正しいものではないためです。これらのメソッドの破損については、CharlesNutterの暴言を参照してください。

Mutex#synchronizeは、親スレッドがvarを設定するときの競合状態の正確なタイミングを本当に気にする場合にのみ、この単純なブール値の反転に必要です。この例ではそうは思われないので、オーバーヘッドを回避できます。設定してstop通常どおりに読み取ります。

于 2012-04-21T23:36:22.577 に答える
2

ミューテックスの例では、スレッドが終了するのを待ってからメソッドを終了する必要があります。現在stop、trueに設定してから、メソッドBを終了し、スクリプトを印刷して終了してから、スピナースレッドがウェイクアップし、最後のバックスペースを印刷して*文字を削除できるようにします。

また、breakinstopm.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で機能します。

于 2012-04-21T23:27:52.300 に答える