プロセス
プロセスは、リソース割り当ての最小単位であり、レジスタ情報、ヒープ、スタック、データセグメント、コードセグメント、仮想メモリ、ファイルハンドル、IOステータス、信号情報などの独立したメモリ空間を持ち、さまざまなプロセスのスイッチングオーバーヘッドは比較的大きい、プロセスは比較的独立していて安定していますが、通常は他のプロセスの影響を受けません
プロセス間通信には、パイプ、メッセージキュー、セマフォ、共有メモリ、ソケットなどが含まれます。
スレッド
スレッドはシステムスケジューリングの最小単位であり、独自のスタックを保存し、情報と他の小さなコンテンツを登録するだけで済みます。プロセスには少なくとも1つのスレッドが必要です。異なるスレッドの切り替えオーバーヘッドはプロセスの切り替えよりもはるかに小さくなりますが、スレッドは独立しておらず、プロセスによって簡単に影響を受けるほど安定していません。そして、他のスレッドの影響
異なるスレッドはすべて同じメモリを共有するため、スレッド間の通信は、グローバルに定義された変数を使用する共有メモリを直接使用します。さらに、異なるスレッドは通常、ロックを通じて同期、相互排他、その他の機能を実現する必要があります。
コルーチン
プロセスとスレッドの両方がオペレーティングシステムによってスケジュールされます。スレッド切り替えのオーバーヘッドはプロセスよりも小さいですが、頻繁に切り替えられる場合でも、パフォーマンスに深刻な影響を及ぼします。
通常、オペレーティングシステムは3つの状況で切り替わります。
- プログラムの実行時間が長くなる
- 優先度の高いプログラムの横取り
- プログラムはブロックされています
多くのネットワークアプリケーションでは、多数のリクエストが同時に受け入れられます。これらのリクエストの計算は非常に小さくなります。メイン時間はIOに費やされます。最も重要なのは、頻繁なIOブロッキングとスレッドスイッチングにつながるネットワークIO時間です。パフォーマンス
コルーチンは、同時実行性の高いシナリオでメインオーバーヘッドとしてIOを使用するプログラムのパフォーマンスの問題を解決することです
スレッド内で複数のコルーチンを実行できます。コルーチンがIOブロッキングを必要とするコマンドを呼び出すと、非同期IOを使用して、オペレーティングシステムがトリガーされて別のコルーチンが実行されるのを防ぎます。スレッド内に実装され、切り替えのオーバーヘッドは非常に小さく、パフォーマンスが大幅に向上します
コルーチンは、大量のIO同時実行の場合に、より明白になります。これは、この場合に限り、非同期IOがいつでも実行できる状態にあることを保証できるためです。10分での要求など、IOの量が少ない場合、非同期操作を実行した後も、非同期IOの準備ができるまで待機する必要があります。これにより、スレッドの切り替えが発生します。
コルーチンは1つの場合にのみ切り替わることに注意してください:IO呼び出し
この機能は、オペレーティングシステムに対して透過的であり、アプリケーションプログラムに対して透過的であるプログラムフレームワークによって実装する必要があります。開発ワークロード
Go言語では、この関数はネイティブです。Go言語自体がこの関数を実装
し、構文レベルでサポートしています。Python言語では、この関数はgeventパッケージでサポートされています。
以下は主にPythonコルーチンについて話します
産出
Yieldは、次のコードなどのジェネレーター用です
def f(max):
n = 1
while n <= max:
yield n*n
n = n + 1
for i in f(5):
print(i)
yieldを使用しない場合、関数fはリストを返す必要があります。maxが非常に大きい場合、このリストを配置するために大きなメモリを作成する必要があります。yieldを使用した後、関数はイテレーターf(5)と見なされます。イテレータを返します。forステートメントが値を取得するたびにイテレータをトリガーします。イテレータがyieldコマンドを実行すると、n * nを返し、次にfor値が取得されるまで実行を停止します。イテレータはn = n + 1から開始します実行を続行すると、最大値がどれほど大きくても、メモリ使用量は一定になります
別の例を挙げてください
def f():
n = 1
print("f function with yield inside")
while True:
msg = yield n
print("msg: ", msg)
n = n + 1
iter = f()
print("before invoke next")
print("receive: ", next(iter))
print("after invoke next")
print("receive: ", next(iter))
返されるものは
before invoke next
f function with yield inside
('receive: ', 1)
after invoke next
('msg: ', None)
('receive: ', 2)
iter = f()が呼び出されると、情報は出力されない、つまり、f()関数は実際には実行されませんが、次の(iter)関数が実行されると、イテレーターが返されます(次はPythonの組み込み関数です)。 、f()関数が実行されます。ここでは、yield nまで実行され、次に実行を停止して結果としてnを返します(ここでは、後で説明するmsgの割り当ても実行されません。これについては後で説明します)。 msgの割り当てから、yieldに再び達するまで実行を続けます。イテレータが実行されている場合、次の関数はStopIteration例外を報告します
次の例に進む
def f():
n = 1
print("f function with yield inside")
while True:
msg = yield n
print("msg: ", msg)
n = n + 1
iter = f()
print("before invoke next")
print("receive: ", next(iter))
print("after invoke next")
print("receive: ", iter.send("from outside"))
ここで2番目の次は、イテレータを呼び出すsend関数に置き換えられ
ます。
before invoke next
f function with yield inside
('receive: ', 1)
after invoke next
('msg: ', 'from outside')
('receive: ', 2)
前の例との唯一の違いは、出力されるmsgがNoneではなく、send関数のパラメーターであることです。次のように、send関数はイテレーターをトリガーして実行を続行しますが、同時に、yieldステートメントの結果としてパラメーターがmsgに割り当てられます。
以下では、yieldを使用してコルーチンをシミュレートします
def f_0():
n = 5
while n >= 0:
print('[f_0] ' + str(n))
yield
n = n - 1
def f_1():
m = 3
while m >= 0:
print('[f_1] ' + str(m))
yield
m = m - 1
iter_list = [f_0(), f_1()]
while True:
for it in iter_list:
try:
next(it)
except:
iter_list.remove(it)
if len(iter_list) == 0:
break
結果は
[f_0] 5
[f_1] 3
[f_0] 4
[f_1] 2
[f_0] 3
[f_1] 1
[f_0] 2
[f_1] 0
[f_0] 1
[f_0] 0
2つの機能を連続的に切り替える機能が実現されていることがわかりますが、コードを書くのは面倒です
Greenlet
greenletは、下部にネイティブコルーチンを実装するC拡張ライブラリです
from greenlet import greenlet
def f_0():
n = 5
while n >= 0:
print('[f_0] ' + str(n))
parent_greenlet.switch()
n = n - 1
def f_1():
m = 3
while m >= 0:
print('[f_1] ' + str(m))
parent_greenlet.switch()
m = m - 1
def parent():
while True:
for task in greenlet_list:
task.switch()
if task.dead:
greenlet_list.remove(task)
if len(greenlet_list) == 0:
break
parent_greenlet = greenlet(parent)
greenlet_list = [greenlet(f_0, parent_greenlet), greenlet(f_1, parent_greenlet)]
parent_greenlet.switch()
戻る
[f_0] 5
[f_1] 3
[f_0] 4
[f_1] 2
[f_0] 3
[f_1] 1
[f_0] 2
[f_1] 0
[f_0] 1
[f_0] 0
スイッチは値で渡すこともできます。これは、プログラムの実行状況に応じて関数パラメーターに渡されるか、スイッチの戻りに渡されます。
def test1(x, y):
z = gr2.switch(x+y)
print(z)
def test2(u):
print(u)
gr1.switch(42)
print "end"
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch("hello", " world")
戻る
hello world
42
親が指定されていないため、endは印刷されないことがわかります。デフォルトでは、一方の端がメインに戻り、もう一方は実行されません。親が指定されている場合、親は終了後に返されます
行商人
Greenletの記述もより複雑であり、Greenletはコルーチンのみを実装しますが、IO操作のキャプチャと切り替えの機能は実装していません。実際、一般的な計算はコルーチンの切り替えを必要とせず、パフォーマンスには影響がなく、同時IO操作が高い場合のみプログラムを切り替えられると、そのパフォーマンスが大幅に向上します
geventはgreenletに基づいており、Linuxのepollイベント監視メカニズムを含む多くの最適化手段を使用して、同時並行IOのパフォーマンスを向上させます。たとえば、greenletプログラムがネットワークIO操作を実行する必要がある場合、非同期監視およびスイッチングとして登録されます他のGreenletプログラムに移動し、IOが完了するのを待ってから、適切なときに実行を継続するように切り替えます。これにより、IOが非常に高い場合に、IO待機に時間を費やす代わりに、スレッドを回避しながらプログラムを実行し続けることができます。切り替えオーバーヘッド
import gevent
def f_0(param):
n = param
while n >= 0:
print('[f_0] ' + str(n))
gevent.sleep(0.1)
n = n - 1
def f_1(param):
m = param
while m >= 0:
print('[f_1] ' + str(m))
gevent.sleep(0.1)
m = m - 1
g1 = gevent.spawn(f_0, 5)
g2 = gevent.spawn(f_1, 3)
gevent.joinall([g1, g2])
戻る
[f_0] 5
[f_1] 3
[f_0] 4
[f_1] 2
[f_0] 3
[f_1] 1
[f_0] 2
[f_1] 0
[f_0] 1
[f_0] 0
通常のプログラムと比較して、time.sleep()をgevent.sleep()に置き換えることで、ブロックする必要がある場所でコルーチンの切り替えを行うことができます。
それは実際にはもっと簡単です
import time
import gevent
from gevent import monkey
monkey.patch_all()
def f_0(param):
n = param
while n >= 0:
print('[f_0] ' + str(n))
time.sleep(0.1)
n = n - 1
def f_1(param):
m = param
while m >= 0:
print('[f_1] ' + str(m))
time.sleep(0.1)
m = m - 1
g1 = gevent.spawn(f_0, 5)
g2 = gevent.spawn(f_1, 3)
gevent.joinall([g1, g2])
monkey.patch_all()を使用してパッチを適用すると、time sleep、httpリクエストなどの多数のIO操作をインターセプトし、それらを非同期に実行し、コルーチンを切り替えることができます。このアプローチにより、元の関数を変更せずに直接使用できます。開発者にとって、コルーチンは透過的であり、特にコードを変更する必要はありません。コードの処理はgeventに任せてください。
非同期
Python 3.6はasyncioライブラリをPython標準ライブラリとして正式に導入しました
最も重要なのはasyncおよびawaitキーワードです
asyncは、関数を非同期として宣言するために使用され、一時停止できます
awaitは、プログラムが中断されていることを宣言するために使用されます。awaitの後に続くのは、非同期プログラムまたは__await__属性を持つオブジェクトのみです。
import asyncio
import aiohttp
async def f_0(param):
n = param
while n >= 0:
print('[f_0] ' + str(n))
await asyncio.sleep(0.1)
n = n - 1
async def f_1(param):
m = param
while m >= 0:
print('[f_1] ' + str(m))
await asyncio.sleep(0.1)
m = m - 1
loop = asyncio.get_event_loop()
tasks = [
f_0(5),
f_1(3)
]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
戻る
[f_0] 5
[f_1] 3
[f_0] 4
[f_1] 2
[f_0] 3
[f_1] 1
[f_0] 2
[f_1] 0
[f_0] 1
[f_0] 0
別の例
import asyncio
import aiohttp
async def request(session, url):
async with session.get(url) as response:
return await response.read()
async def fetch(url):
await asyncio.sleep(1)
async with aiohttp.ClientSession() as session:
html = await request(session, url)
print(html)
url_list = [
"http://www.qq.com",
"http://www.jianshu.com",
"http://www.cnblogs.com"
]
tasks = [fetch(url) for url in url_list]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
非同期呼び出しをサポートするためにasyncを追加し、awaitを使用して中断する場所を指定する必要があることがわかります。awaitで指定し
たコードを中断できない場合、エラーが発生
し、特定の非同期メソッドまたはクラスを使用する必要があります。
比較すると、geventはプログラムに対して透過的であり、
通常の同期プログラムは変更せずにgeventを介して非同期で実現できます。
ただし、geventは3者のパッケージを使用し、asyncioはPython標準ライブラリであり、構文レベルでサポートを提供します。