日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

????ˉ?????????????Python????¨?

之前我們學(xué)習(xí)了線程、進(jìn)程的概念,了解了在操作系統(tǒng)中進(jìn)程是資源分配的最小單位,線程是CPU調(diào)度的最小單位。按道理來說我們已經(jīng)算是把cpu的利用率提高很多了。但是我們知道無論是創(chuàng)建多進(jìn)程還是創(chuàng)建多線程來解決問題,都要消耗一定的時間來創(chuàng)建進(jìn)程、創(chuàng)建線程、以及管理他們之間的切換。

  隨著我們對于效率的追求不斷提高,基于單線程來實(shí)現(xiàn)并發(fā)又成為一個新的課題,即只用一個主線程(很明顯可利用的cpu只有一個)情況下實(shí)現(xiàn)并發(fā)。這樣就可以節(jié)省創(chuàng)建線進(jìn)程所消耗的時間。

  為此我們需要先回顧下并發(fā)的本質(zhì):切換+保存狀態(tài)

  cpu正在運(yùn)行一個任務(wù),會在兩種情況下切走去執(zhí)行其他的任務(wù)(切換由操作系統(tǒng)強(qiáng)制控制),一種情況是該任務(wù)發(fā)生了阻塞,另外一種情況是該任務(wù)計算的時間過長

   

一篇文章搞懂Python協(xié)程

 

  PS:在介紹進(jìn)程理論時,提及進(jìn)程的三種執(zhí)行狀態(tài),而線程才是執(zhí)行單位,所以也可以將上圖理解為線程的三種狀態(tài)

   一:其中第二種情況并不能提升效率,只是為了讓cpu能夠雨露均沾,實(shí)現(xiàn)看起來所有任務(wù)都被“同時”執(zhí)行的效果,如果多個任務(wù)都是純計算的,這種切換反而會降低效率。

  為此我們可以基于yield來驗證。yield本身就是一種在單線程下可以保存任務(wù)運(yùn)行狀態(tài)的方法,我們來簡單復(fù)習(xí)一下

#1 yiled可以保存狀態(tài),yield的狀態(tài)保存與操作系統(tǒng)的保存線程狀態(tài)很像,但是yield是代碼級別控制的,更輕量級
#2 send可以把一個函數(shù)的結(jié)果傳給另外一個函數(shù),以此實(shí)現(xiàn)單線程內(nèi)程序之間的切換

單純地切換反而會降低運(yùn)行效率

#串行執(zhí)行
import time
def consumer(res):
    '''任務(wù)1:接收數(shù)據(jù),處理數(shù)據(jù)'''
    pass

def producer():
    '''任務(wù)2:生產(chǎn)數(shù)據(jù)'''
    res=[]
    for i in range(10000000):
        res.Append(i)
    return res

start=time.time()
#串行執(zhí)行
res=producer()
consumer(res) #寫成consumer(producer())會降低執(zhí)行效率
stop=time.time()
print(stop-start) #1.5536692142486572



#基于yield并發(fā)執(zhí)行
import time
def consumer():
    '''任務(wù)1:接收數(shù)據(jù),處理數(shù)據(jù)'''
    while True:
        x=yield

def producer():
    '''任務(wù)2:生產(chǎn)數(shù)據(jù)'''
    g=consumer()
    next(g)
    for i in range(10000000):
        g.send(i)

start=time.time()
#基于yield保存狀態(tài),實(shí)現(xiàn)兩個任務(wù)直接來回切換,即并發(fā)的效果
#PS:如果每個任務(wù)中都加上打印,那么明顯地看到兩個任務(wù)的打印是你一次我一次,即并發(fā)執(zhí)行的.
producer()

stop=time.time()
print(stop-start) #2.0272178649902344

二:第一種情況的切換。在任務(wù)一遇到io情況下,切到任務(wù)二去執(zhí)行,這樣就可以利用任務(wù)一阻塞的時間完成任務(wù)二的計算,效率的提升就在于此。

yield無法做到遇到io阻塞

import time
def consumer():
    '''任務(wù)1:接收數(shù)據(jù),處理數(shù)據(jù)'''
    while True:
        x=yield

def producer():
    '''任務(wù)2:生產(chǎn)數(shù)據(jù)'''
    g=consumer()
    next(g)
    for i in range(10000000):
        g.send(i)
        time.sleep(2)

start=time.time()
producer() #并發(fā)執(zhí)行,但是任務(wù)producer遇到io就會阻塞住,并不會切到該線程內(nèi)的其他任務(wù)去執(zhí)行

stop=time.time()
print(stop-start)

對于單線程序,我們不可避免程序中出現(xiàn)io操作,但如果我們能在自己的程序中(即用戶程序級別,而非操作系統(tǒng)級別)控制單線程下的多個任務(wù)能在一個任務(wù)遇到io阻塞時就切換到另外一個任務(wù)去計算,這樣就保證了該線程能夠最大限度地處于就緒態(tài),即隨時都可以被cpu執(zhí)行的狀態(tài),相當(dāng)于我們在用戶程序級別將自己的io操作最大限度地隱藏起來,從而可以迷惑操作系統(tǒng),讓其看到:該線程好像是一直在計算,io比較少,從而更多的將cpu的執(zhí)行權(quán)限分配給我們的線程。

協(xié)程的本質(zhì)就是在單線程下,由用戶自己控制一個任務(wù)遇到io阻塞了就切換另外一個任務(wù)去執(zhí)行,以此來提升效率。為了實(shí)現(xiàn)它,我們需要找尋一種可以同時滿足以下條件的解決方案

