pythonのポーリングオブジェクト

 pythonではじめて作ったプログラムから抜粋による実例紹介を基にした備忘録もいよいよ最終回とします。実は他にpymysqlモジュール関連のネタもあるのですが、これは気が向いたら書きます。最終回の今回は、おそらくpyhonとしては初期段階から存在するselectモジュールを使ったポーリングオブジェクトの実装例です。

 DEN将棋EXでは、shogi-serverとソケット通信するプログラムを、ウェブソケットサーバーdaemonの子プロセスとして製作しましたが、この子プロセス側でウェブソケットサーバー(親プロセス)と通信するための手段として、perl伝統的な4引数select関数を使いました。

 そこでpythonで同じようなものが存在しないか調べたところやはり有ったわけです。今回作った子プロセス用プログラムは、DBのテーブルを監視して条件がそろったら親プロセスにデータを通知することが主な仕事ですので、DEN将棋EXよりずいぶん単純な仕様です。その単純なプログラムにとって手軽で軽量な最適な手段だと思います。ただ注意点として、このselectモジュールはWindows上ではソケットに対してしか動作しないそうです。

ポーリングオブジェクト

(1)selectモジュール

 selectモジュール標準ライブラリに同梱されているモジュールなので、あらためてインストールする必要はありません。まずselect.pollメソッドで、ポーリングオブジェクトを生成します。そしてregisterメソッドでファイルディスクリプタをポーリングオブジェクトに登録します。

 ここでは、親プロセスにパイプでオープンされるので、標準入力が親プロセスのファイルディスクリプタになります。ゆえにこのファイルディスクリプタparent_fd をポーリングオブジェクトに登録しています。

 これで親プロセスからの受信データをポーリングする準備が整いました。

import sys,select
# ポーリングオブジェクト生成
poll = select.poll()
parent_fd = sys.stdin #親プロセスにパイプでオープンされる前提
poll.register(parent_fd, select.POLLIN)

(2)pollメソッド

  実際の無限ループの中で、ポーリングオブジェクトのpollメソッドを呼びます。このメソッドはperlの4引数select関数と同様にタイムアウト時間(ミリ秒単位)を引数で設定する事ができます。タイムアウト時間を超えて受信データを発見できない場合は、次のステップに移ります。ここではタイムアウト値を50msとしています。

 pollメソッドの返り値は、ファイルディスクリプタの対応するファイル番号と、結果(イベントマスク値)データです。これらは2 要素のタプル (fd, event) からなるリストとして返されます。返り値は空の場合も有ります。詳細はココを参照。

 無限ループの中では、pollメソッドの返り値を判定することが仕事になります。下記が今回実装したコードです。POLLIN は親プロセスからの読み出し待ちデータがある事を示します。実際の読み出しは、標準入力データを文字列として受け取るinput()関数を使用しています。

 親プロセスとの接続が切れたかエラー(POLLIN以外のイベント発生を全てエラーとする)を捉えるとfinをtrueにして、後処理の部分で無限ループを脱出するようにしています。

 ここでは記述していませんが、無限ループの「親プロセスからの受信データ別処理」の中では、適宜親プロセスにデータを送信します。双方向パイプでつながっているので、使っているのはprint()関数です。せいぜい数10バイトのテキストデータしか送信しないので、ブロッキングな関数で十分です。

fin = false
while True:
    # イベント発生まで待つ
    rdy = poll.poll(50)
    for fno, ev in rdy:
        if fno == parent_fd.fileno():
            if ev == select.POLLIN:
                data = input()
                # 親プロセスからの受信データ別処理
                # ・・・・・・・・
            else:
                # 接続が切れたか、エラーの時
                fin = True
    # 受信データに無関係の処理:
    #   DBの監視テーブルの読み出しと読み出したデータ別処理
    # ・・・・・・・・
    if fin:
        # whileループから抜ける
        break