Pythonスレッドプールの原理と実現とsubprocessモジュール

10055 ワード

最近、プロジェクトにはlinux shellとインタラクティブなマルチスレッドプログラムが必要で、pythonで実現する必要があります.これまでpythonに接触したことがありません.今回は急いでpythonを使用しましたが、pythonは確かに文法が簡単で、機能が非常に強いことがわかりました.自分がゼロからpythonを使用しているので、文法も現在学んでいるので、いくつかの使用を記録します.皆さんの役に立つことを願っています.
pythonの要件は簡単に言えばliunuxのffmpegを呼び出してオーディオのいくつかの情報を取得し、マルチスレッドで実現する必要がある.
一、subprocess
マルチスレッドであるため、pythonが提供するオープンサブスレッドの標準ライブラリであるsubprocessモジュール(公式ドキュメントではosモジュールとPopen 2モジュールとcommandモジュールの代わりにこのモジュールが使用されることを示しています)が最初に考えられます.サブスレッドのstdin、stdout、stderrはpipeによってプライマリスレッドとインタラクティブになります.
subprocess.call(["ls", "-l"])



subprocess.check_call(["ls", "-l"])

 
これは2つの非常に簡単な例で、メインスレッドはサブスレッドコマンドの完了を待ってから戻り値を取得します.2つの方法の唯一の違いはcheck_です.callは戻り値をチェックし、戻り値が0でない場合(正しく実行されている場合)、CalledProcessError異常が放出されます.
 
Popen
では、私はPopenメソッドを使っていますが、実際にsubprocessモジュールの他のメソッドはPopenのパッケージで、より便利に使用するために、私たち自身がいくつかの機能をカスタマイズする必要がある場合は、最後にPopenに戻ります.
Popenの具体的なパラメータはPython documentを参照してください
Popenはタプルをパラメータとして受け入れる
child = subprocess.Popen(["ping","-c","5","www.google.com"])

 
上記の方法とは異なり、Popenを使用すると、メインスレッドはサブスレッドの完了を待たず、待つ場合はwait()メソッドを使用する必要があります.
私が使用しているコードを先に入力します.
command = ["ffmpeg","-i",songPath];



stdoutData,stderrData = subprocess.Popen(command,stdout=subprocess.PIPE,stderr=subprocess.PIPE).communicate();

 
    前述したように、Popenを使用して、標準入力、標準出力、標準エラー出力をカスタマイズできます.
では、この行コードでは、stdoutDataとstderrDataがそれぞれコマンドラインプログラムの標準出力と標準エラー出力(すなわちstdout=subprocess.PIPE、stderr=subprocess.PIPE)を受け入れることを定義し、いずれもパイプ(Pipe)によって実現される.次のプライマリ・スレッドでPopenメソッドを使用してコマンド・ライン・プログラムを実行した後の出力データ(print(stdoutData)など)を使用する場合は、プライマリ・スレッドがPopenの実行完了を待たないため、Popen.wait()を使用することを覚えておいてください.どうして私のところは役に立たないのですか.ここではcommunicateメソッドを使用しているので、communicateメソッドはサブスレッドとメインスレッドとの間の通信を表し、ブロック式であり、communicateメソッドを使用すると、メインスレッドはサブスレッドの完了を待つ.
    実は別のレベルから言えば、linux標準入出力のパイプリダイレクトでもあり、標準入出力をプログラムにリダイレクトするだけです.
    この方法でffmpegでオーディオの出力を取得しました.
 
では、問題が来ました.約束したマルチスレッドは?communicateメソッドはブロック式なので、マルチスレッドを開くことはできません.そこで考えた後,メインスレッドで複数のサブスレッドを開き,subprocessモジュールをそれぞれ呼び出してオーディオの情報を取得することにした.マルチスレッドを用いた以上,効率の問題を考慮して,スレッドプールを自然に連想した.
 
二、スレッドプール
    スレッドプールが必要なのはなぜですか?
        タスクを使用してサブスレッド処理を開始し、処理が完了した後、サブスレッドを破棄したり、サブスレッドが自然に死亡したりすると、タスクにかかる時間が短くなりますが、タスクの数が多くなると、オンラインスレッドの作成と終了に多くの時間がかかり、効率が低下するに違いありません.
 
    スレッドプールの原理:
        スレッドプール(Thread pool)である以上、名前はイメージ的で、指定された数の利用可能なサブスレッドを1つの「プール」に入れ、タスクがあるときに1つのスレッドを取り出して実行し、タスクが実行された後、すぐにスレッドを破棄するのではなく、スレッドプールに入れ、次のタスクの受信を待つ.これにより、メモリとcpuのオーバーヘッドも小さく、スレッドの数を制御できます.
 
    スレッドプールの実装:
        スレッドプールには多くの実装方法があり、pythonではQueue-キューという優れた実装方法が提供されています.pythonではQueue自体が同期しているので、つまりスレッドが安全なので、複数のスレッドに1つのQueueを共有させることが安心できます.
        スレッドプールといえば、実行すべきタスクプールがあるはずです.タスクプールには実行すべきタスクが格納されており、各スレッドがタスクプールにタスク実行を取り、Queueでタスクプールを実現するのが最善です.
 
コードを先に入力:
class TaskManager():



    def __init__(self,maxTasks,maxThreads):

        #     ,   Queue   

        self._maxTasks = maxTasks;

        #            

        self._maxThreads = maxThreads;

        #    

        ….

        ….



        #   

        self._taskQueue = Queue.Queue(maxTasks);

        #

        self._threads = [];



        # __init__     

        self.initThreads();

        self.initTaskQueue();



    #      

    def initTaskQueue(self):

        while True:

        #    

            if not self._taskQueue.full():

                getTasks(self._maxTasks - self._taskQueue.qsize());

                for task in taskMap["tasks"]:

                self._taskQueue.put(task);

                time.sleep(1);



    #      

    def initThreads(self):

        for i in range(self._maxThreads):

        #             

        self._threads.append(Work(self,self._reportUrl));



    def getTask(self):

        return self._taskQueue.get();



#       

class Work(threading.Thread):

    def __init__(self,taskmgr):

    threading.Thread.__init__(self);

    self._logger = logging.getLogger("");

    self.start();



    def run(self):

        while True:

            try:

                #           

                self._taskmgr.getTask();

                ……

                ……



                time.sleep(1);

            except Exception,e:

                self._logger.exception(e);            

 
 
スレッドプールの実装は主に2つの部分に分けられ、一部はTaskMagager、すなわちタスク管理クラスであり、タスクをスケジューリングするために使用され、一部はWork、すなわち具体的に実行する必要があるビジネスコードである.スレッドプールのこのような設計モデルは多くの場所で参考になる.
 
TaskManager
    まずTaskManagerを見てみましょう.主に4つの方法、1つの構造方法、伝達されたパラメータを受け入れ、タスクプールとスレッドプールのサイズなどの初期化情報を実行し、initTaskQueueとinitThreadメソッドを呼び出してタスクプールとスレッドプールを初期化します.
    最後のメソッドgetTaskは、TaskManagerクラスのインスタンスを返します.
 
Work
    具体的な業務を遂行する
 
プロセス分析
  • TaskManagerの_init__メソッドスレッドプールとタスクプール
  • を初期化
  • initTaskQueueメソッドは、タスクプールを初期化し、タスクキューにタスクを埋め込む.
  • initThreadsメソッドは、スレッドプールを初期化し、Workクラスを呼び出してタスクを実行します.
  • getTaskメソッドは、TaskManagerインスタンスを返します.主な役割は、ワーククラスに渡され、サブスレッドがタスクキューからタスク実行を取り出すことです.
  • Workクラスの_init__メソッドスレッドを初期化し、スレッドを開始します.
  • Runメソッドは、タスクを実行し、タスクキューからタスクを取り出します.

  •  
    キー:
  • メインスレッド、すなわちTaskManagerのinitTaskQueueメソッドでタスクを取得し、タスクプール
  • に埋め込む

  • 各サブスレッド、すなわちワーククラスのrunメソッドでタスクプール内のタスクを取得して実行します.
    ここで注意しなければならないのは、前述したように、PythonのQueueはスレッドが安全であり、Queueのgetメソッドはブロック式である.すなわち、Queueが空であれば、サブスレッドがタスクを取れず、Queueにタスクが取れるまで待機する.

  •     三、TaskManagerでの_init__メソッドでは、タスクプールを起動する前にスレッドを起動したほうがいいです.
    self.initThreads();
    
    
    
    self.initTaskQueue();

         
    それ以外の場合、initTaskQueue(プライマリスレッド)のwhileループは常に実行され、スレッドプールの実行がブロックされます.2つ目で説明したように、スレッドプールを先に起動すると、タスクプールにタスクがなくても、サブスレッドはタスクプールに新しいタスクが現れるのを待つのをブロックします.
     
    ここまで書いて、もし間違いがあったら、皆さんに指摘してください:)