以下の要件を考慮してください。
gzip圧縮されたログファイルでいっぱいのログディレクトリがあります。
各ログファイルの形式は固定されています。robots.txtファイルにアクセスしたすべてのホストを抽出します
1.1.1.1 ------------ [10 / june / 2012:00:18:50-0500] "GET /robots.txt ..." 200 71
2.1.1.3 ------------ [12 / june / 2013:00:18:50-0500] "GET /a.txt ..." 202 73
122.1.1.3 ------------ [12 / june / 2013:00:18:50-0500] "GET /robots.txt ..." 202 73
並行性を使用せずに、次のプログラムコードを記述します。
import gzip
import glob
import io
def find_robots(filename):
robots = set()
with gzip.open(filename) as f:
for line in io.TextIOWrapper(f, encoding='ascii'):
fields = line.split()
if fields[6] == '/robots.txt':
robots.add(fields[0])
return robots
def find_all_robots(logdir):
files = glob.glob(logdir+'/*.log.gz')
all_robots = set()
for robots in map(find_robots, files):
all_robots.update(robots)
return all_robots
上記のプログラムはmap-reduceのスタイルで書かれています。
複数のCPUコアを使用するように上記のプログラムを書き換えたい場合。マップを同様の操作で置き換え、concurrent.futuresライブラリのプロセスプールで実行するだけです。
少し修正したコードを次に示します。
def find_all_robots(logdir):
files = glob.glob(logdir+'/*.log.gz')
all_robots = set()
with ProcessPoolExecutor as pool:
for robots in pool.map(find_robots, files):
all_robots.update(robots)
return all_robots
ProcessPoolExecutorの一般的な使用方法は次のとおりです。
from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor() as pool:
do work in parallel using pool
下部では、ProcessPoolExcutorはN個の独立したプロセスを使用してPythonインタープリターを起動します。NはCPUの数です。または、パラメーターNを介してProcessPoolExecutor(N)を渡すことができます。withブロックの最後のステートメントが実行されるまで、ProcessPoolExecutorは終了し、終了するまで待機しますすべてのタスクが完了しました。
プロセスプールに送信されるタスクは、関数の形式である必要があります。タスクを送信するには、2つの方法があります。リスト内包またはマップ操作を並行して処理する場合は、pool.mapを使用するか、submitを使用して手動でタスクを送信できます。
def work(x):
result = '''
'''
return result
from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor() as pool:
future_result = pool.submit(work)
r = future_result.result()
タスクを手動で送信すると、将来のオブジェクトが返されます。resultメソッドを通じて結果を取得できますが、resultメソッドは、結果が返されるまでブロックします。
ブロックされないようにするには、処理完了機能をインストールすることもできます。
def when_done(r):
print('Got:', r.result())
with ProcessPoolExecutor() as pool:
future_result = pool.submit(work)
future_result.add_done_callback(when_done)
プロセスプールは非常に簡単に使用できますが、次の点に注意する必要があります。
1.この並列処理テクノロジーは、問題を個別のパーツに分解できる状況にのみ適しています
2.タスクは、送信する通常の関数としてのみ定義できます。インスタンスメソッド、クロージャ、またはその他のタイプの呼び出し可能なオブジェクトは、並列処理をサポートしていません。
3.関数のパラメーターと戻り値は、pickleエンコーディングと互換性がある必要があります。タスクの実行は、プロセス間通信を必要とする別のインタープリタープロセスで実行されます。したがって、異なるインタープリター間のデータ交換はシリアル化する必要があります
4.提出された仕事関数は、永続的な状態を維持したり、副作用を引き起こしたりしてはなりません。
5. UNIX環境では、プロセスプールはforkシステムコールを介して実装されます。
6.プロセスプールとスレッドプールを組み合わせるときは特に注意してください。通常、スレッドプールを作成する前にプロセスプールを開始する必要があります。
GILの制限を回避する方法。
PythonインタープリターのC言語実装では、コードの一部がスレッドセーフではないため、完全に同時に実行することはできません。実際、インタープリターはグローバルインタープリターロック(GIL)と呼ばれるものによって保護されており、常に実行できるPythonスレッドは1つだけです。GILの最も明白な影響は、マルチスレッドのPythonプログラムがマルチコアCPUを十分に活用できないことです(つまり、マルチスレッドテクノロジーを使用する計算集約型のアプリケーションは、1つのCPUでしか実行できません)。
GILを理解するには、PythonがGILをリリースする時期を知る必要があります。
インタープリターは、I / O操作を待機してブロックされるたびにGILを解放します。ブロック操作を実行しないCPU集中型のスレッドの場合、Pythonインタープリターは、特定の数のバイトコードを実行した後にGILを解放し、他のスレッドが実行できるようにします。ただし、C言語拡張モジュールは異なり、C関数が呼び出されると、GILが戻るまでロックされます。
Cコードはインタープリターによって制御されないため、この期間はPythonバイトコードが実行されないため、インタープリターはGILを解放できません。
TALK SO MUCH。GILの制限を回避するには、通常、次の2つの方法があります。
1. Pythonで完全にプログラミングしている場合は、マルチプロセッシングモジュールを使用してプロセスプールを作成し、それをコプロセッサーとして使用します。
2. C言語の拡張に焦点を当てる:主なアイデアは、計算中心のタスクをCコードに転送し、Pythonから独立させ、CコードでGILをリリースすることです。これは、Cコードに特別なマクロを挿入することで実現されます。
#include "Python.h"
PyObject *pyfunc(PyObject *self, PyObject *args)
{
...
Py_BEGIN_ALLOW_THREADS
// Threaded C code
...
Py_END_ALLOW_THREADS
...
}
cyptesライブラリまたはCythonを使用してCコードにアクセスする場合、ctypesは介入なしでGILを自動的に解放します。