where was "ゆ〜すけの日記" in the old days…
RSS icon Email icon Home icon
  • Pythonでスレッドプログラミング

    Posted on November 16th, 2009 Yusuke No comments

    最近、仕事の空き時間にPythonでプログラムしてる。

    もともと僕はプログラミング系は弱く、それよりは対話的なシェルのほうが好む。だから自動処理的なこともシェルスクリプトで完結できるところはそっちを優先的に使う。

    ただ、今回やりたかった事がシェルスクリプトだとあまりに遅い。

    #!/bin/sh
    CMD1=`/path/to/cmd1`
    CMD2=`/path/to/cmd2`
    CMD3=`/path/to/cmd3`
    ${CMD1}
    ${CMD2}
    ${CMD3}

    とかけばcmd1,cmd2,cmd3の実行結果をそれぞれ出してくれることはいいんだけど、CMD1,CMD2,CMD3は排他的な関係なのに、それぞれが10秒かかるとすると合計で30秒かかる。シェルならバックグラウンドに送るという方法があるが、シェルスクリプトではなかなかそれが難しい。たとえできても${CMD1}に期待の結果が入らない。

    ということでスレッドをサポートしている言語を探したところPythonにあたったというわけ。ただPythonは、ちょっと調べれば分かるが、バージョンによって互換性などが失われやすい言語だった。2.4、2.5、2.6だけでも違いがあるが、3.0ではほとんど書き直しが必要と言われる。まぁ3.0はしばらくおいておいて、Solarisで使える標準の2.4に合わせることにした。

    以下がそのスレッドプログラムの概要。snmpwalkで必要な値を取り出して100回表示する(ここではあまり意味はないが、まぁ対象が100あると思ってもらえればいい)。これはAppleのAirport Expressという無線ルータに付加されているグローバルIPアドレスを知るためのプログラム。民生用のルータでSNMPで情報取得ができるのはなかなかないね。ホントはTrapが出せれば一番いいんだけど。

    #!/usr/bin/python
    
    import subprocess
    import threading
    
    snmpwalk = '/usr/local/bin/snmpwalk'
    
    def snmpget(host,community,oid):
            cmdline = [
                    snmpwalk,
                    '-On',
                    '-v2c',
                    '-c',
                    community,
                    host,
                    oid,
            ]
    
            p = subprocess.Popen(cmdline,stdout=subprocess.PIPE)
            return p.communicate()[0]
    
    class Worker(threading.Thread):
            def run(self):
                    community = 'secret'
                    host = 'Airport-Express.local'
                    oid = '.1.3.6.1.2.1.4.20.1.2'
    
                    r =snmpget(host,community,oid)
                    r = r.split('\n')
                    for l in r[:-1]:
                            s = ' = INTEGER: 6'
                            r = l.find(s)
                            if r>0:
                                    print l[len(oid)+1:r]
    
    def main():
            for i in range(100):
                    Worker().start()
    
    main()

    上記のプログラムはインターネット経由で4秒弱で完了。これをzshでバッチ的にやろうとするならば以下のような感じ。インターネット経由で36秒ほどかかる。


    % for i in {0..99} ; snmpwalk -On -v2c -c secret Airport-Express.local .1.3.6.1.2.1.4.20.1.2 | egrep 'INTEGER: 6' | sed -e 's/.1.3.6.1.2.1.4.20.1.2.//' -e 's/ = INTEGER: 6//'

    さて、問題は。。。

    Exception in thread Thread-29:
    Traceback (most recent call last):
      File "/usr/local/lib/python2.4/threading.py", line 442, in __bootstrap
        self.run()
      File "a.py", line 28, in run
        r =snmpget(host,community,oid)
      File "a.py", line 20, in snmpget
        return p.communicate()[0]
      File "/usr/local/lib/python2.4/subprocess.py", line 1083, in communicate
        self.wait()
      File "/usr/local/lib/python2.4/subprocess.py", line 1007, in wait
        pid, sts = os.waitpid(self.pid, 0)
    OSError: [Errno 10] No child processes

    2.4でさっきのプログラムを動かすと、確率的に上記のような例外が起こってしまう。スレッドの使い方がおかしいのかと色々探っていたのだが、結局原因は分からなかった。同じソースコードを以下の環境で動かしても失敗するのは2.4だけ。subprocessがバグってるのかなんなのか。

    • 2.4(Solaris) Fail
    • 2.5(FreeBSD) OK
    • 2.5(Mac OS X) OK
    • 2.6(Mac OS X) OK

    Leave a reply