ナンクル力学系

学んだ事を書き連ねていこう。

python の C 拡張をスレッドに対応させる方法 (with callback)

with one comment

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 で決めらる!

Written by tkf

May 11, 2009 at 9:05 pm

Posted in PC, programming

Tagged with ,

One Response

Subscribe to comments with RSS.

  1. よく考えたら
    Py_XINCREF(temp); /* 新たなコールバックへの参照を追加 */
    Py_XDECREF(my_callback); /* 以前のコールバックを捨てる */
    my_callback = temp; /* 新たなコールバックを記憶 */
    って要らないなあ.my_callbackに以前のなんて入ってないんだし.

    tkf

    May 11, 2009 at 9:27 pm


Leave a Reply