くろねこ日記

ソフトウェアに関する技術メモが多いです.

シンプルなThreading

はじめに

昨日,Threadingを使用したコードを書きました. 書いた動機としては,もともとスレッド処理を書いたことがない僕は少してこづったことと,オブジェクト指向的なコードとかはググれば出るのですが簡単なのが少なかったからです.頭の中を整理するつもりでメモしておきます.

そもそもThreadって何?

まずスレッドの意味ですが,「プログラムの実行コード一つ辺りの単位」のことらしいです. つまり2スレッドは実行コードが2つ走っているということになります.

実行コードというのはプログラムが終了するまで実行しつづけようとします. ですので実行コードが終了するということはその実行コードのスレッドが一つ消えるということになります.

ではプログラム言語に搭載されている機能としてのスレッドとはどういう意味をなすのかですが,これは実行コードを複数扱ったり停止したり破壊したりと実行コードそのものを制御するためのものです.「実行コード中に停止させたい」,「いくつか実行コードがあってそれらを並列に動かしたい」という場面に有効です.

シンプルなThreadingコード

まずはfooという"foo"という文字列を吐きつづける実行コード(関数という形ですが)をthreadingから制御してみようと思います.

実行コードの生成と開始をして,1秒待ったあと停止させるというものです. つまり,1秒間メインコードが停止しているときでも関係なくfooは動いていることになります.ですので,1秒間fooを出力するはずです. なお,Pythonには停止にあたるものが無いので,ここではflagを使ってwhileループを抜けさせたりするのに使ってます.

import threading
import time

def foo():
    while flag:
        print("foo")

th=threading.Thread(target=foo)
flag=True
th.start()
time.sleep(1)
flag=False
th.join()

注目すべきはflagをTrueにしたりFalseにするタイミングですね.flagはfoo関数のwhileを無限ループさせるのに必要となる変数です.flag=Trueであればwhile Trueとなりprint("foo")が無限に実行されます.Falseにすればwhile Falseとなりwhile文を抜けます.

実はこのwhileの制御が少し悩みました.whileを使って書きたくないなと思いつつも,今のところはflagによる制御が簡単かなと思っています.もし二回個別に実行したいときには再びthreading.Threadで生成してあげる必要がありますので注意してください. 次のようにするともう一度実行できます.

import threading
import time

def foo():
    while flag:
        print("foo")

th=threading.Thread(target=foo)
flag=True
th.start()
time.sleep(1)
flag=False
th.join()
th=threading.Thread(target=foo)
flag=True
th.start()
time.sleep(1)
flag=False
th.join()

また,スレッドにはjoinというのがあります.これもよく使われるみたいです. これは実行コードが終了するまでメインコードの動きを止めることができます.

例えばfooとhogeの二つの関数があり,fooを終えたあとにhogeを開始したいとします. しかしスレッドではfooとhogeを次にように書くとほぼ同時に実行されてしまいます.

import threading

def foo():
    for i in range(10):
        print("foo:"+str(i))

def foo2():
    for i in range(10):
        print("hoge:"+str(i))
th=threading.Thread(target=foo)
th.start()
th2=threading.Thread(target=foo2)
th2.start()

結果

foo:0
foo:1
foo:2
foo:3
hoge:0
foo:4
hoge:1
foo:5
foo:6
hoge:2
foo:7
foo:8
hoge:3
foo:9
hoge:4
hoge:5
hoge:6
hoge:7
hoge:8
hoge:9

わかりやすくfooとhogeに対して数字を割りふりました. 同時に実行するとfooとhogeが混ざりあっているのがわかりますね.前半fooが多いのはstartのタイミングがfooのほうが早いからだと思われます.

ではfooをstart直後にjoinを置いてみましょう.

import threading

def foo():
    for i in range(10):
        print("foo:"+str(i))

def foo2():
    for i in range(10):
        print("hoge:"+str(i))
th=threading.Thread(target=foo)
th.start()
th.join()
th2=threading.Thread(target=foo2)
th2.start()

結果

foo:0
foo:1
foo:2
foo:3
foo:4
foo:5
foo:6
foo:7
foo:8
foo:9
hoge:0
hoge:1
hoge:2
hoge:3
hoge:4
hoge:5
hoge:6
hoge:7
hoge:8
hoge:9

このようにfooが終わるまで待ってくれています.この例だとシングルタスクで動かしても大差ありませんが,例えばfoo,hoge,barの3つのスレッドがあり,fooとhogeはほぼ同時に走らせてbarはfooとhogeの後に実行したいときなんかは有効ですね.

まとめ

スレッドの基本的な考えかたと使いかたをメモしました.

  • スレッドとは実行コードの単位を示す.

  • PythonにはThreadingというスレッドそのものを扱うライブラリがあり,実行コードを並列に実行したり直列に実行したりできる.