LoginSignup
5
3

More than 5 years have passed since last update.

yieldを悪用して一時停止可能なスレッドを実装する

Posted at

普通のサブルーチンがあったとします

def foo():
  print("foo")
  print("bar")

foo()  # "foo"と"bar"を出力

これにyieldを挟み込むことで分割します

def foo():
  print("foo")
  yield 0
  print("bar")
  yield 0

f = foo()
f.__next__()  # "foo"を出力
f.__next__()  # "bar"を出力

つまりひとつの処理をyieldでイテレータにします
これをThreadに応用すれば一時停止/再開/中断(pause/resume/haltメソッド)が実装できます

class Shred(Thread):
    def __init__(actions):
        Thread.__init__()
        self._actions = actions
        self.is_halted = False
        self.is_paused = False

    def run():
        while True:
            if self.is_paused:
                continue
            if self.is_halted:
                return
            try:
                self._actions.__next__()
            except StopIteration:
                self.is_halted = True
                return

    def pause():
        self.is_paused = True

    def resume():
        self.is_paused = False

    def halt():
        self.is_halted = True

(実際はフラグにEventを使ったほうがいいです)
これによって一時停止/再開/中断のチョークポイントをyieldのみで表現でき、
可読性の向上に加え、他のロジックの記述に集中できるようになります

def baz():
    for n in range(10):
        print(n)
        time.sleep(1)
        yield 0

s = Shred(baz())
s.start()
time.sleep(2)
#  0
#  1
#  2
s.pause()
time.sleep(2)
#  2秒間出力なし
s.resume()
time.sleep(2)
#  3
#  4
#  5
s.halt()
#  出力終了

ちなみにこのイテレータは内容をすべて実行すれば元のサブルーチンに戻ります

list(baz())
#  0
#  1
#  2
#  ...
#  9

・・・というのを考えついたのですが以下の理由で実際には使いませんでした

  • 驚き最大
  • たいてい後から普通に実装することになる
  • 副作用を積極的に活用するのは気が引ける
  • 言語に遊ばれてる気がして腹立ってきた
  • ていうか今どきこんな低級API叩いてる人いないよ

というわけでどれだけ参考になるか知りませんがこれで終わります

5
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3