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

歡迎訪問 生活随笔!

生活随笔

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

综合教程

【Python协程的实现】

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

"

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

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

協(xié)程示例代碼:

# 最簡(jiǎn)單的協(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。一句話說(shuō)明什么是協(xié)程:協(xié)程是一種用戶態(tài)的輕量級(jí)線程,即協(xié)程是由用戶程序自己控制調(diào)度的.

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

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

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

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

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

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

缺點(diǎn)如下:

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

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

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

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

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

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

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


二、greenlet模塊

windows安裝命令:pip3 install greenlet

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

# 效率對(duì)比 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)切到一個(gè)任務(wù)執(zhí)行時(shí),如果遇到io操作,便會(huì)原地阻塞,仍然沒有解決遇到io自動(dòng)切換來(lái)提升效率的問題.

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


三、gevent模塊

windows安裝命令:pip3 install gevent

gevent模塊是一個(gè)第三方庫(kù),可以輕松通過gevent實(shí)現(xiàn)并發(fā)同步或異步編程,在gevent中用到的主要模式是greenlet,它是以C擴(kuò)展模塊形式接入Python的輕量級(jí)協(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)建一個(gè)協(xié)程對(duì)象g,spawn括號(hào)內(nèi)的第一個(gè)參數(shù)是函數(shù)名,后面可以有多個(gè)參數(shù),可以是位置實(shí)參或關(guān)鍵字實(shí)參,都是傳給函數(shù)的func。

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

g.value:拿到func的返回值

# 遇到io主動(dòng)切換 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可以識(shí)別的io阻塞,而time.sleep()或其它的阻塞,gevent是不能直接識(shí)別的,需要用到下面一行代碼,打補(bǔ)丁,便可識(shí)別: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()來(lái)查看每個(gè)協(xié)程的變量名都會(huì)為: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()

同步與異步效率對(duì)比

# 同步與異步效率對(duì)比 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 # 在單線程中,如果存在多個(gè)函數(shù),如果有某個(gè)函數(shù)發(fā)生IO操作,你想讓程序馬上切換到另一個(gè)函數(shù)去執(zhí)行#  以此來(lái)實(shí)現(xiàn)一個(gè)假的并發(fā)現(xiàn)象。# 總結(jié):#    yield 只能實(shí)現(xiàn)單純的切換函數(shù)和保存函數(shù)狀態(tài)的功能#    不能實(shí)現(xiàn):當(dāng)某一個(gè)函數(shù)遇到io阻塞時(shí),自動(dòng)的切換到另一個(gè)函數(shù)去執(zhí)行#    目標(biāo)是:當(dāng)某一個(gè)函數(shù)中遇到IO阻塞時(shí),程序能自動(dòng)的切換到另一個(gè)函數(shù)去執(zhí)行#            如果能實(shí)現(xiàn)這個(gè)功能,那么每個(gè)函數(shù)都是一個(gè)協(xié)程##    但是  協(xié)程的本質(zhì)還是主要依靠于yield去實(shí)現(xiàn)的。##    如果只是拿yield去單純的實(shí)現(xiàn)一個(gè)切換的現(xiàn)象,你會(huì)發(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协程的实现】的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

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