百万年俸のpythonの道 - 並行プログラミングコルーチン

コルーチン

I.導入コルーチン

このセクションの内容は、シングルスレッドの同時実行性に基づいており、我々は、同時の性質を検討する必要があるため、同時実行した場合の唯一のメインスレッドは、(明らかに使用可能なCPUのみ)、以下のことが達成される:保存の+スイッチング状態を

  タスクを実行するCPUは、カット(スイッチング動作システムによる強制制御)の周りに閉塞が両方のケースがタスクで発生した場合に他のタスクを実行し、状況が時間を計算する追加のタスクが長すぎるか、または優先度の高いプログラムに置き換えられています

  コルーチン、本質的​​にスレッド上で、タスク切り替えの前に、スレッドは、オペレーティングシステムによって制御されているが、I / Oのスイッチが自動的に、我々は今、コルーチンオブジェクトはOSの切り替え(スイッチングスレッド、作成されたレジスタの少ないオーバーヘッドで遭遇しました、)それらを切り替えるためにここで私たち自身のプログラムがタスクスイッチを制御するなど、など、スタック。

IMG

    PSは:プロセスの理論を紹介し、プロセスの3つの実施状況を言及し、スレッドが実行ユニットであるので、それは三つの状態のスレッドとして地図上に理解することができます

ワン:複数のタスクは、このスイッチを純粋な計算であれば雨は、CPUがすべてのタスクを達成することができ下降数字だけでできるように、効率を改善しない、後者の場合は、結果の「同時」実装であるように見えますそれは、効率が低下します。この目的のために、我々は利回りに基づいて検証することができます。収量自体はあなたが単一のスレッドの下で状態を実行中のタスクを保存することができます方法で、我々は単にオーバー行きます:
#1 yiled可以保存状态,yield的状态保存与操作系统的保存线程状态很像,但是yield是代码级别控制的,更轻量级
#2 send可以把一个函数的结果传给另外一个函数,以此实现单线程内程序之间的切换  

サイトを保護するために、タスク切り替え+によって達成収量:

import time

def func1():
    for i in range(11):
        yield
        print('这是我第%s次打印'%i)
        time.sleep(1)


def func2():
    g = func1()
    next(g)
    for k in range(10):
        print(f'呵呵,我已经打印{k}次')
        time.sleep(1)
        next(g)


# 不写yield, 下面两个任务是执行完func1里面所有的程序才会执行func2里面的程序,
# 有了yield,我们实现了两个任务的切换+保持状态
func1()
func2()

コプロセッサシリアル計算集約的なプロセスとの比較:

# 串行
import time
def task1():
    res = 1
    for i in range(1,100000):
        res += i


def task2():
    res = 1
    for i in range(1,100000):
        res -= i

start_time = time.time()
task1()
task2()
print(time.time()-start_time)   # 0.011995553970336914
# yield版的协程
import time

def task():

    res = 1
    for i in range(1,100000):
        res += i
        yield res

def task2():
    g = task()
    res = 1
    for i in range(1,100000):
        res -= i
        next(g)

start = time.time()
task2()
print(time.time() - start)  # 0.0239870548248291

タスクはシリアルコルーチンより速く、計算集約的である場合。

II:最初のハンドオーバー条件。ミッションはIO状況に遭遇して、あなたがコンピューティングタスクIIを完了するために、ブロックされたタスクを利用することができるように、実行するために、2つのタスクにカットし、この中で効率を改善しました。
import time
def func1():
    while True:
        print('func1')
        yield

def func2():
    g=func1()
    for i in range(10000000):
        next(g)
        time.sleep(3)
        print('func2')
start=time.time()
func2()
stop=time.time()
print(stop-start)


# yield不能检测IO阻塞,不能实现遇到IO自动切换.

コルーチンはCPythonのインタプリタを伝えることです、あなたはNBいない、いない、あなたがスレッドを実行するためにも、私は混乱を所有し、それにGILロックを上演し、あなたの時間の節約を切り替えスレッドは、私はあなたよりも切り替えるように切り替え独自のプログラムでは、我々ができれば非常に速く、我々は必然的にプログラム内で発生したシングルスレッド、IO操作のためのオーバーヘッドの多くを避けるが、単一スレッドの制御の下で(ユーザプログラムレベルではなく、オペレーティングシステムレベルすなわち)ブロックは、このようにスレッドが状態は常に我々のユーザーと同等のCPUを、実行することができることを、レディ状態で最大化することが可能であることを確認して、タスクで計算するために、別のタスクに切り替えたときにioの複数のタスクが発生する可能性が、見てみましょうプログラムレベルがioは非表示にし、その動作を最大化するために、そのオペレーティングシステムが混乱することができます:このスレッドが計算されているようだ、ioは比較的小さいので、より多く配分されるCPUは、私たちのスレッドに実行権限を。