#1. 可以控制多個任務(wù)之間的切換,切換之前將任務(wù)的狀態(tài)保存下來,以便重新運(yùn)行時,可以基于暫停的位置繼續(xù)執(zhí)行。
#2. 作為1的補(bǔ)充:可以檢測io操作,在遇到io操作的情況下才發(fā)生切換

協(xié)程介紹

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

  需要強(qiáng)調(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操作的切換與效率無關(guān))

對比操作系統(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ì)是單線程下,無法利用多核,可以是一個程序開啟多個進(jìn)程,每個進(jìn)程內(nèi)開啟多個線程,每個線程內(nèi)開啟協(xié)程
#2. 協(xié)程指的是單個線程,因而一旦協(xié)程出現(xiàn)阻塞,將會阻塞整個線程

協(xié)程特點(diǎn)

  • 必須在只有一個單線程里實(shí)現(xiàn)并發(fā)
  • 修改共享數(shù)據(jù)不需加鎖
  • 用戶程序里自己保存多個控制流的上下文棧
  • 附加:一個協(xié)程遇到IO操作自動切換到其它協(xié)程(如何實(shí)現(xiàn)檢測IO,yield、greenlet都無法實(shí)現(xiàn),就用到了gevent模塊(select機(jī)制))

Greenlet模塊

安裝

pip3 install greenlet

greenlet實(shí)現(xiàn)狀態(tài)切換

from greenlet import greenlet

def eat(name):
    print('%s eat 1' %name)
    g2.switch('egon')
    print('%s eat 2' %name)
    g2.switch()
def play(name):
    print('%s play 1' %name)
    g1.switch()
    print('%s play 2' %name)

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

g1.switch('egon')#可以在第一次switch時傳入?yún)?shù),以后都不需要

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

效率對比

#順序執(zhí)行
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只是提供了一種比generator更加便捷的切換方式,當(dāng)切到一個任務(wù)執(zhí)行時如果遇到io,那就原地阻塞,仍然是沒有解決遇到IO自動切換來提升效率的問題。

  單線程里的這20個任務(wù)的代碼通常會既有計算操作又有阻塞操作,我們完全可以在執(zhí)行任務(wù)1時遇到阻塞,就利用阻塞的時間去執(zhí)行任務(wù)2。。。。如此,才能提高效率,這就用到了Gevent模塊。

Gevent模塊

安裝

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)度。

用法

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

g2=gevent.spawn(func2)

g1.join() #等待g1結(jié)束

g2.join() #等待g2結(jié)束

#或者上述兩步合作一步: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('主')

  上例gevent.sleep(2)模擬的是gevent可以識別的io阻塞,而time.sleep(2)或其他的阻塞,gevent是不能直接識別的需要用下面一行代碼,打補(bǔ)丁,就可以識別了

  from gevent import monkey;monkey.patch_all()必須放到被打補(bǔ)丁者的前面,如time,socket模塊之前

  或者我們干脆記憶成:要用gevent,需要將from gevent import monkey;monkey.patch_all()放到文件的開頭

from gevent import monkey;monkey.patch_all()

import gevent
import time
def eat():
    print('eat food 1')
    time.sleep(2)
    print('eat food 2')

def play():
    print('play 1')
    time.sleep(1)
    print('play 2')

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

我們可以用threading.current_thread().getName()來查看每個g1和g2,查看的結(jié)果為DummyThread-n,即假線程

查看threading.current_thread().getName()

from gevent import monkey;monkey.patch_all()
import threading
import gevent
import time
def eat():
    print(threading.current_thread().getName())
    print('eat food 1')
    time.sleep(2)
    print('eat food 2')

def play():
    print(threading.current_thread().getName())
    print('play 1')
    time.sleep(1)
    print('play 2')

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

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)
    print('DONE')
    
if __name__ == '__main__':
    print('Synchronous:')
    synchronous()
    print('Asynchronous:')
    asynchronous()
#  上面程序的重要部分是將task函數(shù)封裝到Greenlet內(nèi)部線程的gevent.spawn。
#  初始化的greenlet列表存放在數(shù)組threads中,此數(shù)組被傳給gevent.joinall 函數(shù),
#  后者阻塞當(dāng)前流程,并執(zhí)行所有給定的greenlet任務(wù)。執(zhí)行流程只會在 所有g(shù)reenlet執(zhí)行完后才會繼續(xù)向下走。

Gevent之應(yīng)用舉例一

協(xié)程應(yīng)用:爬蟲

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))

Gevent之應(yīng)用舉例二

通過gevent實(shí)現(xiàn)單線程下的socket并發(fā)

注意 :from gevent import monkey;monkey.patch_all()一定要放到導(dǎo)入socket模塊之前,否則gevent無法識別socket的阻塞

server

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

#如果不想用money.patch_all()打補(bǔ)丁,可以用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)

client

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)
    print(msg.decode('utf-8'))

多線程并發(fā)多個客戶端

from threading import Thread
from socket import *
import threading

def client(server_ip,port):
    c=socket(AF_INET,SOCK_STREAM) #套接字對象一定要加到函數(shù)內(nèi),即局部名稱空間內(nèi),放在函數(shù)外則被所有線程共享,則大家公用一個套接字對象,那么客戶端端口永遠(yuǎn)一樣了
    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()

分享到:
標(biāo)簽:Python 協(xié)程
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定