Python multiprocessing

17.2. multiprocessing ? プロセスベースの並列処理 ? Python 3.5.2 ドキュメント

Python にはこんな標準モジュールがあったんだ。
コレでマルチコア CPU をフル活用できるかも、早速使ってみよう。

#!/usr/bin/env python3

import multiprocessing

arr = [1, 2, 3]

def on_mp(num):
    print(num * 2)
    arr.append(num * 2)

for num in arr:
    p = multiprocessing.Process(target=on_mp, args=(num,))
    p.start()
    p.join() 

print(arr)

''' output
2
4
6
[1, 2, 3]
'''

…なんだこれ。

[1, 2, 3, 2, 4, 6]
が期待できるけどならない、例外もエラーも吐かない。
append した整数はいったいどこに格納されたんでしょう?

日本語で探しても皆 print ばかり、気がついていない人って多いかも。
せっかくマルチスレッドにしても変数に値を格納できなきゃ無意味だ。
色々探してなんとか見つけた。

multithreading – Python multiprocessing.Pool: when to use apply, apply_async or map? – Stack Overflow

apply_async メソッドの callback 指定からなら変数に入れられるようだ。
クラスメソッドでも大丈夫なのかな?試してみる

#!/usr/bin/env python3

import multiprocessing

class MpTest:
    def __init__(self):
        self.arr = [1, 2, 3]
        pool = multiprocessing.Pool()
        for num in self.arr:
            pool.apply_async(self.on_pool, args=(num,), callback=self.on_pool_result)
        pool.close()
        pool.join()

    def on_pool(self, num):
        return num * 2

    def on_pool_result(self, result):
        self.arr.append(result)

app = MpTest()
print(app.arr)
''' output
[1, 2, 3, 2, 4, 6]
'''

うん大丈夫。
よしコレで PyGObject でも本格的なマルチスレッドが。

#!/usr/bin/env python3

import multiprocessing, gi, sys
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio, Gdk, GdkPixbuf

GF_PATH = "/home/sasakima-nao/pic/test"

class MpTest(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        button = Gtk.Button(label="show CBZ")
        button.connect("clicked", self.on_button_clicked)
        self.fbox = Gtk.FlowBox(valign=Gtk.Align.START, max_children_per_line=10, min_children_per_line=10)
        sc = Gtk.ScrolledWindow()
        sc.add(self.fbox)
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        vbox.pack_start(button, False, False, 0)
        vbox.pack_start(sc, True, True, 0)
        self.add(vbox)
        self.resize(800, 600)
        self.show_all()

    def on_button_clicked(self, button):
        d = Gio.file_new_for_path(GF_PATH);
        infolist = d.enumerate_children(
            Gio.FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
            Gio.FileQueryInfoFlags.NONE)
        pool = multiprocessing.Pool()
        for info in infolist:
            name = info.get_display_name()
            pool.apply_async(self.on_pool, args=(name,), callback=self.on_pool_result)
        pool.close()
        pool.join()

    def on_pool(self, name):
        s = "{0}/{1}".format(GF_PATH, name)
        p = GdkPixbuf.Pixbuf.new_from_file(s)
        minp = p.scale_simple(80, 100, GdkPixbuf.InterpType.BILINEAR)
        image = Gtk.Image(pixbuf=minp, visible=True)
        return image

    def on_pool_result(self, result):
        """
            Error @ result is a Python Type Onry ?
        """
        self.fbox.add(result)

class MpApp(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)

    def do_activate(self):
        MpTest(self)

app = MpApp()
app.run(sys.argv)

できなかった。

error_no_gtkwidget

callback で得られる引数を GtkWidget と認識できないようです。
裏技っぽく list にして戻しても駄目、GdkPixbuf を戻すようにしても駄目。
文字列や整数なら問題無し、Python の型以外は戻せないってことみたい。

スレッド自体は動いているから CPU 負荷を見てみる。

cpu_weit

うん見事にマルチコアをフル活用できている。
でも前回 C で作ったものより負荷が多い、速さでは話にならないな。
とはいえシングルスレッドよりはるかに速いのは間違いない。

一番負荷が掛かる GdkPixbuf への取り込みをマルチスレッドにしたいのに。
そういう使い道には現在は対応していないってことのようです。
いや手段を知らないだけかも、引き続き色々探してみる。