コルーチンは、シングルスレッドの性質であり、ユーザ自身のタスクによって制御はブロックIoは効率を向上させるために、実行する他のタスクに切り替え遭遇します。それを達成するために、我々は同時に、次の基準を満たすソリューションの方法を見つける必要があります。

#1. 可以控制多个任务之间的切换,切换之前将任务的状态保存下来,以便重新运行时,可以基于暂停的位置继续执行。
#2. 作为1的补充:可以检测io操作,在遇到io操作的情况下才发生切换

II。生産工程協会

コルーチン:同時シングルスレッドとも呼ばれるマイクロスレッドは、シュレッド。英語コルーチン。何がスレッドの単語を説明している:コルーチン軽量スレッドはすなわちコルーチンは、自分自身をスケジュールするユーザプログラムによって制御され、ユーザー状態です。
コルーチンの定義:シングルスレッドでは、ユーザ自身のタスクによって制御はブロックIoは効率を向上させるために、実行する他のタスクに切り替え遭遇します。

コルーチンとスレッドの違い

その単純なものからCPUコンテキストを保存し、復元するよりも、ときにマルチタスク、スレッド切り替えシステムレベルはるかに。その上で、独自のデータ・キャッシュ・キャッシュを実行している各スレッドのための効率的なオペレーティングシステムは、オペレーティングシステムを使用すると、データ復旧作業を行うのに役立ちます。だから、スレッドに巨大なパフォーマンスを切り換えます。第二は、生体系に対して万回を切り替えるように、しかし、単純に、CPUの動作のコルーチンのコンテキストを切り替えます。

  これは、ことが強調されます。

#1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
#2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)

  比較切替制御スレッド・オペレーティング・システムは、ユーザがシングルスレッドコルーチンのスイッチングを制御します

  利点は次のとおりです。

#1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
#2. 单线程内就可以实现并发的效果,最大限度地利用cpu

  次のような欠点があります:

#1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程

  概要コルーチンの機能:

  1. それは唯一のシングルスレッドで同時に実施されなければなりません
  2. ロックすることなく共有データを変更します
  3. ユーザープログラムの制御フロースタック複数のコンテキストを保存

三。Greenlet

私たちは(あなたが発電機いったん初期化取得する必要があり、その後、送信を呼び出し、あまりにも面倒な歩留まりBuilderの方法を使用して、複数のタスクの切り替えを達成するために、単一スレッド内20個のタスクを持っている場合...非常に面倒このタスクを達成するために非常に単純なgreenletモジュール20を使用している間)、直接切り替えられました

# 安装
一:
    pip3 install greenlet
二:
    PyCharm里settings-----> Project Interpreter-----> 右上角的加号+  ------> 搜索框里搜索
# 真正的协程模块就是使用greenlet完成的切换
from greenlet import greenlet

def eat(name):
    print(f"{name} eat 1")  # 2
    g2.switch('zdr')    # 3
    print(f'{name} eat 2')  # 6
    g2.switch() # 7


def play(name):
    print(f'{name} play 1') # 4
    g1.switch() # 5
    print(f'{name} play 2') # 8



g1 = greenlet(eat)
g2 = greenlet(play)

g1.switch('zcy')    # 可以在第一次switch时传入传输,以后就不需要 1

シンプルスイッチ(IO状況の非存在下またはメモリ空間を開放する操作を繰り返していなかった)が、プログラムの実行速度が低下します

効率の比較:

#顺序执行
import time
def f1():
    res=1
    for i in range(100000000):
        res+=i

def f2():
    res=1
    for i in range(100000000):
        res*=i

start=time.time()
f1()
f2()
stop=time.time()
print('run time is %s' %(stop-start)) #10.985628366470337

#切换
from greenlet import greenlet
import time
def f1():
    res=1
    for i in range(100000000):
        res+=i
        g2.switch()

def f2():
    res=1
    for i in range(100000000):
        res*=i
        g1.switch()

start=time.time()
g1=greenlet(f1)
g2=greenlet(f2)
g1.switch()
stop=time.time()
print('run time is %s' %(stop-start)) # 52.763017892837524

