ナンクル力学系

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

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

with 3 comments

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&#91;&#93; = {
  {"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);
}
&#91;/sourcecode&#93;</div>
</div>
<div id="outline-container-12.4.1.2" class="outline-5">
<h5>setup.py</h5>
<div id="text-12.4.1.2">
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 で決めらる!

Advertisements

Written by tkf

May 11, 2009 at 9:05 pm

Posted in PC, programming

Tagged with ,

3 Responses

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

  2. chan luu 偽物

  3. Hi recently there. Let me start by introducing the author, his
    name is Mckinley but he never really liked that name.
    Meter reading may be the my primary income comes from and I’m doing great financially.
    I am really fond of hot air balooning nonetheless don’t let the time recently.
    For years I’ve been living in Oregon. Go to his how does a person find out more: LOUIS VUITTON アクセサリー


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: