日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

【Python协程的实现】

發(fā)布時間:2024/8/24 综合教程 30 生活家
生活随笔 收集整理的這篇文章主要介紹了 【Python协程的实现】 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

"

補(bǔ)充數(shù)據(jù)安全問題

進(jìn)程:    多個進(jìn)程操作同一個文件,會出現(xiàn)數(shù)據(jù)不安全線程:    多個線程操作同一個全局變量,會出現(xiàn)數(shù)據(jù)不安全    對于共享的數(shù)據(jù)操作:        如果是 += *= /= -= 操作,都存在數(shù)據(jù)不安全問題        如果是append,extend,pop,remove操作,就不會出現(xiàn)數(shù)據(jù)不安全問題協(xié)程:    永遠(yuǎn)不會出現(xiàn)數(shù)據(jù)不安全問題    因?yàn)閰f(xié)程是由程序員控制的,而程序員控制的只能是代碼

協(xié)程示例代碼:

# 最簡單的協(xié)程 a = 0 def fn1():    global a    g = fn2()  # 拿到生成器    next(g)  # 轉(zhuǎn)向fn2函數(shù)執(zhí)行    a += 1    next(g)  # 轉(zhuǎn)向fn2函數(shù)執(zhí)行 def fn2():    global a    yield    a += 1    yield print(fn1())  # Noneprint(a)  # 2

1. 協(xié)程介紹

協(xié)程是單線程下的并發(fā),又稱微線程,纖程。英文名Coroutine。一句話說明什么是協(xié)程:協(xié)程是一種用戶態(tài)的輕量級線程,即協(xié)程是由用戶程序自己控制調(diào)度的.

1. Python的線程屬于內(nèi)核級別的,即由操作系統(tǒng)控制調(diào)度(如單線程遇到io或執(zhí)行時間過長就會被迫交出cpu執(zhí)行權(quán)限,切換其它線程運(yùn)行)

2. 單線程內(nèi)開啟協(xié)程,一旦遇到io,就會從應(yīng)用程序級別(而非操作系統(tǒng))控制切換,以此來提升效率(非io操作的切換反而會降低效率!)

對比操作系統(tǒng)控制線程的切換,用戶在單線程內(nèi)控制協(xié)程的切換

優(yōu)點(diǎn)如下:

1. 協(xié)程的切換開銷更小,屬于程序級別的切換,操作系統(tǒng)完全感知不到,因而更加輕量級

2. 單線程內(nèi)就可以實(shí)現(xiàn)并發(fā)的效果,最大限度地利用cpu

缺點(diǎn)如下:

1. 協(xié)程的本質(zhì)是單線程下實(shí)現(xiàn)并發(fā),因而無法利用多核。(可以是一個程序開啟多個進(jìn)程,每個進(jìn)程內(nèi)開啟多個線程,每個線程內(nèi)開啟協(xié)程)

2. 協(xié)程指的是單個線程,因而一旦協(xié)程出現(xiàn)阻塞,將會阻塞整個線程

總結(jié)協(xié)程特點(diǎn)

1. 必須在一個單線程里實(shí)現(xiàn)并發(fā)

2. 修改共享數(shù)據(jù)不需加鎖

3. 用戶程序里自己保存多個控制流的上下文棧

(附加:一個協(xié)程遇到io操作自動切換到其它協(xié)程(如何實(shí)現(xiàn)檢測io?yield、greenlet都無法實(shí)現(xiàn),需要用到gevent模塊(select機(jī)制)))


二、greenlet模塊

windows安裝命令:pip3 install greenlet

單純的切換(在沒有io或沒有重復(fù)開辟內(nèi)存空間的操作下)反而會降低程序的執(zhí)行速度

# 效率對比 from greenlet import greenletfrom time import time def func1():    res = 1    for i in range(1000000):        res +=i def func2():    res = 1    for i in range(1000000):        res *=i  # 順序執(zhí)行start = time()func1()func2()print('run time is', time() - start)# run time is 0.19996070861816406 #  切換start =time()g1 = greenlet(func1)g2 = greenlet(func2)g1.switch()print('run time is', time() - start)# run time is 19.51878547668457

greenlet只是提供了一種比generator更加便捷的切換方式,當(dāng)切到一個任務(wù)執(zhí)行時,如果遇到io操作,便會原地阻塞,仍然沒有解決遇到io自動切換來提升效率的問題.

單線程里的多個任務(wù)通常會既有計算操作又有阻塞操作,我們完全可以在執(zhí)行任務(wù)1遇到阻塞時就切換到任務(wù)2繼續(xù)執(zhí)行。如此才能提高效率,這就需要用到Gevent模塊.


三、gevent模塊

windows安裝命令:pip3 install gevent

gevent模塊是一個第三方庫,可以輕松通過gevent實(shí)現(xiàn)并發(fā)同步或異步編程,在gevent中用到的主要模式是greenlet,它是以C擴(kuò)展模塊形式接入Python的輕量級協(xié)程。greenlet全部運(yùn)行在主程序操作系統(tǒng)進(jìn)程的內(nèi)部,但它們被協(xié)作式地調(diào)度。

基本用法

