2006年6月 8日 (木)

windowsサービスのapacheでtracを動かす

以下の環境変数の設定がきちんとされてるかチェック

  • PYTHONHOME
  • SVN_HOME
  • DIFFUTILS_HOME
  • APR_ICONV_PATH
  • PATH

make_obcallback: could not import mod_python.apache
とか出ることがある。

| | コメント (0)

2006年6月 4日 (日)

Pythonのスレッド管理

1 スレッド状態 (thread state) とグローバルインタプリタロック (global interpreter lock)
の日本語も英語も理解しづらかったので今日理解したことを俺語でメモ

■初めに

Pythonのマルチスレッド実装は擬似マルチスレッド。ネイティブコードレベルでシングルであるかマルチであるかにはかかわらず。
Pythonレベルではひとつのスレッド同期用フラグ(グローバルインタプリタロック)があって、このロックを取得できたスレッドだけがコードを実行できる。
このロックはバイトコード100個分とブロックする可能性のある操作の前を目安に自動的に解放されて、次のスレッドが獲得できるようになっている。
つまり、Pythonで管理されてるスレッド群がOSレベルで本当に同時に実行されることはない。(んだとおもう)

んで、Pythonの中だけで完結してるときはこんなことを知らなくてもいいんだけど、Cで拡張しようとするとこの仕組みに参加しなければならない。

■グローバルインタプリタロックの初期化

まず初めに必要なのがグローバルインタプリタロックの初期化。

void PyEval_InitThreads()

Pythonインタプリタが走り始める最初のメインスレッドがこれを実行しなきゃいけない。
これを実行するとグローバルインタプリタロックが初期化されて、メインスレッドがその所有者となる。

ちなみにここで出てくる話はPython/C双方ともに一つのスレッドしか使わないのであればまったく関係ない。PyEval_InitThreadsも含め、以降に出てくるAPIは全て関係がない。
が、pythonの中でスレッドが作られているのならCのコードがPythonを起動したメインスレッドからのコールバックで動く場合でも以降のAPIは必要となる。
が、その場合はPyEval_InitThreadsは必要ない。Pythonの中で初めて新しいスレッドが作られるときに自動的にPyEval_InitThreadsされるからである。

PyEval_InitThreadsが必要となるのはPythonコード内で初めて新しいスレッドが作られるのよりも前に以降のAPIを使いたいときとなる。
スレッドのロックに関するたぶん全てのAPIがグローバルインタプリタロックを必要として、それを初期化するのがPyEval_InitThreadsなので。

スレッドのロックAPIを使う人は全てのAPIより前でPyEval_InitThreads()を呼んどいたほうがいいだろう。

■PythonにアクセスするCのスレッドの初期化・解放

次に必要なのがPythonAPIを使うスレッドの初期化と解放。
先に書いたとおり、CのスレッドがPythonAPIを使うときにはPythonのスレッド管理の仕組みに自身のスレッドを参加させなければならない。
CのスレッドをPythonのスレッド管理の仕組みに参加させるには、スレッド状態オブジェクトを作ってスレッドに関連付ける必要がある。

このスレッド状態オブジェクトを扱うときの流れはPythonのドキュメントにあるサンプルコードを引用すると以下のようになる。

PyThreadState *tstate;
PyObject *result;
/* interp is your reference to an interpreter object. */
tstate = PyThreadState_New(interp);
PyEval_AcquireThread(tstate);
/* Perform Python actions here.  */
result = CallSomeFunction();
/* evaluate result */
/* Release the thread. No Python API allowed beyond this point. */
PyEval_ReleaseThread(tstate);
/* You can either delete the thread state, or save it
until you need it the next time. */
PyThreadState_Delete(tstate);

スレッド状態オブジェクトはPyThreadState_Newでつくる。

PyThreadState* PyThreadState_New( PyInterpreterState *interp )

