python の C 拡張をスレッドに対応させる方法 (with callback)
python の C 拡張をスレッドに対応させつつ,時々は Python の callback 関数を呼びたい時は,グローバルインタプリタロック(GIL) を取得しないといけないですよ,という例.
基本的には 「8.1 スレッド状態 (thread state) とグローバルインタプリタロック (global interpreter lock)」 に書いてある方法.
こんなことをする:
Py_BEGIN_ALLOW_THREADS ...ブロックが起きるような何らかの I/O 操作... PyGILState_STATE gstate; gstate = PyGILState_Ensure(); ...コールバック呼び出し... PyGILState_Release(gstate); Py_END_ALLOW_THREADS
コンパイルに必要なファイル:
callbackthreadmodule.c
#include <Python.h>
static PyObject *
set_and_call_callback(PyObject *dummy, PyObject *args)
{
PyObject *temp;
PyObject *my_callback = NULL;
PyObject *callback_arg;
PyObject *callback_result;
PyGILState_STATE gstate;
int i;
if (! PyArg_ParseTuple(args, "OO:set_callback", &temp, &callback_arg)) {
return NULL;
}
if (!PyCallable_Check(temp)) {
PyErr_SetString(PyExc_TypeError, "parameter must be callable");
return NULL;
}
Py_XINCREF(temp); /* 新たなコールバックへの参照を追加 */
Py_XDECREF(my_callback); /* 以前のコールバックを捨てる */
my_callback = temp; /* 新たなコールバックを記憶 */
Py_BEGIN_ALLOW_THREADS
for(i=0; i<1000000000; ++i){
if( !(i % 200000000) ){
printf("i=%d\n", i);
gstate = PyGILState_Ensure();
callback_result = PyEval_CallObject(my_callback, callback_arg);
if (callback_result == NULL){
return NULL; /* エラーを返す */
}
PyGILState_Release(gstate);
}
}
Py_END_ALLOW_THREADS
/* "None" を返す際の定型句 */
Py_INCREF(Py_None);
return Py_None;
}
static PyMethodDef module_methods[] = {
{"set_and_call_callback", (PyCFunction)set_and_call_callback,
METH_VARARGS, "set callback"},
{NULL} /* Sentinel */
};
#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
initcallbackthread(void)
{
(void) Py_InitModule("callbackthread", module_methods);
}
setup.py
from distutils.core import setup, Extension
module1 = Extension(
'callbackthread',
sources = ['callbackthreadmodule.c']
)
setup(
name = 'my',
ext_modules = [module1],
)
コンパイル方法 (その場にcallbackthread.soを作る):
python setup.py config build build_ext –inplace
まずは, thread モジュールを使ってみた
test_thread.py
import callbackthread
import thread
x = (1,2,3)
y = dict(a=2,c=2)
def print_anything(a):
print a
thread.start_new_thread(callbackthread.set_and_call_callback, (print_anything, (x,)))
thread.start_new_thread(callbackthread.set_and_call_callback, (print_anything, (y,)))
実行
% python test_thread.py i=0 zsh: segmentation fault python test_thread.py % python test_thread.py i=0 i=0 % python test_thread.py i=0 i=0 zsh: segmentation fault python test_thread.py
ん?
確率的に segmentation fault している... そして,時々しないけどなぜだか一回目しかループしてないし. 謎.よく分からなかったけど,threading モジュールだとうまくいった↓
threading モジュールだとうまく行く
test_threading.py
import callbackthread
import threading
x = (1,2,3)
y = dict(a=2,c=2)
def print_anything(a):
print a
class TestThread(threading.Thread):
def run(self):
callbackthread.set_and_call_callback(print_anything, (x,))
TestThread().start()
TestThread().start()
結果
% python test_threading.py i=0 (1, 2, 3) i=0 (1, 2, 3) i=200000000 (1, 2, 3) i=200000000 (1, 2, 3) i=400000000 (1, 2, 3) i=400000000 (1, 2, 3) i=600000000 (1, 2, 3) i=600000000 (1, 2, 3) i=800000000 (1, 2, 3) i=800000000 (1, 2, 3)
うまくいった!
ちゃんと CPU 二つ使ってることも確かめられた!
どうやら, 「sh1.2 pyblosxom : pythonのthread/threadingモジュールを見てみた」 と同じ問題くさい. しかし segmentation fault って...
やりたかったことは:
test_functor.py
import callbackthread
import threading
x = (1,2,3)
y = dict(a=2,c=2)
class Func():
i = 0
def call_back(self, a):
print "Func.i =",self.i
print a
self.i += 1
func = Func()
class TestThread(threading.Thread):
def run(self):
callbackthread.set_and_call_callback(func.call_back, (x,))
TestThread().start()
TestThread().start()
実行
% python test_functor.py i=0 Func.i = 0 (1, 2, 3) i=0 Func.i = 1 (1, 2, 3) i=200000000 Func.i = 2 (1, 2, 3) i=200000000 Func.i = 3 (1, 2, 3) i=400000000 Func.i = 4 (1, 2, 3) i=400000000 Func.i = 5 (1, 2, 3) i=600000000 Func.i = 6 (1, 2, 3) i=600000000 Func.i = 7 (1, 2, 3) i=800000000 Func.i = 8 (1, 2, 3) i=800000000 Func.i = 9 (1, 2, 3)
Func.i ってのを二つのスレッドが交互に書き換えてるのが分かる
python 側のオブジェクトを操作するタイミングを C で決めらる!
よく考えたら
Py_XINCREF(temp); /* 新たなコールバックへの参照を追加 */
Py_XDECREF(my_callback); /* 以前のコールバックを捨てる */
my_callback = temp; /* 新たなコールバックを記憶 */
って要らないなあ.my_callbackに以前のなんて入ってないんだし.
tkf
May 11, 2009 at 9:27 pm