效率对比

唯一greenletタスクのIOが発生した場合は所定の位置にそれをブロックし、実行にカットジェネレータスイッチ、より便利な方法を提供し、まだIOは自動的に効率を改善するために、スイッチに発生する問題が解決されていません。

  IMG

  この図の上に、何の固有の回避I / O時間はないが、それは、コルーチンの本当の意味ですが、私たちは何かを行うには、この時間を使用し、通常の作業の過程で、我々は、スレッド+達成するためのコルーチンの道を+です同時、最良の結果を達成するために、同時、コア4のCPUであれば、一般的に5つのプロセス、スレッド20の各(CPUの5倍の数)から、各スレッドは500からコルーチンができ、質量登りますページを取ったときに、時間のネットワークの遅延時間を待って、私たちは、同時コルーチンを達成するために使用することができます。一般4CPU同時機械の最大数である同時= 5 * 20 * 500 = 50000同時の数、。nginxの最大荷重負荷分散が5ワットであるとき

  これらの20回の作業のためのシングルスレッドのコードでは、通常は両方の操作はブロック操作を持って計算し、私たちは、障害物の使用に、午前1時00分を遮断するタスク実行時の経験でタスク2を実行するために行くことができます。だから、Geventモジュールを使用して効率を向上させるため、中

四。Geventプロフィール

# 安装
一:
    pip3 install gevent
二:
    PyCharm里settings-----> Project Interpreter-----> 右上角的加号+  ------> 搜索框里搜索

Geventは、サードパーティのライブラリである、あなたは簡単にgeventによって同期または非同期並行プログラミングを実装することができ、メインモードはgeventで使用されているGreenlet、それはアクセスPythonのC拡張モジュールの軽量コルーチンの形です。すべての主要なオペレーティング・システム・プロセス内で実行されますが、彼らは共同でスケジュールされているGreenlet。

#用法
g1=gevent.spawn(func,1,2,3,x=4,y=5)创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的,spawn是异步提交任务

g2=gevent.spawn(func2)

g1.join() #等待g1结束

g2.join() #等待g2结束  有人测试的时候会发现,不写第二个join也能执行g2,是的,协程帮你切换执行了,但是你会发现,如果g2里面的任务执行的时间长,但是不写join的话,就不会执行完等到g2剩下的任务了


#或者上述两步合作一步:gevent.joinall([g1,g2])

g1.value#拿到func1的返回值

これは、自動的にタスクがIOを遮断遭遇切り替わります

import gevent
def eat(name):
    print('%s eat 1' %name)
    gevent.sleep(2)
    print('%s eat 2' %name)

def play(name):
    print('%s play 1' %name)
    gevent.sleep(1)
    print('%s play 2' %name)


g1=gevent.spawn(eat,'egon')
g2=gevent.spawn(play,name='egon')
g1.join()
g2.join()
#或者gevent.joinall([g1,g2])
print('主')

遇到I/O切换

実施例上部gevent.sleep(2)は、認識可能な閉塞IO模擬geventあります

  そしてtime.sleep(2)またはその他の障害物、GEVENTは直接、次の行を使用する必要性を認識していない、パッチ、識別することができます

  monkey.patch_all前()は、時間、ソケットモジュールとして、パッチを適用し、それらの前に置かれなければならない。geventインポートサルから

  それとも単に覚えている:geventを使用し、あなたがgevent輸入サルからする必要があります。ファイルに()monkey.patch_allを開始します

from gevent import monkey;monkey.patch_all() #必须写在最上面,这句话后面的所有阻塞全部能够识别了

import gevent  #直接导入即可
import time
def eat():
    #print()  
    print('eat food 1')
    time.sleep(2)  #加上mokey就能够识别到time模块的sleep了
    print('eat food 2')

def play():
    print('play 1')
    time.sleep(1)  #来回切换,直到一个I/O的时间结束,这里都是我们个gevent做得,不再是控制不了的操作系统了。
    print('play 2')

g1=gevent.spawn(eat)
g2=gevent.spawn(play_phone)
gevent.joinall([g1,g2])
print('主')