指定したインタプリタオブジェクトに属する新たなスレッド状態オブジェクトを生成します。インタプリタロックを保持しておく必要はありませんが、この関数を次々に呼び出す必要がある場合には保持しておいたほうがよいでしょう。

PyThreadState_Newの引数には先のインタプリタ状態オブジェクトのインスタンスへのポインタが必要となる。 このポインタを取得するには以下の方法がある。

  • PyThreadState::interpから取得する。

    PyThreadStateを取得するAPIにはPyThreadState_Get()などがある。

    PyThreadState* PyThreadState_Get()

    現在のスレッド状態を返します。インタプリタロックを保持していなければなりません。現在のスレッド状態が NULLなら、(呼び出し側が NULLチェックをしなくてすむように) この関数は致命的エラーを起こすようになっています。

    つまり、Pythonで管理されているスレッドでしか使えない。

  • PyInterpreterState_New()で作る。
    PyInterpreterState* PyInterpreterState_New()

    新しいインタプリタ状態オブジェクトを生成します。インタプリタロックを保持しておく必要はありませんが、この関数を次々に呼び出す必要がある場合には保持しておいたほうがよいでしょう。

    void PyInterpreterState_Clear(PyInterpreterState *interp)

    インタプリタ状態オブジェクト内の全ての情報をリセットします。インタプリタロックを保持していなければなりません。

    void PyInterpreterState_Delete(PyInterpreterState *interp)

    インタプリタ状態オブジェクトを破壊します。インタプリタロックを保持しておく必要はありません。インタプリタ状態はPyInterpreterState_Clear() であらかじめリセットしておかなければなりません。

PyThreadState_Getを使うならインタプリタロックを持っている必要がある。インタプリタロックの取得と解放には次のAPIを使う。

void PyEval_AcquireLock()

グローバルインタプリタロックを獲得します。ロックは前もって作成されていなければなりません。この関数を呼び出したスレッドがすでにロックを獲得している場合、デッドロックに陥ります。この関数はコンパイル時にスレッドサポートを無効化すると利用できません。

void PyEval_ReleaseLock()

グローバルインタプリタロックを解放します。ロックは前もって作成されていなければなりません。この関数はコンパイル時にスレッドサポートを無効化すると利用できません。

多分上のAPIはスレッドがPython管理下にないと使えない。
また、通常PythonAPIを使うようなCコードのエントリポイントではPythonのスレッド管理下にあり、グローバルインタプリタロックを持っていることが多そう。
ということからPython管理下のスレッドがインタプリタ状態オブジェクトを取得して、それをこれからPython管理下に入れたいスレッドに渡して、そのスレッドがPyThreadState_Newしてスレッド状態オブジェクトを作る、というステップを踏むことになると思う。もしくはPyThreadState_NewするたびにPyInterpreterState_Newするか。

PyThreadState_Newしてスレッド状態オブジェクトができたらPyEval_AcquireThreadでスレッドに関連付ける。

void PyEval_AcquireThread( PyThreadState *tstate )

グローバルインタプリタロックを取得して、この関数を実行したスレッドに引数のスレッド状態オブジェクトを関連付ける。

グローバルインタプリタロックを獲得し、現在のスレッド状態を tstate に設定します。tstate は NULLであってはなりません。ロックはあらかじめ作成されていなければなりません。この関数を呼び出したスレッドがすでにロックを獲得している場合、デッドロックに陥ります。この関数はコンパイル時にスレッドサポートを無効化すると利用できません。

PyEval_AcquireThreadの実行直後からグローバルインタプリタロックはこのスレッドのものである。
PythonAPIをいじる処理が終わったらPyEval_ReleaseThreadする。

void PyEval_ReleaseThread( PyThreadState *tstate )

引数のスレッド状態オブジェクトがこの関数を実行したスレッドに関連付けられていたら関連付けを解放し、グローバルインタプリタロックを解放する。

