何かを書き留める何か

数学や読んだ本について書く何かです。最近は社会人として生き残りの術を学ぶ日々です。

『Effective Python』Item 37: スレッドはブロッキングI/Oに使い、並列処理は避けよう

『Effective Python』の続き。不遇のWindowswww.effectivepython.com

人によってはGlobal Interpreter Lock(GIL) と聞くと嫌な思い出が蘇ることがあるのだろうか。 私は恥ずかしながら並列・並行プログラミング未経験というアレな状況なのでGILには特に嫌な思い出はない。

素因数分解を例にマルチスレッドで高速化したいと夢見て、

from time import time
from threading import Thread


def factorize(number):
    for i in range(1, number + 1):
        if number % i == 0:
            yield i

numbers = [2139079, 1214759, 1516637, 1852285]


start = time()
for number in numbers:
    list(factorize(number))
end = time()
print('Took %.3f seconds' % (end - start))

という直列バージョンと、

class FactorizeThread(Thread):
    def __init__(self, number):
        super().__init__()
        self.number = number

    def run(self):
        self.factors = list(factorize(self.number))

start = time()
threads = []
for number in numbers:
    thread = FactorizeThread(number)
    thread.start()
    threads.append(thread)

for thread in threads:
    thread.join()
end = time()
print('Took %.3f seconds' % (end - start))

というマルチスレッドによる並列バージョンを実行して どちらも大差ない というつらい現実が紹介されている。

そんな悪名高い?GILにも利点があり、その中でもブロッキングI/O(同期I/O)に使うとよい、と述べている。

問題は、その例が Windowsでは動作しない のである。

import select

def slow_systemcall():
    select.select([], [], [], 0.1)

18.3. select — I/O 処理の完了を待機する — Python 3.4.3 ドキュメントには次のように書いてある。

引数に空のシーケンスを指定してもかまいませんが、3 つの引数全てを空のシーケンスにしてもよいかどうかはプラットフォームに依存します (Unix では動作し、Windows では動作しないことが知られています)。

http://docs.python.jp/3/library/select.html#select.select

並行・並列処理が何故難しいのか、それはシステム寄りの部分の理解がより一層求められるから、ではと仮説を立てている。 今まではPython側がプラットフォーム側の差異を吸収してくれていたがこの辺りになると吸収しきれずUnix系とWindowsで動きが異なってくる。 もっとも、Unix系で実装する分にはそこまで辛い思いをする必要はなさそうである。 問題はWindowsであり、中々大変な道のりが待っていそうである。