我々はthreading.current_thread()を使用することができる。関連項目GetName()をそれぞれG1とG2、ビュー結果DummyThread-Nを表示するために、それは内部スレッドで実際に偽のスレッド、仮想スレッド、あります

  タスク切り替え処理スレッドは、オペレーティングシステム自体によって切り替える、あなたは自分自身を制御することはできません

  コルーチンは、独自のプログラム(コード)を介して切り替えることで、それらが制御することができ、コルーチンモジュールはIO操作を識別することができたときにタスク切り替えのみ満たされるすべてのプログラムがIOでない場合、プログラムは、同時効果を実現します操作は、基本的にはシリアル実行。

同期および非同期の五Gevent

from gevent import spawn,joinall,monkey;monkey.patch_all()

import time
def task(pid):
    """
    Some non-deterministic task
    """
    time.sleep(0.5)
    print('Task %s done' % pid)


def synchronous():
    for i in range(10):
        task(i)

def asynchronous():
    g_l=[spawn(task,i) for i in range(10)]
    joinall(g_l)

if __name__ == '__main__':
    print('Synchronous:')
    synchronous()

    print('Asynchronous:')
    asynchronous()
#上面程序的重要部分是将task函数封装到Greenlet内部线程的gevent.spawn。 初始化的greenlet列表存放在数组threads中,此数组被传给gevent.joinall 函数,后者阻塞当前流程,并执行所有给定的greenlet。执行流程只会在 所有greenlet执行完后才会继续向下走。

协程:同步异步对比

6 Geventの適用例

from gevent import monkey;monkey.patch_all()
import gevent
import requests
import time

def get_page(url):
    print('GET: %s' %url)
    response=requests.get(url)
    if response.status_code == 200:
        print('%d bytes received from %s' %(len(response.text),url))


start_time=time.time()
gevent.joinall([
    gevent.spawn(get_page,'https://www.python.org/'),
    gevent.spawn(get_page,'https://www.yahoo.com/'),
    gevent.spawn(get_page,'https://github.com/'),
])
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))

协程应用:爬虫

上記のプログラムに加えて効率を参照するにはシリアルコードの最後のセクション:あなたのプログラムは、高効率を必要としない場合は、ああ、それは持っていないように同時どのようなものコルーチン。

print('--------------------------------')
s = time.time()
requests.get('https://www.python.org/')
requests.get('https://www.yahoo.com/')
requests.get('https://github.com/')
t = time.time()
print('串行时间>>',t-s)

七Gevent応用例(B)

  同時シングルスレッドソケットによって達成Gevent(gevent輸入サルから、インポートはソケットモジュールmonkey.patch_all()の前に配置する必要があり、そうでなければソケットブロッキングgevent認識されていません)

  IMG

  経過ネットワーク要求時間遅延時間の複数

サーバー:

from gevent import monkey;monkey.patch_all()
from socket import *
import gevent

#如果不想用money.patch_all()打补丁,可以用gevent自带的socket
# from gevent import socket
# s=socket.socket()

def server(server_ip,port):
    s=socket(AF_INET,SOCK_STREAM)
    s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    s.bind((server_ip,port))
    s.listen(5)
    while True:
        conn,addr=s.accept()
        gevent.spawn(talk,conn,addr)

def talk(conn,addr):
    try:
        while True:
            res=conn.recv(1024)
            print('client %s:%s msg: %s' %(addr[0],addr[1],res))
            conn.send(res.upper())
    except Exception as e:
        print(e)
    finally:
        conn.close()

if __name__ == '__main__':
    server('127.0.0.1',8080)

服务端

クライアント:

from socket import *

client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))


while True:
    msg=input('>>: ').strip()
    if not msg:continue

    client.send(msg.encode('utf-8'))
    msg=client.recv(1024)

サービスの上端を求めるために、複数のマルチスレッドクライアントが、問題ありません

from threading import Thread
from socket import *
import threading

def client(server_ip,port):
    c=socket(AF_INET,SOCK_STREAM) #套接字对象一定要加到函数内,即局部名称空间内,放在函数外则被所有线程共享,则大家公用一个套接字对象,那么客户端端口永远一样了
    c.connect((server_ip,port))

    count=0
    while True:
        c.send(('%s say hello %s' %(threading.current_thread().getName(),count)).encode('utf-8'))
        msg=c.recv(1024)
        print(msg.decode('utf-8'))
        count+=1
if __name__ == '__main__':
    for i in range(500):
        t=Thread(target=client,args=('127.0.0.1',8080))
        t.start()

多线程并发多个客户端,去请求上面的服务端是没问题的

おすすめ

転載: www.cnblogs.com/zhangchaoyin/p/11420972.html