Pythonでの並行性-クイックガイド
この章では、Pythonでの並行性の概念を理解し、さまざまなスレッドとプロセスについて学習します。
並行性とは何ですか?
簡単に言うと、並行性とは、2つ以上のイベントが同時に発生することです。多くのイベントが常に同時に発生するため、並行性は自然現象です。
プログラミングの観点から、並行性とは、2つのタスクの実行が重複する場合です。並行プログラミングを使用すると、前の要求が完了するのを待つのではなく、要求を同時に処理できるため、アプリケーションとソフトウェアシステムのパフォーマンスを向上させることができます。
並行性の歴史的レビュー
以下のポイントは、並行性の簡単な歴史的レビューを提供します-
鉄道の概念から
並行性は、鉄道の概念と密接に関連しています。鉄道では、すべての列車が目的地に安全に到着できるように、同じ鉄道システムで複数の列車を処理する必要がありました。
アカデミアにおける並行コンピューティング
コンピュータサイエンスの並行性への関心は、1965年にEdsger W. Dijkstraによって発行された研究論文から始まりました。この論文では、並行性制御の特性である相互排除の問題を特定して解決しました。
高レベルの並行性プリミティブ
最近、プログラマーは、高レベルの並行性プリミティブの導入により、改善された並行性ソリューションを取得しています。
プログラミング言語との並行性の向上
GoogleのGolang、Rust、Pythonなどのプログラミング言語は、より優れた並行ソリューションの取得に役立つ分野で驚異的な発展を遂げました。
スレッド&マルチスレッドとは何ですか?
Threadは、オペレーティングシステムで実行できる実行の最小単位です。それ自体はプログラムではありませんが、プログラム内で実行されます。言い換えれば、スレッドは互いに独立していません。各スレッドは、コードセクションやデータセクションなどを他のスレッドと共有します。それらは軽量プロセスとしても知られています。
スレッドは次のコンポーネントで構成されています-
次の実行可能命令のアドレスで構成されるプログラムカウンタ
Stack
レジスタのセット
一意のID
Multithreading一方、CPUは、複数のスレッドを同時に実行することにより、オペレーティングシステムの使用を管理できます。マルチスレッドの主なアイデアは、プロセスを複数のスレッドに分割することによって並列処理を実現することです。マルチスレッドの概念は、次の例を使用して理解できます。
例
MSWordを開いてコンテンツを入力する特定のプロセスを実行しているとします。1つのスレッドがMSWordを開くために割り当てられ、別のスレッドがコンテンツを入力するために必要になります。そして今、既存のものを編集したい場合は、編集タスクなどを行うために別のスレッドが必要になります。
プロセス&マルチプロセッシングとは何ですか?
Aprocessはエンティティとして定義され、システムに実装される基本的な作業単位を表します。簡単に言うと、コンピュータプログラムをテキストファイルで記述し、このプログラムを実行すると、プログラムに記載されているすべてのタスクを実行するプロセスになります。プロセスのライフサイクル中に、開始、準備、実行、待機、終了などのさまざまな段階を通過します。
次の図は、プロセスのさまざまな段階を示しています-
プロセスは、プライマリスレッドと呼ばれる1つのスレッドのみ、または独自のレジスタ、プログラムカウンタ、およびスタックのセットを持つ複数のスレッドを持つことができます。次の図は違いを示しています-
Multiprocessing,一方、単一のコンピュータシステム内で2つ以上のCPUユニットを使用することです。私たちの主な目標は、ハードウェアから最大限の可能性を引き出すことです。これを実現するには、コンピューターシステムで利用可能なCPUコアの全数を利用する必要があります。マルチプロセッシングは、そうするための最良のアプローチです。
Pythonは、最も人気のあるプログラミング言語の1つです。以下は、並行アプリケーションに適した理由です。
シンタックスシュガー
シンタックスシュガーは、プログラミング言語内の構文であり、読みやすく、表現しやすくするように設計されています。それは言語を人間が使用するために「より甘く」します:物事はより明確に、より簡潔に、または好みに基づいた代替スタイルで表現することができます。Pythonには、オブジェクトに作用するように定義できるMagicメソッドが付属しています。これらのMagicメソッドは、構文糖衣構文として使用され、より理解しやすいキーワードにバインドされます。
大規模なコミュニティ
Python言語は、AI、機械学習、ディープラーニング、定量分析の分野で活躍するデータサイエンティストや数学者の間で、非常に多くの採用率を誇っています。
並行プログラミングに役立つAPI
Python 2および3には、並列/並行プログラミング専用の多数のAPIがあります。それらの中で最も人気があるのはthreading, concurrent.features, multiprocessing, asyncio, gevent and greenlets, 等
並行アプリケーションの実装におけるPythonの制限
Pythonには、並行アプリケーションの制限があります。この制限はGIL (Global Interpreter Lock)Python内に存在します。GILではCPUの複数のコアを利用することはできません。したがって、Pythonには真のスレッドはないと言えます。GILの概念は次のように理解できます-
GIL(グローバルインタープリターロック)
これは、Pythonの世界で最も物議を醸しているトピックの1つです。CPythonでは、GILはミューテックスです。相互排他ロックにより、スレッドセーフになります。言い換えれば、GILは複数のスレッドがPythonコードを並行して実行することを防いでいると言えます。ロックは一度に1つのスレッドのみが保持でき、スレッドを実行する場合は、最初にロックを取得する必要があります。以下に示す図は、GILの動作を理解するのに役立ちます。
ただし、Pythonには次のようないくつかのライブラリと実装があります。 Numpy, Jpython そして IronPytbhon. これらのライブラリは、GILとの対話なしで機能します。
マルチスレッドプログラムに関連して並行性と並列性の両方が使用されますが、それらの類似点と相違点については多くの混乱があります。この点での大きな問題は、並行性の並列性かどうかです。両方の用語は非常に似ているように見えますが、上記の質問に対する答えはNOですが、並行性と並列性は同じではありません。さて、それらが同じでない場合、それらの間の基本的な違いは何ですか?
簡単に言うと、並行性は異なるスレッドからの共有状態へのアクセスの管理を扱い、反対側では、並列性は複数のCPUまたはそのコアを利用してハードウェアのパフォーマンスを向上させることを扱います。
並行性の詳細
並行性とは、2つのタスクの実行が重複する場合です。アプリケーションが同時に複数のタスクで進行している状況である可能性があります。それを図式的に理解することができます。次のように、複数のタスクが同時に進行しています。
並行性のレベル
このセクションでは、プログラミングの観点から並行性の3つの重要なレベルについて説明します。
低レベルの並行性
このレベルの並行性では、アトミック操作が明示的に使用されます。このような同時実行性は、エラーが発生しやすく、デバッグが難しいため、アプリケーションの構築に使用することはできません。Pythonでさえ、そのような種類の並行性をサポートしていません。
中間レベルの並行性
この並行性では、明示的なアトミック操作は使用されません。明示的なロックを使用します。Pythonやその他のプログラミング言語は、このような種類の並行性をサポートしています。ほとんどの場合、アプリケーションプログラマーはこの同時実行性を使用します。
高レベルの並行性
この同時実行では、明示的なアトミック操作も明示的なロックも使用されません。Pythonにはconcurrent.futures そのような種類の並行性をサポートするモジュール。
コンカレントシステムのプロパティ
プログラムまたは並行システムが正しくなるためには、いくつかのプロパティがそれによって満たされる必要があります。システムの終了に関連するプロパティは次のとおりです。
正しさのプロパティ
正しさのプロパティは、プログラムまたはシステムが目的の正解を提供する必要があることを意味します。簡単にするために、システムはプログラムの開始状態を最終状態に正しくマップする必要があると言えます。
安全性
安全性とは、プログラムまたはシステムが “good” または “safe” 状態し、何もしません “bad”。
活気プロパティ
このプロパティは、プログラムまたはシステムが “make progress” そしてそれはある望ましい状態に達するでしょう。
コンカレントシステムのアクター
これは並行システムの一般的な特性の1つであり、複数のプロセスとスレッドが同時に実行されて、独自のタスクを進行させることができます。これらのプロセスとスレッドは、並行システムのアクターと呼ばれます。
コンカレントシステムのリソース
アクターは、タスクを実行するために、メモリ、ディスク、プリンターなどのリソースを利用する必要があります。
特定のルールセット
すべての並行システムは、アクターによって実行されるタスクの種類とそれぞれのタイミングを定義するための一連のルールを所有している必要があります。タスクには、ロックの取得、メモリ共有、状態の変更などがあります。
コンカレントシステムの障壁
データの共有
並行システムを実装する際の重要な問題は、複数のスレッドまたはプロセス間でのデータの共有です。実際、プログラマーは、ロックが共有データを保護し、データへのすべてのアクセスがシリアル化され、一度に1つのスレッドまたはプロセスのみが共有データにアクセスできるようにする必要があります。複数のスレッドまたはプロセスがすべて同じ共有データにアクセスしようとすると、すべてではなく、少なくとも1つがブロックされ、アイドル状態のままになります。つまり、ロックが有効な場合、一度に使用できるプロセスまたはスレッドは1つだけであると言えます。上記の障壁を取り除くためのいくつかの簡単な解決策があります-
データ共有の制限
最も簡単な解決策は、変更可能なデータを共有しないことです。この場合、明示的なロックを使用する必要はなく、相互データによる同時実行の障壁が解決されます。
データ構造支援
多くの場合、並行プロセスは同じデータに同時にアクセスする必要があります。明示的なロックを使用する以外の別の解決策は、同時アクセスをサポートするデータ構造を使用することです。たとえば、queueスレッドセーフなキューを提供するモジュール。使用することもできますmultiprocessing.JoinableQueue マルチプロセッシングベースの並行性のためのクラス。
不変のデータ転送
場合によっては、使用しているデータ構造、たとえば同時実行キューが適切でない場合、不変のデータをロックせずに渡すことができます。
可変データ転送
上記の解決策の続きとして、不変データではなく可変データのみを渡す必要がある場合、読み取り専用の可変データを渡すことができると仮定します。
I / Oリソースの共有
並行システムを実装する際のもう1つの重要な問題は、スレッドまたはプロセスによるI / Oリソースの使用です。この問題は、一方のスレッドまたはプロセスがI / Oを長時間使用していて、もう一方がアイドル状態になっている場合に発生します。I / Oの重いアプリケーションで作業しているときに、このような障壁が見られます。これは、例、Webブラウザからのページの要求の助けを借りて理解することができます。重いアプリケーションです。ここで、データが要求される速度がデータが消費される速度よりも遅い場合、並行システムにI / Oバリアがあります。
次のPythonスクリプトは、Webページを要求し、ネットワークが要求されたページを取得するのにかかった時間を取得するためのものです。
import urllib.request
import time
ts = time.time()
req = urllib.request.urlopen('http://www.tutorialspoint.com')
pageHtml = req.read()
te = time.time()
print("Page Fetching Time : {} Seconds".format (te-ts))
上記のスクリプトを実行した後、以下に示すようにページフェッチ時間を取得できます。
出力
Page Fetching Time: 1.0991398811340332 Seconds
ページをフェッチするのに1秒以上かかることがわかります。何千もの異なるWebページをフェッチしたい場合、ネットワークにかかる時間を理解できます。
並列処理とは何ですか?
並列処理は、タスクを同時に処理できるサブタスクに分割する技術として定義できます。これは、前述のように、2つ以上のイベントが同時に発生する並行性とは反対です。それを図式的に理解することができます。タスクは、次のように、並行して処理できるいくつかのサブタスクに分割されます。
並行性と並列性の違いについてさらに理解するには、次の点を考慮してください。
並行しているが並行していない
アプリケーションは並行することはできますが、並行することはできません。つまり、アプリケーションは同時に複数のタスクを処理しますが、タスクはサブタスクに分割されません。
並行しているが並行していない
アプリケーションは並列にすることができますが、並行することはできません。つまり、一度に1つのタスクでのみ機能し、サブタスクに分割されたタスクを並列で処理できます。
並列でも同時でもない
アプリケーションは、並列または同時にすることはできません。これは、一度に1つのタスクでのみ機能し、タスクがサブタスクに分割されることはないことを意味します。
並列および同時の両方
アプリケーションは並列と同時の両方にすることができます。つまり、アプリケーションは一度に複数のタスクで動作し、タスクはサブタスクに分割されて並列に実行されます。
並列処理の必要性
サブタスクを単一のCPUの異なるコア間、またはネットワーク内で接続された複数のコンピューター間で分散することにより、並列処理を実現できます。
並列処理を実現する必要がある理由を理解するには、次の重要な点を考慮してください。
効率的なコード実行
並列処理の助けを借りて、コードを効率的に実行できます。パーツ内の同じコードが並行して実行されるため、時間を節約できます。
シーケンシャルコンピューティングよりも高速
シーケンシャルコンピューティングは、物理的および実用的な要因によって制約されます。そのため、より高速なコンピューティング結果を得ることができません。一方、この問題は並列計算によって解決され、順次計算よりも高速な計算結果が得られます。
実行時間の短縮
並列処理により、プログラムコードの実行時間が短縮されます。
並列処理の実際の例について言えば、コンピューターのグラフィックカードは、独立して動作し、同時に実行できる数百の個別の処理コアを備えているため、並列処理の真の力を強調する例です。このため、ハイエンドのアプリケーションやゲームも実行できます。
実装のためのプロセッサの理解
並行性、並列性、およびそれらの違いについては知っていますが、それが実装されるシステムについてはどうでしょうか。ソフトウェアを設計する際に十分な情報に基づいた決定を下すことができるという利点があるため、実装するシステムを理解しておく必要があります。次の2種類のプロセッサがあります-
シングルコアプロセッサ
シングルコアプロセッサは、いつでも1つのスレッドを実行できます。これらのプロセッサはcontext switching特定の時間にスレッドに必要なすべての情報を保存し、後でその情報を復元します。コンテキスト切り替えメカニズムは、特定の1秒以内に多数のスレッドを進行させるのに役立ち、システムが複数のことに取り組んでいるように見えます。
シングルコアプロセッサには多くの利点があります。これらのプロセッサは必要な電力が少なく、複数のコア間に複雑な通信プロトコルはありません。一方、シングルコアプロセッサの速度は制限されており、大規模なアプリケーションには適していません。
マルチコアプロセッサ
マルチコアプロセッサには、複数の独立した処理ユニットがあります。 cores。
このようなプロセッサは、各コアに一連の格納された命令を実行するために必要なすべてのものが含まれているため、コンテキスト切り替えメカニズムを必要としません。
フェッチ-デコード-実行サイクル
マルチコアプロセッサのコアは、実行のサイクルに従います。このサイクルは、Fetch-Decode-Executeサイクル。次の手順が含まれます-
フェッチ
これはサイクルの最初のステップであり、プログラムメモリからの命令のフェッチが含まれます。
デコード
最近フェッチされた命令は、CPUの他の部分をトリガーする一連の信号に変換されます。
実行する
これは、フェッチされた命令とデコードされた命令が実行される最後のステップです。実行結果はCPUレジスタに格納されます。
ここでの利点の1つは、マルチコアプロセッサでの実行がシングルコアプロセッサよりも高速であることです。大規模なアプリケーションに適しています。一方、複数のコア間の複雑な通信プロトコルが問題になります。マルチコアは、シングルコアプロセッサよりも多くの電力を必要とします。
プログラムまたは並行システムを設計する際に考慮する必要のある、さまざまなシステムおよびメモリアーキテクチャスタイルがあります。1つのシステムとメモリスタイルが1つのタスクに適している場合もありますが、他のタスクでエラーが発生しやすい場合があるため、これは非常に必要です。
並行性をサポートするコンピュータシステムアーキテクチャ
Michael Flynnは、1972年に、さまざまなスタイルのコンピュータシステムアーキテクチャを分類するための分類法を示しました。この分類法では、4つの異なるスタイルを次のように定義しています。
- 単一命令ストリーム、単一データストリーム(SISD)
- 単一の命令ストリーム、複数のデータストリーム(SIMD)
- 複数の命令ストリーム、単一のデータストリーム(MISD)
- 複数の命令ストリーム、複数のデータストリーム(MIMD)。
単一命令ストリーム、単一データストリーム(SISD)
名前が示すように、このような種類のシステムには、データストリームを実行するための1つの順次着信データストリームと1つの単一の処理ユニットがあります。それらは、並列コンピューティングアーキテクチャを備えたユニプロセッサシステムのようなものです。以下はSISDのアーキテクチャです-
SISDの利点
SISDアーキテクチャの利点は次のとおりです-
- 必要な電力が少なくて済みます。
- 複数のコア間の複雑な通信プロトコルの問題はありません。
SISDのデメリット
SISDアーキテクチャの欠点は次のとおりです-
- SISDアーキテクチャの速度は、シングルコアプロセッサと同じように制限されます。
- 大規模なアプリケーションには適していません。
単一の命令ストリーム、複数のデータストリーム(SIMD)
名前が示すように、このような種類のシステムには、複数の着信データストリームと、任意の時点で1つの命令に作用できる多数の処理ユニットがあります。それらは、並列コンピューティングアーキテクチャを備えたマルチプロセッサシステムのようなものです。以下はSIMDのアーキテクチャです-
SIMDの最良の例は、グラフィックカードです。これらのカードには、何百もの個別の処理装置があります。SISDとSIMDの計算上の違いについて話す場合、配列を追加する場合[5, 15, 20] そして [15, 25, 10],SISDアーキテクチャは、3つの異なる追加操作を実行する必要があります。一方、SIMDアーキテクチャでは、1回の追加操作で追加できます。
SIMDの利点
SIMDアーキテクチャの利点は次のとおりです-
複数の要素に対して同じ操作を実行するには、1つの命令のみを使用します。
プロセッサのコア数を増やすことで、システムのスループットを向上させることができます。
処理速度はSISDアーキテクチャよりも高速です。
SIMDのデメリット
SIMDアーキテクチャの欠点は次のとおりです-
- プロセッサのコアの数の間には複雑な通信があります。
- コストはSISDアーキテクチャよりも高くなります。
複数命令単一データ(MISD)ストリーム
MISDストリームを備えたシステムには、同じデータセットに対して異なる命令を実行することによって異なる操作を実行する多数の処理装置があります。以下はMISDのアーキテクチャです-
MISDアーキテクチャの代表はまだ商業的に存在していません。
複数命令複数データ(MIMD)ストリーム
MIMDアーキテクチャを使用するシステムでは、マルチプロセッサシステムの各プロセッサは、異なるデータセットのセットに対して独立して異なる命令のセットを並列に実行できます。これは、単一の操作が複数のデータセットに対して実行されるSIMDアーキテクチャとは反対です。以下はMIMDのアーキテクチャです-
通常のマルチプロセッサはMIMDアーキテクチャを使用します。これらのアーキテクチャは基本的に、コンピュータ支援設計/コンピュータ支援製造、シミュレーション、モデリング、通信スイッチなどの多くのアプリケーション分野で使用されています。
並行性をサポートするメモリアーキテクチャ
並行性や並列性などの概念を使用する場合、プログラムを高速化する必要が常にあります。コンピュータ設計者が見つけた解決策の1つは、共有メモリマルチコンピュータ、つまり、プロセッサが持つすべてのコアからアクセスされる単一の物理アドレス空間を持つコンピュータを作成することです。このシナリオでは、さまざまなスタイルのアーキテクチャが存在する可能性がありますが、次の3つの重要なアーキテクチャスタイルがあります。
UMA(Uniform Memory Access)
このモデルでは、すべてのプロセッサが物理メモリを均一に共有します。すべてのプロセッサは、すべてのメモリワードへのアクセス時間が等しくなります。各プロセッサには、プライベートキャッシュメモリがあります。周辺機器は一連のルールに従います。
すべてのプロセッサがすべての周辺機器に平等にアクセスできる場合、そのシステムは symmetric multiprocessor。1つまたは少数のプロセッサのみが周辺機器にアクセスできる場合、そのシステムはasymmetric multiprocessor。
不均一メモリアクセス(NUMA)
NUMAマルチプロセッサモデルでは、アクセス時間はメモリワードの場所によって異なります。ここで、共有メモリは、ローカルメモリと呼ばれるすべてのプロセッサに物理的に分散されています。すべてのローカルメモリのコレクションは、すべてのプロセッサがアクセスできるグローバルアドレス空間を形成します。
キャッシュオンリーメモリアーキテクチャ(COMA)
COMAモデルは、NUMAモデルの特殊バージョンです。ここでは、すべての分散メインメモリがキャッシュメモリに変換されます。
一般的に、糸は非常に細い撚り糸で、通常は綿や絹の生地で、衣服などの縫製に使用されます。同じ用語のスレッドは、コンピュータープログラミングの世界でも使用されています。では、衣服の縫製に使用される糸とコンピュータープログラミングに使用される糸をどのように関連付けるのでしょうか。2つのスレッドによって実行される役割は、ここでも同様です。衣服では、糸は布を一緒に保持し、反対側では、コンピュータプログラミングでは、スレッドはコンピュータプログラムを保持し、プログラムが連続アクションまたは多数のアクションを一度に実行できるようにします。
Threadオペレーティングシステムでの実行の最小単位です。それ自体はプログラムではありませんが、プログラム内で実行されます。つまり、スレッドは互いに独立しておらず、コードセクションやデータセクションなどを他のスレッドと共有しています。これらのスレッドは、軽量プロセスとも呼ばれます。
スレッドの状態
スレッドの機能を深く理解するには、スレッドのライフサイクルまたはさまざまなスレッドの状態について学ぶ必要があります。通常、スレッドは5つの異なる状態で存在できます。さまざまな状態を以下に示します-
新しいスレッド
新しいスレッドは、新しい状態でライフサイクルを開始します。ただし、この段階ではまだ開始されておらず、リソースも割り当てられていません。それは単なるオブジェクトのインスタンスであると言えます。
実行可能
新しく生まれたスレッドが開始されると、スレッドは実行可能になります。つまり、実行を待機します。この状態では、すべてのリソースがありますが、タスクスケジューラは実行するようにスケジュールしていません。
ランニング
この状態で、スレッドは進行し、タスクスケジューラによって実行するように選択されたタスクを実行します。これで、スレッドはデッド状態または実行不可/待機状態のいずれかになります。
非実行中/待機中
この状態では、スレッドはI / O要求の応答を待機しているか、他のスレッドの実行の完了を待機しているため、一時停止しています。
デッド
実行可能なスレッドは、タスクを完了するか、その他の方法で終了すると、終了状態になります。
次の図は、スレッドの完全なライフサイクルを示しています-
スレッドの種類
このセクションでは、さまざまなタイプのスレッドについて説明します。タイプは以下のとおりです-
ユーザーレベルのスレッド
これらはユーザー管理のスレッドです。
この場合、スレッド管理カーネルはスレッドの存在を認識しません。スレッドライブラリには、スレッドの作成と破棄、スレッド間でのメッセージとデータの受け渡し、スレッド実行のスケジュール設定、スレッドコンテキストの保存と復元のためのコードが含まれています。アプリケーションはシングルスレッドで始まります。
ユーザーレベルのスレッドの例は次のとおりです。
- Javaスレッド
- POSIXスレッド
ユーザーレベルのスレッドの利点
以下は、ユーザーレベルのスレッドのさまざまな利点です。
- スレッドの切り替えには、カーネルモードの権限は必要ありません。
- ユーザーレベルのスレッドは、任意のオペレーティングシステムで実行できます。
- スケジューリングは、ユーザーレベルのスレッドでアプリケーション固有にすることができます。
- ユーザーレベルのスレッドは、作成と管理が高速です。
ユーザーレベルのスレッドのデメリット
以下は、ユーザーレベルのスレッドのさまざまな欠点です。
- 一般的なオペレーティングシステムでは、ほとんどのシステムコールがブロックされています。
- マルチスレッドアプリケーションは、マルチプロセッシングを利用できません。
カーネルレベルのスレッド
オペレーティングシステムが管理するスレッドは、オペレーティングシステムのコアであるカーネルに作用します。
この場合、カーネルはスレッド管理を行います。アプリケーション領域にスレッド管理コードはありません。カーネルスレッドは、オペレーティングシステムによって直接サポートされています。どのアプリケーションもマルチスレッド化するようにプログラムできます。アプリケーション内のすべてのスレッドは、単一のプロセス内でサポートされます。
カーネルは、プロセス全体およびプロセス内の個々のスレッドのコンテキスト情報を維持します。カーネルによるスケジューリングはスレッドベースで行われます。カーネルは、カーネル空間でスレッドの作成、スケジューリング、および管理を実行します。カーネルスレッドは通常、ユーザースレッドよりも作成と管理に時間がかかります。カーネルレベルのスレッドの例は、Windows、Solarisです。
カーネルレベルのスレッドの利点
カーネルレベルのスレッドのさまざまな利点は次のとおりです-
カーネルは、複数のプロセスで同じプロセスから複数のスレッドを同時にスケジュールできます。
プロセス内の1つのスレッドがブロックされている場合、カーネルは同じプロセスの別のスレッドをスケジュールできます。
カーネルルーチン自体はマルチスレッド化できます。
カーネルレベルのスレッドのデメリット
カーネルスレッドは通常、ユーザースレッドよりも作成と管理に時間がかかります。
同じプロセス内で1つのスレッドから別のスレッドに制御を移すには、カーネルへのモード切り替えが必要です。
スレッド制御ブロック-TCB
スレッド制御ブロック(TCB)は、主にスレッドに関する情報を含むオペレーティングシステムのカーネル内のデータ構造として定義できます。TCBに格納されているスレッド固有の情報は、各プロセスに関するいくつかの重要な情報を強調します。
TCBに含まれるスレッドに関連する次の点を考慮してください-
Thread identification −これは、すべての新しいスレッドに割り当てられる一意のスレッドID(tid)です。
Thread state −スレッドの状態(実行中、実行可能、非実行中、デッド)に関連する情報が含まれています。
Program Counter (PC) −スレッドの現在のプログラム命令を指します。
Register set −計算用に割り当てられたスレッドのレジスタ値が含まれています。
Stack Pointer−プロセス内のスレッドのスタックを指します。これには、スレッドのスコープ内のローカル変数が含まれています。
Pointer to PCB −そのスレッドを作成したプロセスへのポインタが含まれています。
プロセスとスレッドの関係
マルチスレッドでは、プロセスとスレッドは非常に密接に関連する2つの用語であり、コンピューターが一度に複数のことを実行できるようにするという同じ目標を持っています。プロセスには1つ以上のスレッドを含めることができますが、逆に、スレッドにプロセスを含めることはできません。ただし、どちらも2つの基本的な実行単位のままです。一連の命令を実行するプログラムは、プロセスとスレッドの両方を開始します。
次の表は、プロセスとスレッドの比較を示しています。
処理する | 糸 |
---|---|
プロセスは重いか、リソースを大量に消費します。 | スレッドは軽量で、プロセスよりも少ないリソースで済みます。 |
プロセスの切り替えには、オペレーティングシステムとの相互作用が必要です。 | スレッドの切り替えは、オペレーティングシステムと対話する必要はありません。 |
複数の処理環境では、各プロセスは同じコードを実行しますが、独自のメモリとファイルリソースを持っています。 | すべてのスレッドは、開いているファイルの同じセット、子プロセスを共有できます。 |
1つのプロセスがブロックされている場合、最初のプロセスのブロックが解除されるまで、他のプロセスは実行できません。 | 1つのスレッドがブロックされて待機している間、同じタスクの2番目のスレッドを実行できます。 |
スレッドを使用しない複数のプロセスは、より多くのリソースを使用します。 | 複数のスレッド化されたプロセスは、より少ないリソースを使用します。 |
複数のプロセスでは、各プロセスは他のプロセスから独立して動作します。 | あるスレッドは、別のスレッドのデータを読み取り、書き込み、または変更できます。 |
親プロセスに変更があったとしても、子プロセスには影響しません。 | メインスレッドに変更があると、そのプロセスの他のスレッドの動作に影響を与える可能性があります。 |
兄弟プロセスと通信するには、プロセスはプロセス間通信を使用する必要があります。 | スレッドは、そのプロセスの他のスレッドと直接通信できます。 |
マルチスレッドの概念
前に説明したように、マルチスレッドは、複数のスレッドを同時に実行することによってオペレーティングシステムの使用を管理するCPUの機能です。マルチスレッドの主なアイデアは、プロセスを複数のスレッドに分割することによって並列処理を実現することです。もっと簡単に言えば、マルチスレッドはスレッドの概念を使用してマルチタスクを実現する方法であると言えます。
マルチスレッドの概念は、次の例を使用して理解できます。
例
プロセスを実行していると仮定します。プロセスは、何かを書くためにMSワードを開くためのものである可能性があります。このようなプロセスでは、1つのスレッドがMSワードを開くために割り当てられ、別のスレッドが書き込みを行う必要があります。ここで、何かを編集する場合は、編集タスクなどを実行するために別のスレッドが必要になるとします。
次の図は、メモリ内に複数のスレッドがどのように存在するかを理解するのに役立ちます。
上の図から、すべてのスレッドに独自のレジスタセットとローカル変数が含まれている1つのプロセス内に複数のスレッドが存在する可能性があることがわかります。それ以外は、プロセス内のすべてのスレッドがグローバル変数を共有します。
マルチスレッドの長所
ここで、マルチスレッドのいくつかの利点を見てみましょう。利点は次のとおりです-
Speed of communication −マルチスレッドは、各コアまたはプロセッサが別々のスレッドを同時に処理するため、計算速度を向上させます。
Program remains responsive − 1つのスレッドが入力を待機し、別のスレッドが同時にGUIを実行するため、プログラムの応答性を維持できます。
Access to global variables −マルチスレッドでは、特定のプロセスのすべてのスレッドがグローバル変数にアクセスでき、グローバル変数に変更があった場合は、他のスレッドにも表示されます。
Utilization of resources −各プログラムで複数のスレッドを実行すると、CPUがより有効に活用され、CPUのアイドル時間が短くなります。
Sharing of data −プログラム内のスレッドは同じデータを共有できるため、スレッドごとに余分なスペースは必要ありません。
マルチスレッドの短所
ここで、マルチスレッドのいくつかの欠点を見てみましょう。短所は次のとおりです-
Not suitable for single processor system −マルチスレッドは、マルチプロセッサシステムでのパフォーマンスと比較して、シングルプロセッサシステムでの計算速度の点でパフォーマンスを達成するのが難しいと感じています。
Issue of security −プログラム内のすべてのスレッドが同じデータを共有していることがわかっているため、不明なスレッドがデータを変更する可能性があるため、常にセキュリティの問題があります。
Increase in complexity −マルチスレッドはプログラムの複雑さを増し、デバッグが困難になる可能性があります。
Lead to deadlock state −マルチスレッドは、プログラムがデッドロック状態になる潜在的なリスクにつながる可能性があります。
Synchronization required−相互排除を回避するために同期が必要です。これにより、メモリとCPUの使用率が向上します。
この章では、Pythonでスレッドを実装する方法を学習します。
スレッド実装用のPythonモジュール
Pythonスレッドは、スレッドがプロセスよりもはるかに少ないメモリを占有するため、軽量プロセスと呼ばれることもあります。スレッドを使用すると、一度に複数のタスクを実行できます。Pythonには、プログラムにスレッドを実装する次の2つのモジュールがあります-
<_thread>module
<threading>module
これら2つのモジュールの主な違いは <_thread> モジュールはスレッドを関数として扱いますが、 <threading>モジュールはすべてのスレッドをオブジェクトとして扱い、オブジェクト指向の方法で実装します。また、<_thread>モジュールは低レベルのスレッド化に効果的であり、 <threading> モジュール。
<_thread>モジュール
以前のバージョンのPythonでは、 <thread>モジュールですが、かなり長い間「非推奨」と見なされてきました。ユーザーは、<threading>代わりにモジュール。したがって、Python 3では、モジュール「スレッド」は使用できなくなりました。名前が「」に変更されました<_thread>"Python3の後方非互換性について。
の助けを借りて新しいスレッドを生成するには <_thread> モジュール、私たちは呼び出す必要があります start_new_threadそれの方法。このメソッドの動作は、次の構文の助けを借りて理解できます-
_thread.start_new_thread ( function, args[, kwargs] )
ここで−
args 引数のタプルです
kwargs キーワード引数のオプションの辞書です
引数を渡さずに関数を呼び出したい場合は、引数の空のタプルを使用する必要があります args。
このメソッド呼び出しはすぐに戻り、子スレッドが開始し、渡された引数のリストがある場合はそれを使用して関数を呼び出します。関数が戻ると、スレッドは終了します。
例
以下は、を使用して新しいスレッドを生成する例です。 <_thread>モジュール。ここではstart_new_thread()メソッドを使用しています。
import _thread
import time
def print_time( threadName, delay):
count = 0
while count < 5:
time.sleep(delay)
count += 1
print ("%s: %s" % ( threadName, time.ctime(time.time()) ))
try:
_thread.start_new_thread( print_time, ("Thread-1", 2, ) )
_thread.start_new_thread( print_time, ("Thread-2", 4, ) )
except:
print ("Error: unable to start thread")
while 1:
pass
出力
次の出力は、の助けを借りて新しいスレッドの生成を理解するのに役立ちます <_thread> モジュール。
Thread-1: Mon Apr 23 10:03:33 2018
Thread-2: Mon Apr 23 10:03:35 2018
Thread-1: Mon Apr 23 10:03:35 2018
Thread-1: Mon Apr 23 10:03:37 2018
Thread-2: Mon Apr 23 10:03:39 2018
Thread-1: Mon Apr 23 10:03:39 2018
Thread-1: Mon Apr 23 10:03:41 2018
Thread-2: Mon Apr 23 10:03:43 2018
Thread-2: Mon Apr 23 10:03:47 2018
Thread-2: Mon Apr 23 10:03:51 2018
<スレッド>モジュール
ザ・ <threading>モジュールはオブジェクト指向の方法で実装し、すべてのスレッドをオブジェクトとして扱います。したがって、<_ thread>モジュールよりもはるかに強力で高レベルのスレッドサポートを提供します。このモジュールはPython2.4に含まれています。
<threading>モジュールの追加メソッド
ザ・ <threading> モジュールは、のすべてのメソッドで構成されます <_thread>モジュールですが、追加のメソッドも提供します。追加の方法は次のとおりです-
threading.activeCount() −このメソッドは、アクティブなスレッドオブジェクトの数を返します
threading.currentThread() −このメソッドは、呼び出し元のスレッドコントロール内のスレッドオブジェクトの数を返します。
threading.enumerate() −このメソッドは、現在アクティブなすべてのスレッドオブジェクトのリストを返します。
run() − run()メソッドは、スレッドのエントリポイントです。
start() − start()メソッドは、runメソッドを呼び出してスレッドを開始します。
join([time]) − join()は、スレッドが終了するのを待ちます。
isAlive() − isAlive()メソッドは、スレッドがまだ実行中であるかどうかをチェックします。
getName() − getName()メソッドはスレッドの名前を返します。
setName() − setName()メソッドは、スレッドの名前を設定します。
スレッド化を実装するために、 <threading> モジュールには Thread 次のメソッドを提供するクラス-
<threading>モジュールを使用してスレッドを作成するにはどうすればよいですか?
このセクションでは、を使用してスレッドを作成する方法を学習します。 <threading>モジュール。次の手順に従って、<threading>モジュールを使用して新しいスレッドを作成します-
Step 1 −このステップでは、の新しいサブクラスを定義する必要があります。 Thread クラス。
Step 2 −次に、引数を追加するには、をオーバーライドする必要があります __init__(self [,args]) 方法。
Step 3 −このステップでは、run(self [、args])メソッドをオーバーライドして、開始時にスレッドが実行する必要があることを実装する必要があります。
今、新しいを作成した後 Thread サブクラスの場合、そのインスタンスを作成し、を呼び出して新しいスレッドを開始できます。 start()、これは順番に run() 方法。
例
この例を検討して、を使用して新しいスレッドを生成する方法を学習してください。 <threading> モジュール。
import threading
import time
exitFlag = 0
class myThread (threading.Thread):
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
print ("Starting " + self.name)
print_time(self.name, self.counter, 5)
print ("Exiting " + self.name)
def print_time(threadName, delay, counter):
while counter:
if exitFlag:
threadName.exit()
time.sleep(delay)
print ("%s: %s" % (threadName, time.ctime(time.time())))
counter -= 1
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print ("Exiting Main Thread")
Starting Thread-1
Starting Thread-2
出力
ここで、次の出力について考えてみましょう。
Thread-1: Mon Apr 23 10:52:09 2018
Thread-1: Mon Apr 23 10:52:10 2018
Thread-2: Mon Apr 23 10:52:10 2018
Thread-1: Mon Apr 23 10:52:11 2018
Thread-1: Mon Apr 23 10:52:12 2018
Thread-2: Mon Apr 23 10:52:12 2018
Thread-1: Mon Apr 23 10:52:13 2018
Exiting Thread-1
Thread-2: Mon Apr 23 10:52:14 2018
Thread-2: Mon Apr 23 10:52:16 2018
Thread-2: Mon Apr 23 10:52:18 2018
Exiting Thread-2
Exiting Main Thread
さまざまなスレッド状態のPythonプログラム
スレッドの状態には、新規、実行可能、実行中、待機中、停止の5つがあります。これらの5つの中で、私たちは主に3つの状態に焦点を当てます-実行中、待機中、そして死んでいます。スレッドは実行状態でリソースを取得し、待機状態でリソースを待機します。実行および取得された場合のリソースの最終リリースは、デッド状態です。
start()、sleep()、join()メソッドを使用した次のPythonプログラムは、スレッドがそれぞれ実行中、待機中、デッド状態になったことを示しています。
Step 1 −必要なモジュール、<threading>および<time>をインポートします
import threading
import time
Step 2 −スレッドの作成中に呼び出される関数を定義します。
def thread_states():
print("Thread entered in running state")
Step 3 − timeモジュールのsleep()メソッドを使用して、スレッドをたとえば2秒間待機させています。
time.sleep(2)
Step 4 −ここで、上記で定義した関数の引数を取るT1という名前のスレッドを作成しています。
T1 = threading.Thread(target=thread_states)
Step 5−これで、start()関数を使用して、スレッドを開始できます。関数の定義時に設定されたメッセージが生成されます。
T1.start()
Thread entered in running state
Step 6 −これで、実行が終了した後、ついにjoin()メソッドを使用してスレッドを強制終了できます。
T1.join()
Pythonでスレッドを開始する
Pythonでは、さまざまな方法で新しいスレッドを開始できますが、その中で最も簡単な方法は、単一の関数として定義することです。関数を定義した後、これを新しいターゲットとして渡すことができますthreading.Threadオブジェクトなど。次のPythonコードを実行して、関数がどのように機能するかを理解します-
import threading
import time
import random
def Thread_execution(i):
print("Execution of Thread {} started\n".format(i))
sleepTime = random.randint(1,4)
time.sleep(sleepTime)
print("Execution of Thread {} finished".format(i))
for i in range(4):
thread = threading.Thread(target=Thread_execution, args=(i,))
thread.start()
print("Active Threads:" , threading.enumerate())
出力
Execution of Thread 0 started
Active Threads:
[<_MainThread(MainThread, started 6040)>,
<HistorySavingThread(IPythonHistorySavingThread, started 5968)>,
<Thread(Thread-3576, started 3932)>]
Execution of Thread 1 started
Active Threads:
[<_MainThread(MainThread, started 6040)>,
<HistorySavingThread(IPythonHistorySavingThread, started 5968)>,
<Thread(Thread-3576, started 3932)>,
<Thread(Thread-3577, started 3080)>]
Execution of Thread 2 started
Active Threads:
[<_MainThread(MainThread, started 6040)>,
<HistorySavingThread(IPythonHistorySavingThread, started 5968)>,
<Thread(Thread-3576, started 3932)>,
<Thread(Thread-3577, started 3080)>,
<Thread(Thread-3578, started 2268)>]
Execution of Thread 3 started
Active Threads:
[<_MainThread(MainThread, started 6040)>,
<HistorySavingThread(IPythonHistorySavingThread, started 5968)>,
<Thread(Thread-3576, started 3932)>,
<Thread(Thread-3577, started 3080)>,
<Thread(Thread-3578, started 2268)>,
<Thread(Thread-3579, started 4520)>]
Execution of Thread 0 finished
Execution of Thread 1 finished
Execution of Thread 2 finished
Execution of Thread 3 finished
Pythonのデーモンスレッド
Pythonでデーモンスレッドを実装する前に、デーモンスレッドとその使用法について知っておく必要があります。コンピューティングに関しては、デーモンは、データ送信、ファイル転送などのさまざまなサービスの要求を処理するバックグラウンドプロセスです。それが不要になると、休止状態になります。デーモン以外のスレッドを使用しても、同じタスクを実行できます。ただし、この場合、メインスレッドは非デーモンスレッドを手動で追跡する必要があります。一方、デーモンスレッドを使用している場合、メインスレッドはこれを完全に忘れることができ、メインスレッドが終了すると強制終了されます。デーモンスレッドに関するもう1つの重要な点は、デーモンスレッドが完了しないか、途中で強制終了されても影響を与えない、必須ではないタスクにのみ使用することを選択できることです。以下は、Pythonでのデーモンスレッドの実装です。
import threading
import time
def nondaemonThread():
print("starting my thread")
time.sleep(8)
print("ending my thread")
def daemonThread():
while True:
print("Hello")
time.sleep(2)
if __name__ == '__main__':
nondaemonThread = threading.Thread(target = nondaemonThread)
daemonThread = threading.Thread(target = daemonThread)
daemonThread.setDaemon(True)
daemonThread.start()
nondaemonThread.start()
上記のコードには、2つの関数があります。 >nondaemonThread() そして >daemonThread()。最初の関数はその状態を出力し、8秒後にスリープしますが、deamonThread()関数は2秒ごとにHelloを無期限に出力します。次の出力の助けを借りて、非デーモンスレッドとデーモンスレッドの違いを理解できます-
Hello
starting my thread
Hello
Hello
Hello
Hello
ending my thread
Hello
Hello
Hello
Hello
Hello
スレッドの同期は、2つ以上の同時スレッドがクリティカルセクションと呼ばれるプログラムセグメントに同時にアクセスしていないことを保証できる方法として定義できます。一方、クリティカルセクションは、共有リソースにアクセスするプログラムの一部であることがわかっています。したがって、同期とは、リソースに同時にアクセスすることによって、2つ以上のスレッドが相互にインターフェイスしないようにするプロセスであると言えます。次の図は、4つのスレッドがプログラムのクリティカルセクションに同時にアクセスしようとしていることを示しています。
明確にするために、2つ以上のスレッドが同時にリストにオブジェクトを追加しようとしていると仮定します。この行為は、1つまたはすべてのオブジェクトを削除するか、リストの状態を完全に破壊するため、正常に終了することはできません。ここでの同期の役割は、一度に1つのスレッドのみがリストにアクセスできることです。
スレッド同期の問題
並行プログラミングの実装中または同期プリミティブの適用中に問題が発生する可能性があります。このセクションでは、2つの主要な問題について説明します。問題は-
- Deadlock
- 競合状態
競合状態
これは、並行プログラミングにおける主要な問題の1つです。共有リソースへの同時アクセスは、競合状態につながる可能性があります。競合状態は、2つ以上のスレッドが共有データにアクセスし、同時にその値を変更しようとする状態の発生として定義できます。このため、変数の値は予測不可能であり、プロセスのコンテキストスイッチのタイミングによって異なる場合があります。
例
競合状態の概念を理解するために、この例を検討してください。
Step 1 −このステップでは、スレッドモジュールをインポートする必要があります−
import threading
Step 2 −ここで、グローバル変数、たとえばxを、その値とともに0 −として定義します。
x = 0
Step 3 −ここで、を定義する必要があります increment_global() このグローバル関数x−で1ずつインクリメントする関数
def increment_global():
global x
x += 1
Step 4 −このステップでは、 taskofThread()関数。increment_global()関数を指定された回数呼び出します。この例では、50000回です-
def taskofThread():
for _ in range(50000):
increment_global()
Step 5−ここで、スレッドt1とt2が作成されるmain()関数を定義します。どちらもstart()関数を使用して開始され、join()関数を使用してジョブが終了するまで待機します。
def main():
global x
x = 0
t1 = threading.Thread(target= taskofThread)
t2 = threading.Thread(target= taskofThread)
t1.start()
t2.start()
t1.join()
t2.join()
Step 6−ここで、main()関数を呼び出す反復回数のように範囲を指定する必要があります。ここでは、5回呼び出しています。
if __name__ == "__main__":
for i in range(5):
main()
print("x = {1} after Iteration {0}".format(i,x))
以下に示す出力では、各反復後のxの値が100000と予想されるため、競合状態の影響を確認できます。ただし、値には多くの変動があります。これは、共有グローバル変数xへのスレッドの同時アクセスによるものです。
出力
x = 100000 after Iteration 0
x = 54034 after Iteration 1
x = 80230 after Iteration 2
x = 93602 after Iteration 3
x = 93289 after Iteration 4
ロックを使用した競合状態への対処
上記のプログラムで競合状態の影響を確認したので、複数のスレッド間の競合状態を処理できる同期ツールが必要です。Pythonでは、<threading>モジュールは、競合状態を処理するためのLockクラスを提供します。さらに、Lockクラスは、複数のスレッド間の競合状態を処理できるさまざまなメソッドを提供します。方法は以下のとおりです。
取得()メソッド
このメソッドは、ロックを取得、つまりブロックするために使用されます。ロックは、次の真または偽の値に応じて、ブロックまたは非ブロックにすることができます-
With value set to True −デフォルトの引数であるTrueを指定してacquire()メソッドが呼び出された場合、ロックが解除されるまでスレッドの実行はブロックされます。
With value set to False −acquire()メソッドがデフォルトの引数ではないFalseで呼び出された場合、スレッドの実行はtrueに設定されるまで、つまりロックされるまでブロックされません。
release()メソッド
このメソッドは、ロックを解放するために使用されます。以下は、この方法に関連するいくつかの重要なタスクです-
ロックがロックされている場合、 release()メソッドはそれをロック解除します。その仕事は、複数のスレッドがブロックされ、ロックがロック解除されるのを待っている場合に、正確に1つのスレッドを続行できるようにすることです。
それは発生します ThreadError ロックがすでにロック解除されている場合。
これで、競合状態を回避するために、上記のプログラムをロッククラスとそのメソッドで書き直すことができます。ロック引数を使用してtaskofThread()メソッドを定義してから、競合状態を回避するために、ロックのブロックと非ブロックにacquire()メソッドとrelease()メソッドを使用する必要があります。
例
以下は、競合状態を処理するためのロックの概念を理解するためのPythonプログラムの例です。
import threading
x = 0
def increment_global():
global x
x += 1
def taskofThread(lock):
for _ in range(50000):
lock.acquire()
increment_global()
lock.release()
def main():
global x
x = 0
lock = threading.Lock()
t1 = threading.Thread(target = taskofThread, args = (lock,))
t2 = threading.Thread(target = taskofThread, args = (lock,))
t1.start()
t2.start()
t1.join()
t2.join()
if __name__ == "__main__":
for i in range(5):
main()
print("x = {1} after Iteration {0}".format(i,x))
次の出力は、競合状態の影響が無視されていることを示しています。xの値は、各反復後、100000になります。これは、このプログラムの期待どおりです。
出力
x = 100000 after Iteration 0
x = 100000 after Iteration 1
x = 100000 after Iteration 2
x = 100000 after Iteration 3
x = 100000 after Iteration 4
デッドロック-食事する哲学者の問題
デッドロックは、並行システムの設計中に直面する可能性のある厄介な問題です。この問題は、食事する哲学者の問題の助けを借りて、次のように説明できます。
Edsger Dijkstraは当初、食事する哲学者の問題を紹介しました。これは、デッドロックと呼ばれる並行システムの最大の問題の1つを示す有名なイラストの1つです。
この問題では、5人の有名な哲学者が円卓に座ってボウルから食べ物を食べています。5人の哲学者が食べ物を食べるために使用できる5つのフォークがあります。しかし、哲学者たちは、食べ物を食べるために同時に2つのフォークを使用することにしました。
さて、哲学者には2つの主な条件があります。第一に、各哲学者は食事中または思考状態のいずれかである可能性があり、第二に、彼らは最初に両方のフォーク、すなわち左と右を取得する必要があります。この問題は、5人の哲学者のそれぞれが左のフォークを同時に選ぶことに成功したときに発生します。今、彼らは皆、正しいフォークが自由になるのを待っていますが、食べ物を食べるまでフォークを手放すことはなく、正しいフォークが利用できなくなるでしょう。したがって、夕食の席でデッドロック状態が発生します。
並行システムでのデッドロック
ご覧のとおり、同時システムでも同じ問題が発生する可能性があります。上記の例のフォークはシステムリソースであり、各哲学者はリソースを取得するために競合しているプロセスを表すことができます。
Pythonプログラムによる解決策
この問題の解決策は、哲学者を2つのタイプに分けることによって見つけることができます– greedy philosophers そして generous philosophers。主に貪欲な哲学者は左のフォークを拾い上げ、そこに来るまで待ちます。それから彼は正しいフォークがそこにあるのを待ち、それを拾い上げ、食べ、そしてそれを置きます。一方、寛大な哲学者は左のフォークを拾おうとし、そこにない場合は、しばらくしてから再試行します。彼らが左のフォークを手に入れたら、彼らは右のフォークを手に入れようとします。彼らも正しいフォークを手に入れるなら、彼らは両方のフォークを食べて解放します。ただし、右のフォークを取得できない場合は、左のフォークを解放します。
例
次のPythonプログラムは、食事する哲学者の問題の解決策を見つけるのに役立ちます-
import threading
import random
import time
class DiningPhilosopher(threading.Thread):
running = True
def __init__(self, xname, Leftfork, Rightfork):
threading.Thread.__init__(self)
self.name = xname
self.Leftfork = Leftfork
self.Rightfork = Rightfork
def run(self):
while(self.running):
time.sleep( random.uniform(3,13))
print ('%s is hungry.' % self.name)
self.dine()
def dine(self):
fork1, fork2 = self.Leftfork, self.Rightfork
while self.running:
fork1.acquire(True)
locked = fork2.acquire(False)
if locked: break
fork1.release()
print ('%s swaps forks' % self.name)
fork1, fork2 = fork2, fork1
else:
return
self.dining()
fork2.release()
fork1.release()
def dining(self):
print ('%s starts eating '% self.name)
time.sleep(random.uniform(1,10))
print ('%s finishes eating and now thinking.' % self.name)
def Dining_Philosophers():
forks = [threading.Lock() for n in range(5)]
philosopherNames = ('1st','2nd','3rd','4th', '5th')
philosophers= [DiningPhilosopher(philosopherNames[i], forks[i%5], forks[(i+1)%5]) \
for i in range(5)]
random.seed()
DiningPhilosopher.running = True
for p in philosophers: p.start()
time.sleep(30)
DiningPhilosopher.running = False
print (" It is finishing.")
Dining_Philosophers()
上記のプログラムは、貪欲で寛大な哲学者の概念を使用しています。プログラムはまた使用しましたacquire() そして release() の方法 Lock のクラス <threading>モジュール。次の出力で解決策を見ることができます-
出力
4th is hungry.
4th starts eating
1st is hungry.
1st starts eating
2nd is hungry.
5th is hungry.
3rd is hungry.
1st finishes eating and now thinking.3rd swaps forks
2nd starts eating
4th finishes eating and now thinking.
3rd swaps forks5th starts eating
5th finishes eating and now thinking.
4th is hungry.
4th starts eating
2nd finishes eating and now thinking.
3rd swaps forks
1st is hungry.
1st starts eating
4th finishes eating and now thinking.
3rd starts eating
5th is hungry.
5th swaps forks
1st finishes eating and now thinking.
5th starts eating
2nd is hungry.
2nd swaps forks
4th is hungry.
5th finishes eating and now thinking.
3rd finishes eating and now thinking.
2nd starts eating 4th starts eating
It is finishing.
実生活では、人々のチームが共通のタスクに取り組んでいる場合、タスクを適切に完了するために彼らの間でコミュニケーションが必要です。同じアナロジーがスレッドにも当てはまります。プログラミングでは、プロセッサの理想的な時間を短縮するために、複数のスレッドを作成し、すべてのスレッドに異なるサブタスクを割り当てます。したがって、通信機能が必要であり、同期してジョブを完了するために相互に対話する必要があります。
スレッドの相互通信に関連する次の重要な点を考慮してください-
No performance gain −スレッドとプロセス間の適切な通信を実現できない場合、並行性と並列性によるパフォーマンスの向上は役に立ちません。
Accomplish task properly −スレッド間の適切な相互通信メカニズムがないと、割り当てられたタスクを正しく完了できません。
More efficient than inter-process communication −プロセス内のすべてのスレッドは同じアドレス空間を共有し、共有メモリを使用する必要がないため、スレッド間通信はプロセス間通信よりも効率的で使いやすいです。
スレッドセーフな通信のためのPythonデータ構造
マルチスレッドコードでは、あるスレッドから別のスレッドに情報を渡すという問題が発生します。標準の通信プリミティブはこの問題を解決しません。したがって、スレッド間でオブジェクトを共有して通信をスレッドセーフにするために、独自の複合オブジェクトを実装する必要があります。以下は、いくつかの変更を加えた後、スレッドセーフな通信を提供するいくつかのデータ構造です。
セット
セットデータ構造をスレッドセーフな方法で使用するには、セットクラスを拡張して独自のロックメカニズムを実装する必要があります。
例
これはクラスを拡張するPythonの例です-
class extend_class(set):
def __init__(self, *args, **kwargs):
self._lock = Lock()
super(extend_class, self).__init__(*args, **kwargs)
def add(self, elem):
self._lock.acquire()
try:
super(extend_class, self).add(elem)
finally:
self._lock.release()
def delete(self, elem):
self._lock.acquire()
try:
super(extend_class, self).delete(elem)
finally:
self._lock.release()
上記の例では、 extend_class Pythonからさらに継承されたものが定義されています set class。このクラスのコンストラクター内にロックオブジェクトが作成されます。今、2つの機能があります-add() そして delete()。これらの関数は定義されており、スレッドセーフです。彼らは両方ともに依存していますsuper 1つの重要な例外を除いてクラス機能。
デコレータ
これは、スレッドセーフな通信のもう1つの重要な方法であり、デコレータを使用します。
例
デコレータの使用方法を示すPythonの例を考えてみましょう&mminus;
def lock_decorator(method):
def new_deco_method(self, *args, **kwargs):
with self._lock:
return method(self, *args, **kwargs)
return new_deco_method
class Decorator_class(set):
def __init__(self, *args, **kwargs):
self._lock = Lock()
super(Decorator_class, self).__init__(*args, **kwargs)
@lock_decorator
def add(self, *args, **kwargs):
return super(Decorator_class, self).add(elem)
@lock_decorator
def delete(self, *args, **kwargs):
return super(Decorator_class, self).delete(elem)
上記の例では、lock_decoratorという名前のデコレータメソッドが定義されており、Pythonメソッドクラスからさらに継承されています。次に、このクラスのコンストラクター内にロックオブジェクトが作成されます。現在、add()とdelete()の2つの関数があります。これらの関数は定義されており、スレッドセーフです。どちらも、1つの重要な例外を除いて、スーパークラスの機能に依存しています。
リスト
リストのデータ構造は、スレッドセーフで、一時的なメモリ内ストレージ用の迅速で簡単な構造です。Cpythonでは、GILはそれらへの同時アクセスから保護します。リストはスレッドセーフであることがわかったので、リストにあるデータについてはどうでしょうか。実際には、リストのデータは保護されていません。例えば、L.append(x)別のスレッドが同じことを行おうとしている場合、期待される結果を返すことは保証されません。これは、append() はアトミック操作でスレッドセーフですが、他のスレッドはリストのデータを同時に変更しようとしているため、出力に対する競合状態の副作用を確認できます。
この種の問題を解決し、データを安全に変更するには、適切なロックメカニズムを実装する必要があります。これにより、複数のスレッドが競合状態に陥ることがないようになります。適切なロックメカニズムを実装するために、前の例で行ったようにクラスを拡張できます。
リストに対する他のいくつかのアトミック操作は次のとおりです-
L.append(x)
L1.extend(L2)
x = L[i]
x = L.pop()
L1[i:j] = L2
L.sort()
x = y
x.field = y
D[x] = y
D1.update(D2)
D.keys()
ここで−
- L、L1、L2はすべてリストです
- D、D1、D2は口述です
- x、yはオブジェクトです
- i、jはintです
キュー
リストのデータが保護されていない場合、その結果に直面しなければならない可能性があります。競合状態の間違ったデータ項目を取得または削除する場合があります。そのため、キューデータ構造を使用することをお勧めします。キューの実際の例は、車両が最初に進入し、最初に退出する単一車線の一方通行道路です。切符売り場やバス停の列の実例をもっと見ることができます。
キューはデフォルトでスレッドセーフなデータ構造であり、複雑なロックメカニズムの実装について心配する必要はありません。Pythonは私たちに
キューの種類
このセクションでは、さまざまな種類のキューについて説明します。Pythonには、から使用するキューの3つのオプションがあります。<queue> モジュール-
- 通常のキュー(FIFO、先入れ先出し)
- LIFO、後入れ先出し
- Priority
後続のセクションで、さまざまなキューについて学習します。
通常のキュー(FIFO、先入れ先出し)
これは、Pythonが提供する最も一般的に使用されるキューの実装です。このキューイングメカニズムでは、誰が最初に来るかは誰でも最初にサービスを受けます。FIFOは通常のキューとも呼ばれます。FIFOキューは次のように表すことができます-
FIFOキューのPython実装
Pythonでは、FIFOキューはマルチスレッドだけでなくシングルスレッドでも実装できます。
シングルスレッドのFIFOキュー
シングルスレッドでFIFOキューを実装する場合、 Queueクラスは、基本的な先入れ先出しコンテナを実装します。要素は、を使用してシーケンスの1つの「終わり」に追加されます。put()、を使用してもう一方の端から削除 get()。
例
以下は、シングルスレッドでFIFOキューを実装するためのPythonプログラムです-
import queue
q = queue.Queue()
for i in range(8):
q.put("item-" + str(i))
while not q.empty():
print (q.get(), end = " ")
出力
item-0 item-1 item-2 item-3 item-4 item-5 item-6 item-7
出力は、上記のプログラムが単一のスレッドを使用して、要素が挿入されたのと同じ順序でキューから削除されることを示しています。
複数のスレッドを持つFIFOキュー
複数のスレッドでFIFOを実装するには、キューモジュールから拡張されたmyqueue()関数を定義する必要があります。get()メソッドとput()メソッドの動作は、シングルスレッドでFIFOキューを実装する場合の上記と同じです。次に、マルチスレッドにするには、スレッドを宣言してインスタンス化する必要があります。これらのスレッドは、FIFO方式でキューを消費します。
例
以下は、複数のスレッドでFIFOキューを実装するためのPythonプログラムです。
import threading
import queue
import random
import time
def myqueue(queue):
while not queue.empty():
item = queue.get()
if item is None:
break
print("{} removed {} from the queue".format(threading.current_thread(), item))
queue.task_done()
time.sleep(2)
q = queue.Queue()
for i in range(5):
q.put(i)
threads = []
for i in range(4):
thread = threading.Thread(target=myqueue, args=(q,))
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
出力
<Thread(Thread-3654, started 5044)> removed 0 from the queue
<Thread(Thread-3655, started 3144)> removed 1 from the queue
<Thread(Thread-3656, started 6996)> removed 2 from the queue
<Thread(Thread-3657, started 2672)> removed 3 from the queue
<Thread(Thread-3654, started 5044)> removed 4 from the queue
LIFO、後入れ先出しキュー
このキューは、FIFO(先入れ先出し)キューとはまったく逆のアナロジーを使用します。このキューイングメカニズムでは、最後に来た人が最初にサービスを取得します。これは、スタックデータ構造の実装に似ています。LIFOキューは、人工知能のアルゴリズムのような深さ優先探索を実装する際に役立ちます。
LIFOキューのPython実装
Pythonでは、LIFOキューはマルチスレッドだけでなくシングルスレッドでも実装できます。
シングルスレッドのLIFOキュー
シングルスレッドでLIFOキューを実装する場合、 Queue クラスは、構造を使用して基本的な後入れ先出しコンテナを実装します Queue.LifoQueue。さて、電話でput()、要素はコンテナのヘッドに追加され、使用時にもヘッドから削除されます get()。
例
以下は、シングルスレッドでLIFOキューを実装するためのPythonプログラムです-
import queue
q = queue.LifoQueue()
for i in range(8):
q.put("item-" + str(i))
while not q.empty():
print (q.get(), end=" ")
Output:
item-7 item-6 item-5 item-4 item-3 item-2 item-1 item-0
出力は、上記のプログラムが単一のスレッドを使用して、要素が挿入されたのとは逆の順序でキューから削除されることを示しています。
複数のスレッドを持つLIFOキュー
実装は、複数のスレッドでFIFOキューの実装を行ったのと似ています。唯一の違いは、を使用する必要があるということですQueue 構造体を使用して基本的な後入れ先出しコンテナを実装するクラス Queue.LifoQueue。
例
以下は、複数のスレッドでLIFOキューを実装するためのPythonプログラムです-
import threading
import queue
import random
import time
def myqueue(queue):
while not queue.empty():
item = queue.get()
if item is None:
break
print("{} removed {} from the queue".format(threading.current_thread(), item))
queue.task_done()
time.sleep(2)
q = queue.LifoQueue()
for i in range(5):
q.put(i)
threads = []
for i in range(4):
thread = threading.Thread(target=myqueue, args=(q,))
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
出力
<Thread(Thread-3882, started 4928)> removed 4 from the queue
<Thread(Thread-3883, started 4364)> removed 3 from the queue
<Thread(Thread-3884, started 6908)> removed 2 from the queue
<Thread(Thread-3885, started 3584)> removed 1 from the queue
<Thread(Thread-3882, started 4928)> removed 0 from the queue
優先キュー
FIFOおよびLIFOキューでは、アイテムの順序は挿入の順序に関連しています。ただし、挿入順序よりも優先度が重要な場合が多くあります。実際の例を考えてみましょう。空港のセキュリティがさまざまなカテゴリの人々をチェックしているとします。VVIPの人々、航空会社のスタッフ、税関職員、カテゴリーは、庶民のように到着に基づいてチェックされるのではなく、優先的にチェックされる場合があります。
優先キューで考慮する必要があるもう1つの重要な側面は、タスクスケジューラを開発する方法です。一般的な設計の1つは、キュー内で最も多くのエージェントタスクを優先度に基づいて処理することです。このデータ構造を使用して、優先度の値に基づいてキューからアイテムを取得できます。
優先度付きキューのPython実装
Pythonでは、優先キューはマルチスレッドだけでなくシングルスレッドでも実装できます。
シングルスレッドの優先キュー
シングルスレッドで優先キューを実装する場合、 Queue クラスは、構造を使用して優先コンテナにタスクを実装します Queue.PriorityQueue。さて、電話でput()、要素には、最小値が最高の優先度を持つ値が追加されるため、を使用して最初に取得されます。 get()。
例
シングルスレッドで優先キューを実装するための次のPythonプログラムを検討してください-
import queue as Q
p_queue = Q.PriorityQueue()
p_queue.put((2, 'Urgent'))
p_queue.put((1, 'Most Urgent'))
p_queue.put((10, 'Nothing important'))
prio_queue.put((5, 'Important'))
while not p_queue.empty():
item = p_queue.get()
print('%s - %s' % item)
出力
1 – Most Urgent
2 - Urgent
5 - Important
10 – Nothing important
上記の出力では、キューが優先度に基づいてアイテムを格納していることがわかります。値が少ないほど優先度が高くなります。
マルチスレッドの優先キュー
実装は、複数のスレッドを持つFIFOおよびLIFOキューの実装に似ています。唯一の違いは、を使用する必要があるということですQueue 構造体を使用して優先度を初期化するためのクラス Queue.PriorityQueue。もう1つの違いは、キューの生成方法です。以下の例では、2つの同一のデータセットを使用して生成されます。
例
次のPythonプログラムは、複数のスレッドを使用した優先キューの実装に役立ちます-
import threading
import queue
import random
import time
def myqueue(queue):
while not queue.empty():
item = queue.get()
if item is None:
break
print("{} removed {} from the queue".format(threading.current_thread(), item))
queue.task_done()
time.sleep(1)
q = queue.PriorityQueue()
for i in range(5):
q.put(i,1)
for i in range(5):
q.put(i,1)
threads = []
for i in range(2):
thread = threading.Thread(target=myqueue, args=(q,))
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
出力
<Thread(Thread-4939, started 2420)> removed 0 from the queue
<Thread(Thread-4940, started 3284)> removed 0 from the queue
<Thread(Thread-4939, started 2420)> removed 1 from the queue
<Thread(Thread-4940, started 3284)> removed 1 from the queue
<Thread(Thread-4939, started 2420)> removed 2 from the queue
<Thread(Thread-4940, started 3284)> removed 2 from the queue
<Thread(Thread-4939, started 2420)> removed 3 from the queue
<Thread(Thread-4940, started 3284)> removed 3 from the queue
<Thread(Thread-4939, started 2420)> removed 4 from the queue
<Thread(Thread-4940, started 3284)> removed 4 from the queue
この章では、スレッドアプリケーションのテストについて学習します。また、テストの重要性についても学びます。
なぜテストするのですか?
テストの重要性について議論する前に、テストとは何かを知る必要があります。一般的に、テストとは、何かがどれだけうまく機能しているかを調べる手法です。一方、特にコンピュータプログラムやソフトウェアについて話す場合、テストはソフトウェアプログラムの機能にアクセスする手法です。
このセクションでは、ソフトウェアテストの重要性について説明します。ソフトウェア開発では、クライアントにソフトウェアをリリースする前に、再確認する必要があります。そのため、経験豊富なテストチームがソフトウェアをテストすることが非常に重要です。ソフトウェアテストの重要性を理解するには、次の点を考慮してください。
ソフトウェア品質の向上
確かに、低品質のソフトウェアを提供したい企業はなく、低品質のソフトウェアを購入したいクライアントもいません。テストは、その中のバグを見つけて修正することにより、ソフトウェアの品質を向上させます。
顧客満足度
ビジネスの最も重要な部分は、顧客の満足度です。バグのない高品質のソフトウェアを提供することで、企業は顧客満足を実現できます。
新機能の影響を軽減する
10000行のソフトウェアシステムを作成し、新しい機能を追加する必要があるとすると、開発チームはこの新しい機能がソフトウェア全体に与える影響について懸念を抱くでしょう。ここでも、テストは重要な役割を果たします。テストチームが優れた一連のテストを行った場合、壊滅的な中断の可能性から私たちを救うことができるからです。
ユーザー体験
ビジネスのもう1つの最も重要な部分は、その製品のユーザーのエクスペリエンスです。テストのみが、エンドユーザーが製品をシンプルで使いやすいと感じることを保証できます。
経費の削減
テストでは、配信後に修正するのではなく、開発のテスト段階でバグを見つけて修正することにより、ソフトウェアの総コストを削減できます。ソフトウェアの納品後に大きなバグがあると、費用などの有形コストと、顧客の不満や企業の評判などの無形コストが増加します。
何をテストしますか?
何をテストするかについて適切な知識を持っていることを常にお勧めします。このセクションでは、ソフトウェアをテストする際のテスターの主な動機を最初に理解します。コードカバレッジ、つまり、テスト中にテストスイートがヒットするコードの行数は避ける必要があります。これは、テスト中、コードの行数だけに焦点を当てても、システムに実際の価値がないためです。いくつかのバグが残っている可能性があります。これらのバグは、展開後も後の段階で反映されます。
何をテストするかに関連する次の重要なポイントを考慮してください-
コードカバレッジではなく、コードの機能のテストに焦点を当てる必要があります。
最初にコードの最も重要な部分をテストしてから、コードの重要性の低い部分に移動する必要があります。それは間違いなく時間を節約します。
テスターは、ソフトウェアを限界まで押し上げることができるさまざまなテストを行う必要があります。
並行ソフトウェアプログラムをテストするためのアプローチ
マルチコアアーキテクチャの真の機能を利用できるため、並行ソフトウェアシステムが順次システムに取って代わりつつあります。最近では、携帯電話から洗濯機、車から飛行機など、あらゆるもので並行システムプログラムが使用されています。単一スレッドアプリケーションに複数のスレッドを追加した場合、次のような並行ソフトウェアプログラムのテストにはさらに注意が必要です。すでにバグがあると、複数のバグが発生します。
並行ソフトウェアプログラムのテスト手法は、競合状態、デッドロック、原子性の侵害などの潜在的に有害なパターンを明らかにするインターリーブの選択に広く焦点を合わせています。以下は、並行ソフトウェアプログラムをテストするための2つのアプローチです-
体系的な調査
このアプローチは、インターリーブの空間を可能な限り広く探索することを目的としています。そのようなアプローチは強引な手法を採用することができ、他のアプローチは部分次数削減手法またはヒューリスティック手法を採用してインターリーブの空間を探索します。
プロパティ主導
プロパティ駆動型のアプローチは、疑わしいメモリアクセスパターンなどの特定のプロパティを公開するインターリーブの下で同時実行障害が発生する可能性が高いという観察に依存しています。さまざまなプロパティ駆動型アプローチは、競合状態、デッドロック、原子性の違反などのさまざまな障害を対象としています。これは、1つまたは他の特定のプロパティにさらに依存します。
テスト戦略
テスト戦略は、テストアプローチとも呼ばれます。この戦略は、テストの実行方法を定義します。テストアプローチには2つの手法があります-
プロアクティブ
ビルドが作成される前に欠陥を見つけて修正するために、テスト設計プロセスをできるだけ早く開始するアプローチ。
反応性
開発プロセスが完了するまでテストを開始しないアプローチ。
Pythonプログラムにテスト戦略やアプローチを適用する前に、ソフトウェアプログラムで発生する可能性のあるエラーの種類について基本的な考え方を理解しておく必要があります。エラーは次のとおりです-
構文エラー
プログラム開発中に、多くの小さなエラーが発生する可能性があります。エラーは主に入力ミスが原因です。たとえば、コロンがない、キーワードのスペルが間違っているなどです。このようなエラーは、プログラムの構文の誤りが原因であり、ロジックの誤りが原因ではありません。したがって、これらのエラーは構文エラーと呼ばれます。
セマンティックエラー
セマンティックエラーは、論理エラーとも呼ばれます。ソフトウェアプログラムに論理的または意味的なエラーがある場合、ステートメントは正しくコンパイルおよび実行されますが、論理が正しくないため、目的の出力が得られません。
ユニットテスト
これは、Pythonプログラムをテストするために最もよく使用されるテスト戦略の1つです。この戦略は、コードのユニットまたはコンポーネントをテストするために使用されます。ユニットまたはコンポーネントとは、コードのクラスまたは関数を意味します。ユニットテストは、「小さな」ユニットをテストすることにより、大規模なプログラミングシステムのテストを簡素化します。上記の概念の助けを借りて、ユニットテストは、ソースコードの個々のユニットをテストして、目的の出力が返されるかどうかを判断する方法として定義できます。
以降のセクションでは、単体テスト用のさまざまなPythonモジュールについて学習します。
unittestモジュール
ユニットテストの最初のモジュールは、unittestモジュールです。これはJUnitに触発され、デフォルトでPython3.6に含まれています。テストの自動化、テストのセットアップコードとシャットダウンコードの共有、テストのコレクションへの集約、およびレポートフレームワークからのテストの独立性をサポートします。
以下は、unittestモジュールでサポートされているいくつかの重要な概念です。
テキストフィクスチャ
これは、テストを開始する前に実行し、テストの終了後に破棄できるようにテストを設定するために使用されます。テストを開始する前に必要な一時データベース、ディレクトリなどの作成が含まれる場合があります。
テストケース
テストケースは、必要な応答が特定の入力セットからのものであるかどうかを確認します。unittestモジュールには、新しいテストケースを作成するために使用できるTestCaseという名前の基本クラスが含まれています。デフォルトで2つのメソッドが含まれています-
setUp()−テストフィクスチャを実行する前にセットアップするためのフックメソッド。これは、実装されたテストメソッドを呼び出す前に呼び出されます。
tearDown( −クラス内のすべてのテストを実行した後、クラスフィクスチャを分解するためのフックメソッド。
テストスイート
これは、テストスイート、テストケース、またはその両方のコレクションです。
テストランナー
テストケースまたはスーツの実行を制御し、ユーザーに結果を提供します。結果を提供するためにGUIまたは単純なテキストインターフェイスを使用する場合があります。
Example
次のPythonプログラムは、unittestモジュールを使用して、という名前のモジュールをテストします。 Fibonacci。このプログラムは、数値のフィボナッチ数列を計算するのに役立ちます。この例では、さまざまなメソッドを使用してテストケースを定義するために、Fibo_testという名前のクラスを作成しました。これらのメソッドは、unittest.TestCaseから継承されます。デフォルトでは、setUp()とtearDown()の2つのメソッドを使用しています。また、testfibocalメソッドを定義します。テストの名前は、文字テストで開始する必要があります。最後のブロックで、unittest.main()はテストスクリプトへのコマンドラインインターフェイスを提供します。
import unittest
def fibonacci(n):
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a
class Fibo_Test(unittest.TestCase):
def setUp(self):
print("This is run before our tests would be executed")
def tearDown(self):
print("This is run after the completion of execution of our tests")
def testfibocal(self):
self.assertEqual(fib(0), 0)
self.assertEqual(fib(1), 1)
self.assertEqual(fib(5), 5)
self.assertEqual(fib(10), 55)
self.assertEqual(fib(20), 6765)
if __name__ == "__main__":
unittest.main()
コマンドラインから実行すると、上記のスクリプトは次のような出力を生成します-
出力
This runs before our tests would be executed.
This runs after the completion of execution of our tests.
.
----------------------------------------------------------------------
Ran 1 test in 0.006s
OK
わかりやすくするために、フィボナッチモジュールの定義に役立つコードを変更します。
例として次のコードブロックを考えてみましょう-
def fibonacci(n):
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a
以下に示すように、コードブロックにいくつかの変更が加えられています。
def fibonacci(n):
a, b = 1, 1
for i in range(n):
a, b = b, a + b
return a
変更したコードでスクリプトを実行すると、次の出力が得られます。
This runs before our tests would be executed.
This runs after the completion of execution of our tests.
F
======================================================================
FAIL: testCalculation (__main__.Fibo_Test)
----------------------------------------------------------------------
Traceback (most recent call last):
File "unitg.py", line 15, in testCalculation
self.assertEqual(fib(0), 0)
AssertionError: 1 != 0
----------------------------------------------------------------------
Ran 1 test in 0.007s
FAILED (failures = 1)
上記の出力は、モジュールが目的の出力を提供できなかったことを示しています。
ドックテストモジュール
docktestモジュールは、ユニットテストにも役立ちます。また、Pythonがあらかじめパッケージ化されています。unittestモジュールよりも使いやすいです。unittestモジュールは、複雑なテストに適しています。doctestモジュールを使用するには、それをインポートする必要があります。対応する関数のdocstringには、出力とともにインタラクティブなpythonセッションが必要です。
コードですべてが正常であれば、docktestモジュールからの出力はありません。それ以外の場合は、出力を提供します。
例
次のPythonの例では、docktestモジュールを使用して、フィボナッチという名前のモジュールをテストします。これは、数値のフィボナッチ数列の計算に役立ちます。
import doctest
def fibonacci(n):
"""
Calculates the Fibonacci number
>>> fibonacci(0)
0
>>> fibonacci(1)
1
>>> fibonacci(10)
55
>>> fibonacci(20)
6765
>>>
"""
a, b = 1, 1
for i in range(n):
a, b = b, a + b
return a
if __name__ == "__main__":
doctest.testmod()
fibという名前の対応する関数のdocstringには、出力とともにインタラクティブなpythonセッションがあったことがわかります。コードに問題がなければ、doctestモジュールからの出力はありません。しかし、それがどのように機能するかを確認するには、–vオプションを使用して実行できます。
(base) D:\ProgramData>python dock_test.py -v
Trying:
fibonacci(0)
Expecting:
0
ok
Trying:
fibonacci(1)
Expecting:
1
ok
Trying:
fibonacci(10)
Expecting:
55
ok
Trying:
fibonacci(20)
Expecting:
6765
ok
1 items had no tests:
__main__
1 items passed all tests:
4 tests in __main__.fibonacci
4 tests in 2 items.
4 passed and 0 failed.
Test passed.
ここで、フィボナッチモジュールの定義に役立つコードを変更します
例として次のコードブロックを考えてみましょう-
def fibonacci(n):
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a
次のコードブロックは変更に役立ちます-
def fibonacci(n):
a, b = 1, 1
for i in range(n):
a, b = b, a + b
return a
–vオプションを指定せずにスクリプトを実行した後、コードを変更すると、次のような出力が得られます。
出力
(base) D:\ProgramData>python dock_test.py
**********************************************************************
File "unitg.py", line 6, in __main__.fibonacci
Failed example:
fibonacci(0)
Expected:
0
Got:
1
**********************************************************************
File "unitg.py", line 10, in __main__.fibonacci
Failed example:
fibonacci(10)
Expected:
55
Got:
89
**********************************************************************
File "unitg.py", line 12, in __main__.fibonacci
Failed example:
fibonacci(20)
Expected:
6765
Got:
10946
**********************************************************************
1 items had failures:
3 of 4 in __main__.fibonacci
***Test Failed*** 3 failures.
上記の出力から、3つのテストが失敗したことがわかります。
この章では、スレッドアプリケーションをデバッグする方法を学習します。また、デバッグの重要性についても学びます。
デバッグとは何ですか?
コンピュータプログラミングでは、デバッグとは、コンピュータプログラムからバグ、エラー、異常を見つけて削除するプロセスです。このプロセスは、コードが記述されるとすぐに開始され、コードが他のプログラミング単位と組み合わされてソフトウェア製品を形成するまで、次の段階に続きます。デバッグはソフトウェアテストプロセスの一部であり、ソフトウェア開発ライフサイクル全体の不可欠な部分です。
Pythonデバッガー
Pythonデバッガーまたは pdbPython標準ライブラリの一部です。これは、見つけにくいバグを追跡するための優れたフォールバックツールであり、障害のあるコードを迅速かつ確実に修正することができます。以下は、の2つの最も重要なタスクです。pdp デバッガー-
- これにより、実行時に変数の値を確認できます。
- コードをステップスルーして、ブレークポイントを設定することもできます。
次の2つの方法でpdbを操作できます-
- コマンドラインから; これは、事後デバッグとも呼ばれます。
- pdbをインタラクティブに実行する。
pdbの操作
Pythonデバッガーを操作するには、デバッガーに侵入する場所で次のコードを使用する必要があります-
import pdb;
pdb.set_trace()
コマンドラインからpdbを操作するには、次のコマンドを検討してください。
- h(help)
- d(down)
- u(up)
- b(break)
- cl(clear)
- l(list))
- n(next))
- c(continue)
- s(step)
- r(return))
- b(break)
以下は、Pythonデバッガーのh(help)コマンドのデモです。
import pdb
pdb.set_trace()
--Call--
>d:\programdata\lib\site-packages\ipython\core\displayhook.py(247)__call__()
-> def __call__(self, result = None):
(Pdb) h
Documented commands (type help <topic>):
========================================
EOF c d h list q rv undisplay
a cl debug help ll quit s unt
alias clear disable ignore longlist r source until
args commands display interact n restart step up
b condition down j next return tbreak w
break cont enable jump p retval u whatis
bt continue exit l pp run unalias where
Miscellaneous help topics:
==========================
exec pdb
例
Pythonデバッガーを使用しているときに、次の行を使用して、スクリプトの任意の場所にブレークポイントを設定できます。
import pdb;
pdb.set_trace()
ブレークポイントを設定すると、スクリプトを正常に実行できます。スクリプトは特定の時点まで実行されます。行が設定されるまで。スクリプトのさまざまな場所で上記の行を使用してスクリプトを実行する次の例について考えてみます。
import pdb;
a = "aaa"
pdb.set_trace()
b = "bbb"
c = "ccc"
final = a + b + c
print (final)
上記のスクリプトを実行すると、a =“ aaa”になるまでプログラムが実行されます。これは、次の出力で確認できます。
出力
--Return--
> <ipython-input-7-8a7d1b5cc854>(3)<module>()->None
-> pdb.set_trace()
(Pdb) p a
'aaa'
(Pdb) p b
*** NameError: name 'b' is not defined
(Pdb) p c
*** NameError: name 'c' is not defined
pdbでコマンド「p(print)」を使用した後、このスクリプトは「aaa」のみを出力します。a = "aaa"までブレークポイントを設定したため、この後にエラーが発生します。
同様に、ブレークポイントを変更してスクリプトを実行し、出力の違いを確認できます-
import pdb
a = "aaa"
b = "bbb"
c = "ccc"
pdb.set_trace()
final = a + b + c
print (final)
出力
--Return--
> <ipython-input-9-a59ef5caf723>(5)<module>()->None
-> pdb.set_trace()
(Pdb) p a
'aaa'
(Pdb) p b
'bbb'
(Pdb) p c
'ccc'
(Pdb) p final
*** NameError: name 'final' is not defined
(Pdb) exit
次のスクリプトでは、プログラムの最後の行にブレークポイントを設定しています-
import pdb
a = "aaa"
b = "bbb"
c = "ccc"
final = a + b + c
pdb.set_trace()
print (final)
出力は次のとおりです-
--Return--
> <ipython-input-11-8019b029997d>(6)<module>()->None
-> pdb.set_trace()
(Pdb) p a
'aaa'
(Pdb) p b
'bbb'
(Pdb) p c
'ccc'
(Pdb) p final
'aaabbbccc'
(Pdb)
この章では、ベンチマークとプロファイリングがパフォーマンスの問題に対処するのにどのように役立つかを学びます。
コードを記述し、それでも目的の結果が得られたとしますが、ニーズが変わったためにこのコードを少し速く実行したい場合はどうでしょうか。この場合、コードのどの部分がプログラム全体の速度を低下させているかを調べる必要があります。この場合、ベンチマークとプロファイリングが役立ちます。
ベンチマークとは何ですか?
ベンチマークは、標準と比較して何かを評価することを目的としています。ただし、ここで発生する問題は、ベンチマークとは何か、ソフトウェアプログラミングの場合にベンチマークが必要な理由です。コードのベンチマークとは、コードの実行速度とボトルネックの場所を意味します。ベンチマークの主な理由の1つは、コードを最適化することです。
ベンチマークはどのように機能しますか?
ベンチマークの動作について話す場合、プログラム全体を1つの現在の状態としてベンチマークすることから始める必要があります。次に、マイクロベンチマークを組み合わせて、プログラムをより小さなプログラムに分解できます。プログラム内のボトルネックを見つけて最適化するため。言い換えれば、大きくて難しい問題を、それらを最適化するための一連の小さくて少し簡単な問題に分割することとして理解できます。
ベンチマーク用のPythonモジュール
Pythonには、ベンチマーク用のデフォルトのモジュールがあります。 timeit。の助けを借りてtimeit モジュールでは、メインプログラム内のPythonコードの小さなビットのパフォーマンスを測定できます。
例
次のPythonスクリプトでは、 timeit モジュール。2つの機能の実行にかかる時間をさらに測定します– functionA そして functionB −
import timeit
import time
def functionA():
print("Function A starts the execution:")
print("Function A completes the execution:")
def functionB():
print("Function B starts the execution")
print("Function B completes the execution")
start_time = timeit.default_timer()
functionA()
print(timeit.default_timer() - start_time)
start_time = timeit.default_timer()
functionB()
print(timeit.default_timer() - start_time)
上記のスクリプトを実行した後、以下に示すように両方の関数の実行時間を取得します。
出力
Function A starts the execution:
Function A completes the execution:
0.0014599495514175942
Function B starts the execution
Function B completes the execution
0.0017024724827479076
デコレータ関数を使用して独自のタイマーを作成する
Pythonでは、独自のタイマーを作成できます。これは、 timeitモジュール。それはの助けを借りて行うことができますdecorator関数。以下はカスタムタイマーの例です-
import random
import time
def timer_func(func):
def function_timer(*args, **kwargs):
start = time.time()
value = func(*args, **kwargs)
end = time.time()
runtime = end - start
msg = "{func} took {time} seconds to complete its execution."
print(msg.format(func = func.__name__,time = runtime))
return value
return function_timer
@timer_func
def Myfunction():
for x in range(5):
sleep_time = random.choice(range(1,3))
time.sleep(sleep_time)
if __name__ == '__main__':
Myfunction()
上記のPythonスクリプトは、ランダムな時間モジュールのインポートに役立ちます。timer_func()デコレータ関数を作成しました。これには、function_timer()関数が含まれています。これで、ネストされた関数は、渡された関数を呼び出す前に時間を取得します。次に、関数が戻るのを待ち、終了時刻を取得します。このようにして、最終的にPythonスクリプトに実行時間を出力させることができます。スクリプトは、以下に示すように出力を生成します。
出力
Myfunction took 8.000457763671875 seconds to complete its execution.
プロファイリングとは何ですか?
プログラマーは、メモリの使用、時間の複雑さ、プログラムに関する特定の命令の使用などの属性を測定して、そのプログラムの実際の機能を測定したい場合があります。このようなプログラムに関する測定は、プロファイリングと呼ばれます。プロファイリングは、動的プログラム分析を使用してそのような測定を行います。
以降のセクションでは、プロファイリング用のさまざまなPythonモジュールについて学習します。
cProfile –組み込みモジュール
cProfileプロファイリング用のPython組み込みモジュールです。このモジュールは、長時間実行されるプログラムのプロファイリングに適した妥当なオーバーヘッドを備えたC拡張機能です。実行後、すべての関数と実行時間をログに記録します。それは非常に強力ですが、解釈して行動するのが少し難しい場合があります。次の例では、以下のコードでcProfileを使用しています-
例
def increment_global():
global x
x += 1
def taskofThread(lock):
for _ in range(50000):
lock.acquire()
increment_global()
lock.release()
def main():
global x
x = 0
lock = threading.Lock()
t1 = threading.Thread(target=taskofThread, args=(lock,))
t2 = threading.Thread(target= taskofThread, args=(lock,))
t1.start()
t2.start()
t1.join()
t2.join()
if __name__ == "__main__":
for i in range(5):
main()
print("x = {1} after Iteration {0}".format(i,x))
上記のコードはに保存されます thread_increment.pyファイル。ここで、次のようにコマンドラインでcProfileを使用してコードを実行します-
(base) D:\ProgramData>python -m cProfile thread_increment.py
x = 100000 after Iteration 0
x = 100000 after Iteration 1
x = 100000 after Iteration 2
x = 100000 after Iteration 3
x = 100000 after Iteration 4
3577 function calls (3522 primitive calls) in 1.688 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:103(release)
5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:143(__init__)
5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:147(__enter__)
… … … …
上記の出力から、cProfileは、呼び出された3577個の関数すべてを、それぞれに費やされた時間と呼び出された回数とともに出力することが明らかです。以下は、出力で取得した列です-
ncalls −これは発信された呼び出しの数です。
tottime −指定された関数で費やされた合計時間です。
percall −これはtottimeをncallで割った商を指します。
cumtime−これは、このサブ機能とすべてのサブ機能に費やされた累積時間です。再帰関数に対しても正確です。
percall −これはcumtimeをプリミティブ呼び出しで割った商です。
filename:lineno(function) −基本的に各機能のそれぞれのデータを提供します。
マルチスレッドタスク用に多数のスレッドを作成する必要があるとします。スレッドが多すぎるためにパフォーマンスの問題が多数発生する可能性があるため、計算コストが最も高くなります。主な問題は、スループットが制限されることである可能性があります。スレッドのプールを作成することで、この問題を解決できます。スレッドプールは、事前にインスタンス化されたアイドル状態のスレッドのグループとして定義できます。これらのスレッドは、作業を開始する準備ができています。多数のタスクを実行する必要がある場合は、すべてのタスクに対して新しいスレッドをインスタンス化するよりも、スレッドプールを作成することをお勧めします。スレッドプールは、次のように多数のスレッドの同時実行を管理できます。
スレッドプール内のスレッドが実行を完了すると、そのスレッドを再利用できます。
スレッドが終了すると、そのスレッドを置き換えるために別のスレッドが作成されます。
Pythonモジュール– Concurrent.futures
Python標準ライブラリには concurrent.futuresモジュール。このモジュールは、非同期タスクを起動するための高レベルのインターフェースを開発者に提供するためにPython3.2で追加されました。これは、スレッドまたはプロセスのプールを使用してタスクを実行するためのインターフェイスを提供するための、Pythonのスレッド化およびマルチプロセッシングモジュールの上部にある抽象化レイヤーです。
以降のセクションでは、concurrent.futuresモジュールのさまざまなクラスについて学習します。
エグゼキュータクラス
Executorの抽象クラスです concurrent.futuresPythonモジュール。直接使用することはできず、次の具体的なサブクラスのいずれかを使用する必要があります-
- ThreadPoolExecutor
- ProcessPoolExecutor
ThreadPoolExecutor –具象サブクラス
これは、Executorクラスの具体的なサブクラスの1つです。サブクラスはマルチスレッドを使用し、タスクを送信するためのスレッドのプールを取得します。このプールは、使用可能なスレッドにタスクを割り当て、実行するようにスケジュールします。
ThreadPoolExecutorを作成する方法は?
の助けを借りて concurrent.futures モジュールとその具体的なサブクラス Executor、スレッドのプールを簡単に作成できます。このために、私たちは構築する必要がありますThreadPoolExecutorプールに必要なスレッドの数で。デフォルトでは、番号は5です。次に、タスクをスレッドプールに送信できます。私たちがsubmit() タスク、私たちは戻ってきます Future。Futureオブジェクトには、というメソッドがありますdone()、未来が解決したかどうかを示します。これにより、その特定の将来のオブジェクトに値が設定されました。タスクが終了すると、スレッドプールエグゼキュータは値をfutureオブジェクトに設定します。
例
from concurrent.futures import ThreadPoolExecutor
from time import sleep
def task(message):
sleep(2)
return message
def main():
executor = ThreadPoolExecutor(5)
future = executor.submit(task, ("Completed"))
print(future.done())
sleep(2)
print(future.done())
print(future.result())
if __name__ == '__main__':
main()
出力
False
True
Completed
上記の例では、 ThreadPoolExecutor5つのスレッドで構成されています。次に、メッセージを送信する前に2秒間待機するタスクが、スレッドプールエグゼキュータに送信されます。出力からわかるように、タスクは2秒まで完了しないため、最初の呼び出しはdone()Falseを返します。2秒後、タスクが完了し、を呼び出すことで将来の結果を取得しますresult() その上でメソッド。
ThreadPoolExecutorのインスタンス化–コンテキストマネージャー
インスタンス化する別の方法 ThreadPoolExecutorコンテキストマネージャーの助けを借りています。上記の例で使用した方法と同様に機能します。コンテキストマネージャを使用する主な利点は、構文的に見栄えがよいことです。インスタンス化は、次のコードを使用して実行できます-
with ThreadPoolExecutor(max_workers = 5) as executor
例
次の例は、Pythonドキュメントから借用したものです。この例では、まず最初にconcurrent.futuresモジュールをインポートする必要があります。次に、という名前の関数load_url()要求されたURLをロードするが作成されます。次に、関数は作成しますThreadPoolExecutorプール内の5つのスレッドで。ザ・ThreadPoolExecutorコンテキストマネージャーとして利用されています。と呼ぶことで未来の結果を得ることができますresult() その上でメソッド。
import concurrent.futures
import urllib.request
URLS = ['http://www.foxnews.com/',
'http://www.cnn.com/',
'http://europe.wsj.com/',
'http://www.bbc.co.uk/',
'http://some-made-up-domain.com/']
def load_url(url, timeout):
with urllib.request.urlopen(url, timeout = timeout) as conn:
return conn.read()
with concurrent.futures.ThreadPoolExecutor(max_workers = 5) as executor:
future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
data = future.result()
except Exception as exc:
print('%r generated an exception: %s' % (url, exc))
else:
print('%r page is %d bytes' % (url, len(data)))
出力
以下は、上記のPythonスクリプトの出力です-
'http://some-made-up-domain.com/' generated an exception: <urlopen error [Errno 11004] getaddrinfo failed>
'http://www.foxnews.com/' page is 229313 bytes
'http://www.cnn.com/' page is 168933 bytes
'http://www.bbc.co.uk/' page is 283893 bytes
'http://europe.wsj.com/' page is 938109 bytes
Executor.map()関数の使用
Python map()関数は多くのタスクで広く使用されています。そのようなタスクの1つは、反復可能オブジェクト内のすべての要素に特定の関数を適用することです。同様に、イテレータのすべての要素を関数にマップし、これらを独立したジョブとして送信できます。ThreadPoolExecutor。関数がどのように機能するかを理解するために、次のPythonスクリプトの例を検討してください。
例
以下のこの例では、map関数を使用して square() 値配列内のすべての値に対して関数。
from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import as_completed
values = [2,3,4,5]
def square(n):
return n * n
def main():
with ThreadPoolExecutor(max_workers = 3) as executor:
results = executor.map(square, values)
for result in results:
print(result)
if __name__ == '__main__':
main()
出力
上記のPythonスクリプトは、次の出力を生成します-
4
9
16
25
プロセスのプールは、スレッドのプールを作成して使用したのと同じ方法で作成して使用できます。プロセスプールは、事前にインスタンス化されたアイドル状態のプロセスのグループとして定義できます。これらのプロセスは、作業を開始する準備ができています。多数のタスクを実行する必要がある場合は、すべてのタスクに対して新しいプロセスをインスタンス化するよりも、プロセスプールを作成することをお勧めします。
Pythonモジュール– Concurrent.futures
Python標準ライブラリには、 concurrent.futures。このモジュールは、非同期タスクを起動するための高レベルのインターフェースを開発者に提供するためにPython3.2で追加されました。これは、スレッドまたはプロセスのプールを使用してタスクを実行するためのインターフェイスを提供するための、Pythonのスレッド化およびマルチプロセッシングモジュールの上部にある抽象化レイヤーです。
以降のセクションでは、concurrent.futuresモジュールのさまざまなサブクラスについて説明します。
エグゼキュータクラス
Executor の抽象クラスです concurrent.futuresPythonモジュール。直接使用することはできず、次の具体的なサブクラスのいずれかを使用する必要があります-
- ThreadPoolExecutor
- ProcessPoolExecutor
ProcessPoolExecutor –具体的なサブクラス
これは、Executorクラスの具体的なサブクラスの1つです。マルチプロセッシングを使用し、タスクを送信するためのプロセスのプールを取得します。このプールは、使用可能なプロセスにタスクを割り当て、実行するようにスケジュールします。
ProcessPoolExecutorを作成する方法は?
の助けを借りて concurrent.futures モジュールとその具体的なサブクラス Executor、プロセスのプールを簡単に作成できます。このために、私たちは構築する必要がありますProcessPoolExecutorプールに必要なプロセスの数で。デフォルトでは、番号は5です。これに続いて、プロセスプールにタスクが送信されます。
例
ここで、スレッドプールの作成時に使用したのと同じ例を検討しますが、唯一の違いは、これから使用することです。 ProcessPoolExecutor の代わりに ThreadPoolExecutor 。
from concurrent.futures import ProcessPoolExecutor
from time import sleep
def task(message):
sleep(2)
return message
def main():
executor = ProcessPoolExecutor(5)
future = executor.submit(task, ("Completed"))
print(future.done())
sleep(2)
print(future.done())
print(future.result())
if __name__ == '__main__':
main()
出力
False
False
Completed
上記の例では、プロセスPoolExecutor5つのスレッドで構成されています。次に、メッセージを送信する前に2秒間待機するタスクが、プロセスプールエグゼキュータに送信されます。出力からわかるように、タスクは2秒まで完了しないため、最初の呼び出しはdone()Falseを返します。2秒後、タスクが完了し、を呼び出すことで将来の結果を取得しますresult() その上でメソッド。
ProcessPoolExecutorのインスタンス化–コンテキストマネージャー
ProcessPoolExecutorをインスタンス化する別の方法は、コンテキストマネージャーを使用することです。上記の例で使用した方法と同様に機能します。コンテキストマネージャを使用する主な利点は、構文的に見栄えがよいことです。インスタンス化は、次のコードを使用して実行できます-
with ProcessPoolExecutor(max_workers = 5) as executor
例
理解を深めるために、スレッドプールの作成時に使用したのと同じ例を取り上げています。この例では、まずインポートする必要がありますconcurrent.futuresモジュール。次に、という名前の関数load_url()要求されたURLをロードするが作成されます。ザ・ProcessPoolExecutor次に、プール内の5つのスレッド数で作成されます。プロセスPoolExecutorコンテキストマネージャーとして利用されています。と呼ぶことで未来の結果を得ることができますresult() その上でメソッド。
import concurrent.futures
from concurrent.futures import ProcessPoolExecutor
import urllib.request
URLS = ['http://www.foxnews.com/',
'http://www.cnn.com/',
'http://europe.wsj.com/',
'http://www.bbc.co.uk/',
'http://some-made-up-domain.com/']
def load_url(url, timeout):
with urllib.request.urlopen(url, timeout = timeout) as conn:
return conn.read()
def main():
with concurrent.futures.ProcessPoolExecutor(max_workers=5) as executor:
future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
data = future.result()
except Exception as exc:
print('%r generated an exception: %s' % (url, exc))
else:
print('%r page is %d bytes' % (url, len(data)))
if __name__ == '__main__':
main()
出力
上記のPythonスクリプトは、次の出力を生成します-
'http://some-made-up-domain.com/' generated an exception: <urlopen error [Errno 11004] getaddrinfo failed>
'http://www.foxnews.com/' page is 229476 bytes
'http://www.cnn.com/' page is 165323 bytes
'http://www.bbc.co.uk/' page is 284981 bytes
'http://europe.wsj.com/' page is 967575 bytes
Executor.map()関数の使用
Python map()関数は、多くのタスクを実行するために広く使用されています。そのようなタスクの1つは、反復可能オブジェクト内のすべての要素に特定の関数を適用することです。同様に、イテレータのすべての要素を関数にマップし、これらを独立したジョブとしてProcessPoolExecutor。これを理解するために、次のPythonスクリプトの例を検討してください。
例
を使用してスレッドプールを作成するときに使用したのと同じ例を検討します。 Executor.map()関数。以下の例では、map関数を使用して適用していますsquare() 値配列内のすべての値に対して関数。
from concurrent.futures import ProcessPoolExecutor
from concurrent.futures import as_completed
values = [2,3,4,5]
def square(n):
return n * n
def main():
with ProcessPoolExecutor(max_workers = 3) as executor:
results = executor.map(square, values)
for result in results:
print(result)
if __name__ == '__main__':
main()
出力
上記のPythonスクリプトは、次の出力を生成します
4
9
16
25
ProcessPoolExecutorとThreadPoolExecutorをいつ使用するのですか?
ThreadPoolExecutorとProcessPoolExecutorの両方のエグゼキュータークラスについて学習したので、どちらのエグゼキューターをいつ使用するかを知る必要があります。CPUバウンドワークロードの場合はProcessPoolExecutorを選択し、I / Oバウンドワークロードの場合はThreadPoolExecutorを選択する必要があります。
使用する場合 ProcessPoolExecutor、マルチプロセッシングを使用しているため、GILについて心配する必要はありません。さらに、実行時間は比較すると短くなりますThreadPoolExecution。これを理解するには、次のPythonスクリプトの例を検討してください。
例
import time
import concurrent.futures
value = [8000000, 7000000]
def counting(n):
start = time.time()
while n > 0:
n -= 1
return time.time() - start
def main():
start = time.time()
with concurrent.futures.ProcessPoolExecutor() as executor:
for number, time_taken in zip(value, executor.map(counting, value)):
print('Start: {} Time taken: {}'.format(number, time_taken))
print('Total time taken: {}'.format(time.time() - start))
if __name__ == '__main__':
main()
出力
Start: 8000000 Time taken: 1.5509998798370361
Start: 7000000 Time taken: 1.3259999752044678
Total time taken: 2.0840001106262207
Example- Python script with ThreadPoolExecutor:
import time
import concurrent.futures
value = [8000000, 7000000]
def counting(n):
start = time.time()
while n > 0:
n -= 1
return time.time() - start
def main():
start = time.time()
with concurrent.futures.ThreadPoolExecutor() as executor:
for number, time_taken in zip(value, executor.map(counting, value)):
print('Start: {} Time taken: {}'.format(number, time_taken))
print('Total time taken: {}'.format(time.time() - start))
if __name__ == '__main__':
main()
出力
Start: 8000000 Time taken: 3.8420000076293945
Start: 7000000 Time taken: 3.6010000705718994
Total time taken: 3.8480000495910645
上記の両方のプログラムの出力から、使用中の実行時間の違いを確認できます。 ProcessPoolExecutor そして ThreadPoolExecutor。
この章では、マルチプロセッシングとマルチスレッドの比較に焦点を当てます。
マルチプロセッシング
これは、単一のコンピューターシステム内で2つ以上のCPUユニットを使用することです。コンピュータシステムで利用可能なCPUコアの全数を利用して、ハードウェアから最大限の可能性を引き出すことが最善のアプローチです。
マルチスレッド
これは、複数のスレッドを同時に実行することによってオペレーティングシステムの使用を管理するCPUの機能です。マルチスレッドの主なアイデアは、プロセスを複数のスレッドに分割することによって並列処理を実現することです。
次の表は、それらの間の重要な違いのいくつかを示しています-
マルチプロセッシング | マルチプログラミング |
---|---|
マルチプロセッシングとは、複数のCPUが同時に複数のプロセスを処理することです。 | マルチプログラミングは、複数のプログラムを同時にメインメモリに保持し、単一のCPUを使用してそれらを同時に実行します。 |
複数のCPUを利用します。 | シングルCPUを使用しています。 |
並列処理が可能です。 | コンテキストの切り替えが行われます。 |
ジョブの処理にかかる時間が短縮されます。 | ジョブの処理にかかる時間が長くなります。 |
それはコンピュータシステムのデバイスの非常に効率的な利用を容易にします。 | マルチプロセッシングよりも効率が悪い。 |
通常はより高価です。 | そのようなシステムはより安価です。 |
グローバルインタプリタロック(GIL)の影響を排除する
並行アプリケーションで作業している間、Pythonには次のような制限があります。 GIL (Global Interpreter Lock)。GILではCPUの複数のコアを利用することはできません。したがって、Pythonには真のスレッドはないと言えます。GILはミューテックス–相互排他ロックであり、スレッドセーフになります。言い換えれば、GILは複数のスレッドがPythonコードを並行して実行することを防いでいると言えます。ロックは一度に1つのスレッドのみが保持でき、スレッドを実行する場合は、最初にロックを取得する必要があります。
マルチプロセッシングを使用することで、GIL-によって引き起こされる制限を効果的に回避できます。
マルチプロセッシングを使用することで、複数のプロセスの機能を利用しているため、GILの複数のインスタンスを利用しています。
このため、プログラム内の1つのスレッドのバイトコードを一度に実行するという制限はありません。
Pythonでプロセスを開始する
次の3つの方法を使用して、マルチプロセッシングモジュール内でPythonでプロセスを開始できます。
- Fork
- Spawn
- Forkserver
Forkを使用したプロセスの作成
Forkコマンドは、UNIXにある標準コマンドです。子プロセスと呼ばれる新しいプロセスを作成するために使用されます。この子プロセスは、親プロセスと呼ばれるプロセスと同時に実行されます。これらの子プロセスも親プロセスと同一であり、親が使用できるすべてのリソースを継承します。Fork −を使用してプロセスを作成する際に、次のシステムコールが使用されます。
fork()−一般的にカーネルに実装されているシステムコールです。プロセスのコピーを作成するために使用されます。p>
getpid() −このシステムコールは、呼び出し元のプロセスのプロセスID(PID)を返します。
例
次のPythonスクリプトの例は、新しい子プロセスを作成し、子プロセスと親プロセスのPIDを取得する方法を理解するのに役立ちます-
import os
def child():
n = os.fork()
if n > 0:
print("PID of Parent process is : ", os.getpid())
else:
print("PID of Child process is : ", os.getpid())
child()
出力
PID of Parent process is : 25989
PID of Child process is : 25990
Spawnを使用したプロセスの作成
スポーンとは、何か新しいことを始めることを意味します。したがって、プロセスの生成とは、親プロセスによる新しいプロセスの作成を意味します。親プロセスは非同期で実行を継続するか、子プロセスが実行を終了するまで待機します。プロセスを生成するには、次の手順に従います-
マルチプロセッシングモジュールのインポート。
オブジェクトプロセスの作成。
呼び出してプロセスアクティビティを開始する start() 方法。
プロセスが作業を終了するまで待機し、を呼び出して終了します join() 方法。
例
次のPythonスクリプトの例は、3つのプロセスを生成するのに役立ちます
import multiprocessing
def spawn_process(i):
print ('This is process: %s' %i)
return
if __name__ == '__main__':
Process_jobs = []
for i in range(3):
p = multiprocessing.Process(target = spawn_process, args = (i,))
Process_jobs.append(p)
p.start()
p.join()
出力
This is process: 0
This is process: 1
This is process: 2
Forkserverを使用したプロセスの作成
Forkserverメカニズムは、Unixパイプを介したファイル記述子の受け渡しをサポートする選択されたUNIXプラットフォームでのみ使用できます。Forkserverメカニズムの動作を理解するには、次の点を考慮してください。
サーバーは、新しいプロセスを開始するためにForkserverメカニズムを使用してインスタンス化されます。
次に、サーバーはコマンドを受信し、新しいプロセスを作成するためのすべての要求を処理します。
新しいプロセスを作成するために、PythonプログラムがForkserverにリクエストを送信し、プロセスを作成します。
最後に、この新しく作成されたプロセスをプログラムで使用できます。
Pythonのデーモンプロセス
Python multiprocessingモジュールを使用すると、デーモンオプションを介してデーモンプロセスを使用できます。デーモンプロセスまたはバックグラウンドで実行されているプロセスは、デーモンスレッドと同様の概念に従います。プロセスをバックグラウンドで実行するには、デーモンフラグをtrueに設定する必要があります。デーモンプロセスは、メインプロセスが実行されている限り実行を継続し、実行が終了した後、またはメインプログラムが強制終了されたときに終了します。
例
ここでは、デーモンスレッドで使用されているのと同じ例を使用しています。唯一の違いは、からのモジュールの変更です。multithreading に multiprocessingデーモンフラグをtrueに設定します。ただし、以下に示すように出力に変化があります。
import multiprocessing
import time
def nondaemonProcess():
print("starting my Process")
time.sleep(8)
print("ending my Process")
def daemonProcess():
while True:
print("Hello")
time.sleep(2)
if __name__ == '__main__':
nondaemonProcess = multiprocessing.Process(target = nondaemonProcess)
daemonProcess = multiprocessing.Process(target = daemonProcess)
daemonProcess.daemon = True
nondaemonProcess.daemon = False
daemonProcess.start()
nondaemonProcess.start()
出力
starting my Process
ending my Process
デーモンモードのプロセスには出力がないため、デーモンスレッドによって生成されたものと比較すると出力が異なります。したがって、実行中のプロセスの永続性を回避するために、メインプログラムが終了した後、デーモンプロセスは自動的に終了します。
Pythonでプロセスを終了する
を使用して、プロセスをすぐに強制終了または終了できます。 terminate()方法。このメソッドを使用して、関数を使用して作成された子プロセスを、実行が完了する直前に終了します。
例
import multiprocessing
import time
def Child_process():
print ('Starting function')
time.sleep(5)
print ('Finished function')
P = multiprocessing.Process(target = Child_process)
P.start()
print("My Process has terminated, terminating main thread")
print("Terminating Child Process")
P.terminate()
print("Child Process successfully terminated")
出力
My Process has terminated, terminating main thread
Terminating Child Process
Child Process successfully terminated
出力は、Child_process()関数を使用して作成された子プロセスの実行前にプログラムが終了することを示しています。これは、子プロセスが正常に終了したことを意味します。
Pythonで現在のプロセスを特定する
オペレーティングシステムのすべてのプロセスには、PIDと呼ばれるプロセスIDがあります。Pythonでは、次のコマンドを使用して現在のプロセスのPIDを見つけることができます-
import multiprocessing
print(multiprocessing.current_process().pid)
例
次のPythonスクリプトの例は、メインプロセスのPIDと子プロセスのPIDを見つけるのに役立ちます-
import multiprocessing
import time
def Child_process():
print("PID of Child Process is: {}".format(multiprocessing.current_process().pid))
print("PID of Main process is: {}".format(multiprocessing.current_process().pid))
P = multiprocessing.Process(target=Child_process)
P.start()
P.join()
出力
PID of Main process is: 9401
PID of Child Process is: 9402
サブクラスでプロセスを使用する
サブクラス化することでスレッドを作成できます threading.Threadクラス。さらに、サブクラス化することでプロセスを作成することもできますmultiprocessing.Processクラス。サブクラスでプロセスを使用するには、次の点を考慮する必要があります。
の新しいサブクラスを定義する必要があります Process クラス。
オーバーライドする必要があります _init_(self [,args] ) クラス。
のをオーバーライドする必要があります run(self [,args] ) 何を実装する方法 Process
を呼び出してプロセスを開始する必要がありますstart() 方法。
例
import multiprocessing
class MyProcess(multiprocessing.Process):
def run(self):
print ('called run method in process: %s' %self.name)
return
if __name__ == '__main__':
jobs = []
for i in range(5):
P = MyProcess()
jobs.append(P)
P.start()
P.join()
出力
called run method in process: MyProcess-1
called run method in process: MyProcess-2
called run method in process: MyProcess-3
called run method in process: MyProcess-4
called run method in process: MyProcess-5
Pythonマルチプロセッシングモジュール–プールクラス
単純な並列について話す場合 processingPythonアプリケーションのタスク、次にマルチプロセッシングモジュールがPoolクラスを提供します。次の方法Pool クラスは、メインプログラム内の子プロセスの数をスピンアップするために使用できます
apply()メソッド
この方法は、.submit()の方法 .ThreadPoolExecutor.結果の準備ができるまでブロックします。
apply_async()メソッド
タスクの並列実行が必要な場合は、apply_async()タスクをプールに送信するメソッド。これは、すべての子プロセスが実行されるまでメインスレッドをロックしない非同期操作です。
map()メソッド
のように apply()メソッドでは、結果の準備ができるまでブロックします。ビルトインと同等ですmap() 反復可能なデータをいくつかのチャンクに分割し、個別のタスクとしてプロセスプールに送信する関数。
map_async()メソッド
それはの変種です map() としての方法 apply_async() にあります apply()方法。結果オブジェクトを返します。結果の準備が整うと、callableが適用されます。呼び出し可能オブジェクトはすぐに完了する必要があります。そうしないと、結果を処理するスレッドがブロックされます。
例
次の例は、並列実行を実行するためのプロセスプールを実装するのに役立ちます。数の二乗の簡単な計算は、square() を通じて機能する multiprocessing.Pool方法。次にpool.map() 入力は0から4までの整数のリストであるため、5を送信するために使用されています。結果はに格納されます。 p_outputs そしてそれは印刷されます。
def square(n):
result = n*n
return result
if __name__ == '__main__':
inputs = list(range(5))
p = multiprocessing.Pool(processes = 4)
p_outputs = pool.map(function_square, inputs)
p.close()
p.join()
print ('Pool :', p_outputs)
出力
Pool : [0, 1, 4, 9, 16]
プロセス相互通信とは、プロセス間のデータ交換を意味します。並列アプリケーションの開発には、プロセス間でデータを交換する必要があります。次の図は、複数のサブプロセス間の同期のためのさまざまな通信メカニズムを示しています。
さまざまな通信メカニズム
このセクションでは、さまざまな通信メカニズムについて学習します。メカニズムは以下のとおりです-
キュー
キューは、マルチプロセスプログラムで使用できます。のキュークラスmultiprocessing モジュールはに似ています Queue.Queueクラス。したがって、同じAPIを使用できます。Multiprocessing.Queueは、プロセス間の通信のスレッドおよびプロセスセーフFIFO(先入れ先出し)メカニズムを提供します。
例
以下は、マルチプロセッシングのQueueクラスの概念を理解するために、マルチプロセッシングに関するPythonの公式ドキュメントから抜粋した簡単な例です。
from multiprocessing import Process, Queue
import queue
import random
def f(q):
q.put([42, None, 'hello'])
def main():
q = Queue()
p = Process(target = f, args = (q,))
p.start()
print (q.get())
if __name__ == '__main__':
main()
出力
[42, None, 'hello']
パイプ
これは、マルチプロセスプログラムのプロセス間で通信するために使用されるデータ構造です。Pipe()関数は、デフォルトでデュプレックス(双方向)であるパイプで接続された接続オブジェクトのペアを返します。それは次のように機能します-
パイプの両端を表す接続オブジェクトのペアを返します。
すべてのオブジェクトには2つのメソッドがあります– send() そして recv()、プロセス間で通信します。
例
以下は、マルチプロセッシングの概念を理解するためにPythonの公式ドキュメントから抜粋した簡単な例です。 Pipe() マルチプロセッシングの機能。
from multiprocessing import Process, Pipe
def f(conn):
conn.send([42, None, 'hello'])
conn.close()
if __name__ == '__main__':
parent_conn, child_conn = Pipe()
p = Process(target = f, args = (child_conn,))
p.start()
print (parent_conn.recv())
p.join()
出力
[42, None, 'hello']
マネージャー
Managerは、すべてのユーザー間で共有される情報を調整する方法を提供するマルチプロセッシングモジュールのクラスです。マネージャーオブジェクトはサーバープロセスを制御します。サーバープロセスは共有オブジェクトを管理し、他のプロセスがそれらを操作できるようにします。言い換えると、マネージャーは、異なるプロセス間で共有できるデータを作成する方法を提供します。以下は、マネージャーオブジェクトのさまざまなプロパティです。
managerの主な特性は、共有オブジェクトを管理するサーバープロセスを制御することです。
もう1つの重要なプロパティは、プロセスが変更したときにすべての共有オブジェクトを更新することです。
例
以下は、マネージャーオブジェクトを使用してサーバープロセスでリストレコードを作成し、そのリストに新しいレコードを追加する例です。
import multiprocessing
def print_records(records):
for record in records:
print("Name: {0}\nScore: {1}\n".format(record[0], record[1]))
def insert_record(record, records):
records.append(record)
print("A New record is added\n")
if __name__ == '__main__':
with multiprocessing.Manager() as manager:
records = manager.list([('Computers', 1), ('Histoty', 5), ('Hindi',9)])
new_record = ('English', 3)
p1 = multiprocessing.Process(target = insert_record, args = (new_record, records))
p2 = multiprocessing.Process(target = print_records, args = (records,))
p1.start()
p1.join()
p2.start()
p2.join()
出力
A New record is added
Name: Computers
Score: 1
Name: Histoty
Score: 5
Name: Hindi
Score: 9
Name: English
Score: 3
Managerの名前空間の概念
Manager Classには、名前空間の概念が付属しています。これは、複数のプロセス間で複数の属性を共有するための迅速な方法です。名前空間には、呼び出すことができるパブリックメソッドはありませんが、書き込み可能な属性があります。
例
次のPythonスクリプトの例は、メインプロセスと子プロセス間でデータを共有するために名前空間を利用するのに役立ちます-
import multiprocessing
def Mng_NaSp(using_ns):
using_ns.x +=5
using_ns.y *= 10
if __name__ == '__main__':
manager = multiprocessing.Manager()
using_ns = manager.Namespace()
using_ns.x = 1
using_ns.y = 1
print ('before', using_ns)
p = multiprocessing.Process(target = Mng_NaSp, args = (using_ns,))
p.start()
p.join()
print ('after', using_ns)
出力
before Namespace(x = 1, y = 1)
after Namespace(x = 6, y = 10)
Ctypes-配列と値
マルチプロセッシングモジュールは、共有メモリマップにデータを格納するための配列オブジェクトと値オブジェクトを提供します。 Array 共有メモリから割り当てられたctypes配列であり、 Value 共有メモリから割り当てられたctypesオブジェクトです。
一緒にいるには、マルチプロセッシングからプロセス、値、配列をインポートします。
例
次のPythonスクリプトは、プロセス間で一部のデータを共有するためにCtypes配列と値を利用するためにPythonドキュメントから抜粋した例です。
def f(n, a):
n.value = 3.1415927
for i in range(len(a)):
a[i] = -a[i]
if __name__ == '__main__':
num = Value('d', 0.0)
arr = Array('i', range(10))
p = Process(target = f, args = (num, arr))
p.start()
p.join()
print (num.value)
print (arr[:])
出力
3.1415927
[0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
シーケンシャルプロセス(CSP)の通信
CSPは、システムと並行モデルを備えた他のシステムとの相互作用を説明するために使用されます。CSPは、メッセージパッシングを介して並行プログラムまたはプログラムを作成するためのフレームワークであるため、並行性を記述するのに効果的です。
Pythonライブラリ– PyCSP
CSPにあるコアプリミティブを実装するために、PythonにはPyCSPと呼ばれるライブラリがあります。実装を非常に短く読みやすくするため、非常に簡単に理解できます。以下は、PyCSPの基本的なプロセスネットワークです。
上記のPyCSPプロセスネットワークには、Process1とProcess 2の2つのプロセスがあります。これらのプロセスは、チャネル1とチャネル2の2つのチャネルを介してメッセージを渡すことによって通信します。
PyCSPのインストール
次のコマンドの助けを借りて、PythonライブラリPyCSPをインストールできます-
pip install PyCSP
例
次のPythonスクリプトは、2つのプロセスを互いに並行して実行するための簡単な例です。それはPyCSPpythonlibabaryの助けを借りて行われます-
from pycsp.parallel import *
import time
@process
def P1():
time.sleep(1)
print('P1 exiting')
@process
def P2():
time.sleep(1)
print('P2 exiting')
def main():
Parallel(P1(), P2())
print('Terminating')
if __name__ == '__main__':
main()
上記のスクリプトでは、2つの関数、つまり P1 そして P2 作成され、装飾されています @process それらをプロセスに変換するため。
出力
P2 exiting
P1 exiting
Terminating
イベントドリブンプログラミングは、イベントに焦点を当てています。最終的に、プログラムの流れはイベントに依存します。これまでは、順次実行モデルと並列実行モデルのどちらかを扱っていましたが、イベント駆動型プログラミングの概念を持つモデルは非同期モデルと呼ばれています。イベントドリブンプログラミングは、常に新しい着信イベントをリッスンしているイベントループに依存しています。イベントドリブンプログラミングの動作は、イベントに依存しています。イベントがループすると、イベントは何をどの順序で実行するかを決定します。次のフローチャートは、これがどのように機能するかを理解するのに役立ちます-
Pythonモジュール– Asyncio
AsyncioモジュールはPython3.4で追加され、コルーチンを使用してシングルスレッドの並行コードを記述するためのインフラストラクチャを提供します。以下は、Asyncioモジュールで使用されるさまざまな概念です。
イベントループ
イベントループは、計算コード内のすべてのイベントを処理する機能です。プログラム全体の実行中に途中で動作し、イベントの着信と実行を追跡します。Asyncioモジュールでは、プロセスごとに1つのイベントループを使用できます。以下は、イベントループを管理するためにAsyncioモジュールによって提供されるいくつかのメソッドです-
loop = get_event_loop() −このメソッドは、現在のコンテキストのイベントループを提供します。
loop.call_later(time_delay,callback,argument) −このメソッドは、指定されたtime_delay秒の後に呼び出されるコールバックを調整します。
loop.call_soon(callback,argument)−このメソッドは、できるだけ早く呼び出されるコールバックを手配します。call_soon()が戻った後、コントロールがイベントループに戻ったときに、コールバックが呼び出されます。
loop.time() −このメソッドは、イベントループの内部クロックに従って現在の時刻を返すために使用されます。
asyncio.set_event_loop() −このメソッドは、現在のコンテキストのイベントループをループに設定します。
asyncio.new_event_loop() −このメソッドは、新しいイベントループオブジェクトを作成して返します。
loop.run_forever() −このメソッドは、stop()メソッドが呼び出されるまで実行されます。
例
次のイベントループの例は、印刷に役立ちます hello worldget_event_loop()メソッドを使用する。この例は、Pythonの公式ドキュメントから抜粋したものです。
import asyncio
def hello_world(loop):
print('Hello World')
loop.stop()
loop = asyncio.get_event_loop()
loop.call_soon(hello_world, loop)
loop.run_forever()
loop.close()
出力
Hello World
先物
これは、実行されていない計算を表すconcurrent.futures.Futureクラスと互換性があります。asyncio.futures.Futureとconcurrent.futures.Futureの間に次の違いがあります-
result()メソッドとexception()メソッドは、timeout引数を取りません。また、futureがまだ実行されていない場合は、例外を発生させます。
add_done_callback()で登録されたコールバックは、常にイベントループのcall_soon()を介して呼び出されます。
asyncio.futures.Futureクラスは、concurrent.futuresパッケージのwait()およびas_completed()関数と互換性がありません。
例
以下は、asyncio.futures.futureクラスの使用方法を理解するのに役立つ例です。
import asyncio
async def Myoperation(future):
await asyncio.sleep(2)
future.set_result('Future Completed')
loop = asyncio.get_event_loop()
future = asyncio.Future()
asyncio.ensure_future(Myoperation(future))
try:
loop.run_until_complete(future)
print(future.result())
finally:
loop.close()
出力
Future Completed
コルーチン
Asyncioのコルーチンの概念は、スレッドモジュールの下の標準のThreadオブジェクトの概念に似ています。これは、サブルーチンの概念の一般化です。コルーチンは実行中に一時停止して、外部処理を待機し、外部処理が実行されたときに停止したポイントから戻ることができます。次の2つの方法は、コルーチンの実装に役立ちます-
async def function()
これは、Asyncioモジュールでコルーチンを実装するためのメソッドです。以下は同じためのPythonスクリプトです-
import asyncio
async def Myoperation():
print("First Coroutine")
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(Myoperation())
finally:
loop.close()
出力
First Coroutine
@ asyncio.coroutineデコレータ
コルーチンを実装する別の方法は、@ asyncio.coroutineデコレータでジェネレータを利用することです。以下は同じためのPythonスクリプトです-
import asyncio
@asyncio.coroutine
def Myoperation():
print("First Coroutine")
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(Myoperation())
finally:
loop.close()
出力
First Coroutine
タスク
Asyncioモジュールのこのサブクラスは、イベントループ内のコルーチンの並列実行を担当します。次のPythonスクリプトは、いくつかのタスクを並行して処理する例です。
import asyncio
import time
async def Task_ex(n):
time.sleep(1)
print("Processing {}".format(n))
async def Generator_task():
for i in range(10):
asyncio.ensure_future(Task_ex(i))
int("Tasks Completed")
asyncio.sleep(2)
loop = asyncio.get_event_loop()
loop.run_until_complete(Generator_task())
loop.close()
出力
Tasks Completed
Processing 0
Processing 1
Processing 2
Processing 3
Processing 4
Processing 5
Processing 6
Processing 7
Processing 8
Processing 9
トランスポート
Asyncioモジュールは、さまざまなタイプの通信を実装するためのトランスポートクラスを提供します。これらのクラスはスレッドセーフではなく、通信チャネルの確立後に常にプロトコルインスタンスとペアになります。
以下は、BaseTransportから継承された異なるタイプのトランスポートです。
ReadTransport −これは読み取り専用トランスポートのインターフェースです。
WriteTransport −これは書き込み専用トランスポートのインターフェースです。
DatagramTransport −これはデータを送信するためのインターフェースです。
BaseSubprocessTransport −BaseTransportクラスに似ています。
以下は、BaseTransportクラスの5つの異なるメソッドであり、その後、4つのトランスポートタイプ間で一時的になります。
close() −トランスポートを閉じます。
is_closing() −このメソッドは、トランスポートが閉じているか、すでに閉じている場合にtrueを返します。
get_extra_info(name, default = none) −これにより、輸送に関する追加情報が得られます。
get_protocol() −このメソッドは、現在のプロトコルを返します。
プロトコル
Asyncioモジュールは、ネットワークプロトコルを実装するためにサブクラス化できる基本クラスを提供します。これらのクラスは、トランスポートと組み合わせて使用されます。プロトコルは着信データを解析し、発信データの書き込みを要求しますが、トランスポートは実際のI / Oとバッファリングを担当します。以下はプロトコルの3つのクラスです-
Protocol −これは、TCPおよびSSLトランスポートで使用するストリーミングプロトコルを実装するための基本クラスです。
DatagramProtocol −これは、UDPトランスポートで使用するデータグラムプロトコルを実装するための基本クラスです。
SubprocessProtocol −これは、一方向パイプのセットを介して子プロセスと通信するプロトコルを実装するための基本クラスです。
リアクティブプログラミングは、データフローと変更の伝播を処理するプログラミングパラダイムです。これは、データフローが1つのコンポーネントによって発行されると、変更がリアクティブプログラミングライブラリによって他のコンポーネントに伝播されることを意味します。変更の伝播は、最終的な受信者に到達するまで続きます。イベントドリブンプログラミングとリアクティブプログラミングの違いは、イベントドリブンプログラミングはイベントを中心に展開し、リアクティブプログラミングはデータを中心に展開することです。
リアクティブプログラミング用のReactiveXまたはRX
ReactiveXまたはRaectiveExtensionは、リアクティブプログラミングの最も有名な実装です。ReactiveXの動作は、次の2つのクラスに依存します-
観察可能なクラス
このクラスは、データストリームまたはイベントのソースであり、受信データをパックして、データを1つのスレッドから別のスレッドに渡すことができるようにします。一部のオブザーバーがサブスクライブするまで、データは提供されません。
オブザーバークラス
このクラスは、によって発行されたデータストリームを消費します observable。オブザーバブルを持つ複数のオブザーバーが存在する可能性があり、各オブザーバーは発行された各データ項目を受け取ります。オブザーバブルは、observableにサブスクライブすることで3種類のイベントを受け取ることができます-
on_next() event −データストリームに要素があることを意味します。
on_completed() event −これは排出の終了を意味し、これ以上のアイテムは来ていません。
on_error() event −放出の終了も意味しますが、エラーがスローされた場合 observable。
RxPY –リアクティブプログラミング用のPythonモジュール
RxPYは、リアクティブプログラミングに使用できるPythonモジュールです。モジュールがインストールされていることを確認する必要があります。次のコマンドを使用して、RxPYモジュールをインストールできます-
pip install RxPY
例
以下は、を使用するPythonスクリプトです。 RxPY モジュールとそのクラス Observable そして Observe forリアクティブプログラミング。基本的に2つのクラスがあります-
get_strings() −オブザーバーから文字列を取得するため。
PrintObserver()−オブザーバーから文字列を印刷するため。オブザーバークラスの3つのイベントすべてを使用します。また、subscribe()クラスも使用します。
from rx import Observable, Observer
def get_strings(observer):
observer.on_next("Ram")
observer.on_next("Mohan")
observer.on_next("Shyam")
observer.on_completed()
class PrintObserver(Observer):
def on_next(self, value):
print("Received {0}".format(value))
def on_completed(self):
print("Finished")
def on_error(self, error):
print("Error: {0}".format(error))
source = Observable.create(get_strings)
source.subscribe(PrintObserver())
出力
Received Ram
Received Mohan
Received Shyam
Finished
リアクティブプログラミング用のPyFunctionalライブラリ
PyFunctionalリアクティブプログラミングに使用できるもう1つのPythonライブラリです。これにより、Pythonプログラミング言語を使用して関数型プログラムを作成できます。チェーンされた関数演算子を使用してデータパイプラインを作成できるので便利です。
RxPYとPyFunctionalの違い
両方のライブラリはリアクティブプログラミングに使用され、同様の方法でストリームを処理しますが、両方の主な違いはデータの処理に依存します。 RxPY システム内のデータとイベントを処理します PyFunctional 関数型プログラミングパラダイムを使用したデータの変換に焦点を当てています。
PyFunctionalモジュールのインストール
このモジュールを使用する前にインストールする必要があります。次のようにpipコマンドを使用してインストールできます-
pip install pyfunctional
例
次の使用例 the PyFunctional モジュールとその seq反復および操作できるストリームオブジェクトとして機能するクラス。このプログラムでは、すべての値を2倍にするlamda関数を使用してシーケンスをマップし、xが4より大きい値をフィルタリングし、最後にシーケンスを残りのすべての値の合計に減らします。
from functional import seq
result = seq(1,2,3).map(lambda x: x*2).filter(lambda x: x > 4).reduce(lambda x, y: x + y)
print ("Result: {}".format(result))
出力
Result: 6