現在のスレッド状態をリセットして NULL にし、グローバルインタプリタロックを解放します。ロックはあらかじめ作成されていなければならず、かつ現在のスレッドが保持していなければなりません。tstate は NULLであってはなりませんが、その値が現在のスレッド状態を表現しているかどうかを調べるためにだけ使われます -- もしそうでなければ、致命的エラーが報告されます。この関数はコンパイル時にスレッドサポートを無効化すると利用できません。

ここで再び先のサンプルコードを見返してみる。

/* Release the thread. No Python API allowed beyond this point. */
PyEval_ReleaseThread(tstate);
/* You can either delete the thread state, or save it
until you need it the next time. */
PyThreadState_Delete(tstate);

PyThreadState_Deleteではいらなくなったスレッド状態オブジェクトを削除できる。

void PyThreadState_Delete( PyThreadState *tstate )

スレッド状態オブジェクトを破壊します。インタプリタロックを保持していなければなりません。スレッド状態はPyThreadState_Clear() であらかじめリセットしておかなければなりません。

ここでへんなのはPyEval_ReleaseThreadでグローバルインタプリタロックを解放しているはずなのに、グローバルインタプリタロックを持っていないと呼べないPyThreadState_Deleteを呼んでいるところ。
インタプリタロックとグローバルインタプリタロックは別なのか?とも思ったけどそれもたぶん違う。ていうかそもそもPyThreadState_Clearしてない。

void PyThreadState_Clear( PyThreadState *tstate )

スレッド状態オブジェクト内の全ての情報をリセットします。インタプリタロックを保持していなければなりません。

多分ドキュメントが最後のステップをいろいろ省略してるんじゃないかと。その辺の流れをきちんと整理してみると以下のようになる。

ReleaseThreadした後、いらなくなったスレッド状態オブジェクトを削除したいときはスレッドの開始時にPython管理下のスレッドからインタプリタ状態オブジェクトをもらったように、今度はPython管理下のスレッドにいらなくなったスレッド状態オブジェクトのポインタを渡し、そのスレッドでグローバルインタプリタロックを取得している状態で、PyThreadState_Clearし、PyThreadState_Deleteする。

■スレッドからの一時的なグローバルインタプリタロックの解放

たとえばPython管理下に入ったCスレッドでネットにアクセスしてデータをダウンロードする処理をしたいときその間ずっとグローバルインタプリタロックを握りっぱなしだと、たとえPythonのコードがマルチスレッドで書かれていてもそれら全てのスレッドがブロックしてしまう。
そこでダウンロードする前のPythonAPIを使い終わった時点でグローバルインタプリタロックを手放し、ダウンロードが終わったあとでPythonAPIを使う前にもう一度グローバルインタプリタロックを取得しなおす必要がある。

それらをするのが

Py_BEGIN_ALLOW_THREADS
...ブロックが起きるような何らかの I/O 操作...
Py_END_ALLOW_THREADS

というマクロ。 これは

PyThreadState *_save;
_save = PyEval_SaveThread();
...ブロックが起きるような何らかの I/O 操作...
PyEval_RestoreThread(_save);

というコードがマクロ化されたもの。

PyThreadState *_save;
_save = PyThreadState_Swap(
NULL
);
PyEval_ReleaseLock();
...ブロックが起きるような何らかの I/O 操作...
PyEval_AcquireLock();
PyThreadState_Swap(_save);

という書き方もできるが、errnoが退避されずロックを手放してもう一度獲得したときに変更されていたり、pythonインタプリタのスレッドサポートが無効の場合そもそもPyEval_ReleaseLock/PyEval_AcquireLockがロック操作をしないのだとか。とりあえずPy_BEGIN_ALLOW_THREADS~Py_END_ALLOW_THREADSしとけばよさそう。

PyThreadState* PyEval_SaveThread()

(インタプリタロックが生成されていて、スレッドサポートが有効の場合) インタプリタロックを解放して、スレッド状態を NULLにし、以前のスレッド状態 (NULLにはなりません) を返します。ロックがすでに生成されている場合、現在のスレッドがロックを獲得していなければなりません。