g = gevent.spawn(func, 1, 2, x=3, y=4):創(chuàng)建一個協(xié)程對象g,spawn括號內(nèi)的第一個參數(shù)是函數(shù)名,后面可以有多個參數(shù),可以是位置實(shí)參或關(guān)鍵字實(shí)參,都是傳給函數(shù)的func。

g.join():等待g結(jié)束,等價于gevent.joinall([g1, g2])

g.value:拿到func的返回值

# 遇到io主動切換 import gevent def eat(name):    print('%s eat 1' % name)    gevent.sleep(1)    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])

上面的gevent.sleep()模擬的是gevent可以識別的io阻塞,而time.sleep()或其它的阻塞,gevent是不能直接識別的,需要用到下面一行代碼,打補(bǔ)丁,便可識別:from gevent import monkey; monkey.patch_all() 必須寫在被打補(bǔ)丁者之前:

from gevent import spawn, monkeyimport timedef eat(name):    print('%s eat 1' % name)    time.sleep(1)    print('%s eat 2' % name) def play(name):    print('%s play 1' % name)    time.sleep(1)    print('%s play 2' % name) monkey.patch_all()  # 打補(bǔ)丁g1 = spawn(eat, 'egon')g2 = spawn(play, name='egon')g1.join()g2.join()# 或者 gevent.joinall([g1, g2])

我們可以使用threading.current_thread().getName()來查看每個協(xié)程的變量名都會為:DummyThread-n,既假線程。

from gevent import spawn, monkeyfrom threading import current_thread func1 = lambda :print(current_thread().getName())   # DummyThread-1 monkey.patch_all()spawn(func1).join()

同步與異步效率對比

# 同步與異步效率對比 from gevent import spawn, joinall, monkey;monkey.patch_all()from time import sleep def task(pid):    """Some non-deterministic task"""    sleep(0.5)    print('Task %s done' % pid) def synchronous():  # 同步    [task(i) for i in range(10)] def asynchronous(): # 異步    gevent_lst = [spawn(task, i) for i in range(10)]    joinall(gevent_lst)    print('DONE') if __name__ == '__main__':    print('Syinchronous:')    synchronous()    print('Asynchronous:')    asynchronous()

異步應(yīng)用爬蟲

from gevent import spawn, joinall, monkey; monkey.patch_all()from requests import getfrom time import time def get_page(url):    print('GET: %s' % url)    response = get(url)    if response.status_code == 200:        print('%d bytes received from %s' %(len(response.text), url)) start_time = time()joinall([    spawn(get_page, 'https://www.python.org/'),    spawn(get_page, 'https://www.yahoo.com/'),    spawn(get_page, 'https://github.com/'),]) print('run time is %s' %(time() - start_time))

實(shí)例:實(shí)現(xiàn)單線程下的socket并發(fā)

# Server from gevent import spawn, monkey; monkey.patch_all()from socket import socket, SOL_SOCKET, SO_REUSEADDR def server(ip='127.0.0.1', port=8080):    sk = socket()    sk.setsockopt(SOL_SOCKET, SO_REUSEADDR,1)    sk.bind((ip, port))    sk.listen(10)    while 1:        conn, addr = sk.accept()        spawn(task, conn)        print('Client', addr) def task(conn):    try:        while 1:            res = conn.recv(1472)            if not res:break            print(res.decode('UTF-8'))            conn.send(res.upper())    except Exception as e:        print(e)    finally:        conn.close() if __name__ == '__main__':    server()
# Clinet from socket import socket sk = socket()sk.connect_ex(('127.0.0.1', 8080)) while 1:    ret = input('>>>').strip()    sk.send(ret.encode('UTF-8'))    if not ret:break    print(sk.recv(1472).decode('UTF-8'))

關(guān)于yield:

from time import time # 在單線程中,如果存在多個函數(shù),如果有某個函數(shù)發(fā)生IO操作,你想讓程序馬上切換到另一個函數(shù)去執(zhí)行#  以此來實(shí)現(xiàn)一個假的并發(fā)現(xiàn)象。# 總結(jié):#    yield 只能實(shí)現(xiàn)單純的切換函數(shù)和保存函數(shù)狀態(tài)的功能#    不能實(shí)現(xiàn):當(dāng)某一個函數(shù)遇到io阻塞時,自動的切換到另一個函數(shù)去執(zhí)行#    目標(biāo)是:當(dāng)某一個函數(shù)中遇到IO阻塞時,程序能自動的切換到另一個函數(shù)去執(zhí)行#            如果能實(shí)現(xiàn)這個功能,那么每個函數(shù)都是一個協(xié)程##    但是  協(xié)程的本質(zhì)還是主要依靠于yield去實(shí)現(xiàn)的。##    如果只是拿yield去單純的實(shí)現(xiàn)一個切換的現(xiàn)象,你會發(fā)現(xiàn),跟本沒有程序串行執(zhí)行效率高 def consumer():    while 1:        x = yield        print(x) def producer():    g = consumer()    next(g)    [g.send(i) for i in range(100000)] start = time()producer()print('yield:', time() - start)

"

總結(jié)

以上是生活随笔為你收集整理的【Python协程的实现】的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。