void PyEval_RestoreThread( PyThreadState *tstate)

(インタプリタロックが生成されていて、スレッドサポートが有効の場合) インタプリタロックを獲得して、現在のスレッド状態を tstate に設定します。tstate は NULLであってはなりません。この関数を呼び出したスレッドがすでにロックを獲得している場合、デッドロックに陥ります。 (この関数はコンパイル時にスレッドサポートを無効化すると利用できません。)

■PyGILState_Ensure~PyGILState_Release

という素敵APIも登場していた。
どうもこれを使うとPyGILState_EnsureからPyGILState_Releaseまでの間で今まであったややこしいことを忘れてPythonAPIを使えるようになるらしい。
んで、これは以前と変わらずに一時的にロックを手放したいときはPy_BEGIN_ALLOW_THREADS~Py_END_ALLOW_THREADSとする。
ここまでの内容を理解した上でPyGILState_Ensure~PyGILState_ReleaseとPy_BEGIN_ALLOW_THREADS~Py_END_ALLOW_THREADSすれば完璧?

PyGILState_STATE PyGILState_Ensure()

Pythonの状態やスレッドロックに関わらず、実行中スレッドでPython C APIの呼び出しが可能となるようにします。この関数はスレッド内で何度でも呼び出すことができますが、必ず全ての呼び出しに対応して PyGILState_Release()を呼び出す必要があります。
通常、PyGILState_Ensure()呼び出しと PyGILState_Release()呼び出しの間でこれ以外のスレッド関連API を使用することができますが、Release()の前にスレッド状態は復元されていなければなりません。通常のPy_BEGIN_ALLOW_THREADSマクロと Py_END_ALLOW_THREADSも使用することができます。

戻り値はPyGILState_Acquire()呼び出し時のスレッド状態を隠蔽した"ハンドル"で、PyGILState_Release()に渡してPythonを同じ状態に保たなければなりません。再起呼び出しも可能ですが、ハンドルを共有することはできません - それぞれのPyGILState_Ensure呼び出しでハンドルを保存し、対応するPyGILState_Release呼び出しで渡してください。

関数から復帰したとき、実行中のスレッドはGILを所有しています。処理の失敗は致命的なエラーです。

バージョン 2.3 で 新たに追加 された仕様です。

void PyGILState_Release( PyGILState_STATE)

獲得したすべてのリソースを開放します。この関数を呼び出すと、Pythonの状態は対応するPyGILState_Ensureを呼び出す前と同じとなります。(通常、この状態は呼び出し元でははわかりませんので、GILState APIを利用するようにしてください。)
PyGILState_Ensure()を呼び出す場合は、必ず同一スレッド内で対応するPyGILState_Release()を呼び出してください。 バージョン 2.3 で 新たに追加 された仕様です。

以上、完璧に超意訳。
間違ってても責任負えませんっていうか間違ってたら教えてくださいm(__)m

| | コメント (0)

2006年5月29日 (月)

コピーできないPDFからコピーする

http://www.bravaviewer.jp/reader.htm

このビュアーを使うとできる。

(Ref: http://q.hatena.ne.jp/1115015484#a297395 )

| | コメント (0)

2006年5月27日 (土)

pythonスクリプトからmultipart/form-dataを送る。

Webサービスのサイトに写真とかを送信するのを自動化するにはどうするか、的な話。

最初はIEとJavaScriptで適当に送ってみようと思ったんだけどセキュリティがきつくなってて無理でした。というわけでpythonを使うことに。が、pythonもデフォルトではmultipart/form-dataはサポートしてない。ので、結局のところHTTPリクエストを自分でがりがり書くしかない。んでよくよく調べてたらurllib2をかっこよく拡張してる人を発見。

http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306/

このスレッドの一番最後のコメント。

http://odin.himinbi.org/MultipartPostHandler.py

このMultipartPostHandler.pyを以下のように使うとurlとパラメータ渡すだけで通信できる。

cookies = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies),
                            MultipartPostHandler.MultipartPostHandler)
params = { "username" : "bob", "password" : "riviera",
         "file" : open("filename", "rb") }
opener.open("http://wwww.bobsite.com/upload/", params)

ちなみにこのMultipartPostHandlerをハンドラに加えるとopenの第二引数のエンコードを自動的にやってくれる。

| | コメント (0)

2006年5月21日 (日)

はてなブックマークに新しいウインドウで登録

はてなブックマークに新しいウインドウで登録

ブックマークレットです。タイトルのまんまです。
登録したいときはたいていそのページを読み終わってないことが多くて、デフォルトのブックマークレットだとはてなに飛んでしまって続きが読めないので別ウインドウにしてみました。

window.openしただけだから1エントリにするのも何だけどPC再インストしたときにまた書くのがめんどくさいからUP

| | コメント (0)

2006年5月20日 (土)

YouTubeからflvを直接落とすブックマークレット

  1. ここのリンクをお気に入りに登録してください。>>DownFromYouTube
  2. 適当なYouTubeの動画ファイルのページを開きます。
  3. その状態で先に登録したお気に入りを開きます。

これでそのページの動画をダウンロードするダイアログが開きます。(ダウンロードページを開くのではなくて直接落とし始めます。)
常にファイル名がget_videoになってしまいますが、ファイルの拡張子は.flvという形式です。

ちなみにgoogle videoからAVIで直ダウンロードするブックマークレットは
Google Operating System: Download Google Videos As AVI Files
にあります。
(Ref: Going My Way: Google VideoからAVI形式でダウンロード可能なBookmarklet )

さらにちなみに、VideoDownloader(直リンじゃないけどやたら多数の動画サイトからダウンロードするページを作れる)のブックマークレットは以下

Youtubeなどのムービーをダウンロードするトイブックマークレット - Ogawa::Memoranda

| | コメント (0)

2006年5月14日 (日)

USB外付けHDDの初期化

c:\windows\system32\diskmgmt.msc
を管理者権限で起動すればドライブ文字が割り振られていないドライブでもフォーマットとかができる。

| | コメント (0)

いえもん

思わず作ってみました。

http://glucose.jp/blog/11

IE系ブラウザ(IE6,Sleipnir,glucose,...)でみてるWebページのリンクにショートカットをつけて、Altキーを押しながらショートカットを入力することでリンクを飛べるようになります。
ほかにはAlt+上で上のディレクトリに移動したり、マウス右クリックしながら右にドラッグで進む、左にドラッグで戻る、といったマウスジェスチャーがIEでも可能になります。

FireFoxにおけるGreaseMonkeyみたいなことをして実装してます。
ほとんど思いつきで作ったんでかゆいところに手が届いてないですが、機会があったらもっと使えるものにしたいなーと。

| | コメント (0)

ターミナルサーバーライセンスの期限が切れたとき。

「このコンピューターで利用できるターミナル サーバー クライアント ライセンスがないため、リモートセッションは切断されました。サーバー管理者に問い合わせてください。」

というのが出たときはHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSLicensingをいじるとなんとかなったりする。
・HardwareID,Parameters,Storeをリネームするなどしてバックアップ。特にHardwareID\ClientHWIDがなくなると何とかして書き戻すまでリモートデスクトップ接続がまったく使えなくなるため注意。
・HardwareID\ClientHWIDに元の値の末尾を適当にちょこっとかえた値を設定すると繋がるようになる。

| | コメント (0)

2006年5月 4日 (木)

大体の日本語文字にヒットする正規表現

[ぁ-ヶ]|[亜-黑]

上を秀丸とかで正規表現で検索すればOK

(Ref: http://homepage2.nifty.com/zaco/rexp/rexp06.html#2)

| | コメント (1)

より以前の記事一覧