協程--gevent模塊(單線程高併發)

先惡補一下知識點,上節回顧

上下文切換:當CPU從執行一個線程切換到執行另一個線程的時候,它須要先存儲當前線程的本地的數據,程序指針等,而後載入另外一個線程的本地數據,程序指針等,最後纔開始執行。這種切換稱爲「上下文切換」(「context switch」)html

      CPU會在一個上下文中執行一個線程,而後切換到另一個上下文中執行另一個線程,上下文切換並不廉價。若是沒有必要,應該減小上下文切換的發生python

進程: 一個程序須要運行所需的資源的集合
每一個進程數據是獨立的
每一個進程裏至少有一個線程
每一個進程裏有能夠多有個線程
線程數據是共享的
進程間共享數據的代價是高昂的,因此要儘可能避免進程間的數據共享
線程間的數據原本就是共享的
線程要修改同一份數據,必須加鎖,互斥鎖mutex
生產者消費者:1.解耦2.提升程序的運行效率,把中間等待的時間省去

多線程場景: IO密集型,由於IO操做基本不佔用CPU,因此多用在web,爬蟲,socket交互
多進程場景:CPU密集型,大數據分析,金融分析,這樣用的IO就不多,由於這個進程會進行大量的運算, 可是若是切換了進程,就會變慢
 


協程

協程:微線程, 協程是一種用戶態的輕量級線程,CPU不知道它的存在,web

協程擁有本身的寄存器上下文和棧.協程調度切換時,將寄存器上下文和棧保存到其餘地方,在切回來的時候,恢復先前保存的寄存器上下文和棧,編程

所以協程能保留上一次調用時的狀態(即全部局部狀態的一個特定組合,),每次過程重入時,就至關於上一次調用的狀態, 也就是進入上一次離開時所處邏輯流的位置網頁爬蟲

 

協程的好處:(是程序級別切換,CPU是不知道的.)多線程

1.無需線程上下文切換,併發

2.無需原子操做鎖定及同步開銷  ,  什麼是原子操做?  :是不須要同步的!!,是指不會被線程調度打斷的操做;這種操做一旦開始,就運行到結束,中間不會有任何  context switch(切換到另外一個線程,)app

                         原子操做能夠是一個步驟,也能夠是多個操做步驟,可是其順序是不能夠被打亂,或者切割掉只執行部分。視做總體是原子性的核心異步

3.方便切換控制流,簡化編程模型socket

4.高併發 + 高擴展 + 低成本 : 一個CPU支持上萬的協程都不是問題,因此很適合用於高併發處理

壞處-----:

1.沒法利用多核資源,協程的本質是個單線程,它不能同時將單個CPU的多個核用上, 協程須要配合進程才能在多CPU上,  適用於CPU密集型應用

2.進程阻塞 (Blocking)  操做 如IO操做時,會阻塞掉整個程序

----什麼條件符合才能稱之爲協程?

  A.必須在只有一個單線程裏實現併發

  B.修改共享數據不須要加鎖

  C.用戶程序裏本身保持多個控制流的上下文棧

  D.一個協程遇到IO操做自動切換到其餘協程!!!!!!

重點來了。。。。。 大量的模塊知識點---我但願我之後還能記起來----汗顏!

Greenlet模塊

greenlet是一個用C實現的協程模塊,相比與python自帶的yield,它能夠使你在任意函數之間隨意切換,而不需把這個函數先聲明爲generator(生成器)

from greenlet import greenlet

def test1():
    print('test1:我是1')
    gr2.switch()    #切換到test2
    print('test1:我是1.1')
    gr2.switch()
def test2():
    print('test2:我是2')
    gr1.switch()  #切換到test1
    print('test2:我是2.2')


gr1=greenlet(test1)
gr2=greenlet(test2)
gr1.switch()      #先切換到test1
>>
test1:我是1
test2:我是2
test1:我是1.1
test2:我是2.2

swich()  就是切換,   按執行順序--  可是遇到IO操做 好像並無自動切換

Gevent模塊

Gevent 是一個第三方庫,能夠輕鬆經過gevent實現併發同步或異步編程,在gevent中用到的主要模式是Greenlet, 它是以C擴展模塊形式接入Python的輕量級協程。 Greenlet所有運行在主程序操做系統進程的內部,但它們被協做式地調度。  

 這裏使用gevent.sleep 來獲取控制權    

import gevent

def func1():
    print('\033[31;1m我是func1\033[0m')
    gevent.sleep(3)
    print('\033[31;1m我是func1.1--我上面有3秒\033[0m')

def func2():
    print('\033[32;1m我是func2.\033[0m')
    gevent.sleep(2)
    print('\033[32;1m我是func2.1 我上面有2秒\033[0m')

def func3():
    print('\033[32;1m我是func3.\033[0m')
    gevent.sleep(2)
    print('\033[32;1m我是func3.1我上面有2秒\033[0m')

gevent.joinall([gevent.spawn(func1),
                gevent.spawn(func2),
                gevent.spawn(func3),])

這裏會按照sleep 設置來執行      必定會先打印出func2-->func3-->func1  

同步和異步的性能區別

import gevent


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


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


def asynchronous():
    #threads = [gevent.spawn(task, i) for i in range(10)]
    threads=[]
    for i in range(10):
        threads.append(gevent.spawn(task,i))
    gevent.joinall(threads)    #執行流程只會在 全部greenlet執行完後纔會繼續向下走


print('Synchronous:')
synchronous()

print('Asynchronous:')
asynchronous()

Synchrounous:定義了同步的函數:定義一個for循環。依次把內容傳輸給task函數,而後打印執行結果-----

Aynchrounous:定義了異步的函數: 這裏用到了一個gevent.spawn方法,就是產生的意思. gevent.joinall 也就是等待因此操做都執行完畢

                gevent.spawn 能夠調用函數

但是咱們通常也不會這麼用。去故意的設置一個gevent.sleep來切換  ,下面就來在實際場景中應用

遇到IO阻塞,自動切換任務

這裏就用到了簡單的網頁爬蟲環境中,  操做IO的時候。自動切換。這裏就用到了猴子補丁(monkey.patch_all(), 知道這是運行時,動態修改已有的代碼,而不須要修改原始代碼)

from gevent import monkey
import gevent
import time
from urllib.request import urlopen
monkey.patch_all()
#對比得出 協程 運行出的更快
#IO阻塞 自動切換任務。。
def say(url):
    print('get url',url)
    resp = urlopen(url)
    data = resp.read()
    print(len(data),url)
t1_start = time.time()
say('http://www.xiaohuar.com/')
say('http://www.oldboyedu.com/')
print("普通--time cost",time.time() - t1_start)

t2_stat = time.time()
gevent.joinall(
    [gevent.spawn(say,'http://www.xiaohuar.com/'),
     gevent.spawn(say,'http://www.oldboyedu.com/'),
     gevent.spawn(say,'http://weibo.com/MMbdzx?from=myfollow_all&is_all=1#_rnd1482040021384')]
)
print("gevent---time cost",time.time() - t2_stat)

因爲切換時再IO操做就自動完成,因此須要gevent修改py自帶的標準庫,這一過程在啓動時經過monkey patch完成  -- 

對比2次運行完畢的時間,很明顯的看到gevent在處理上,更加有優點,

 到了這裏簡單的就算完了。。。來進入總結概念的部分--------http://www.cnblogs.com/zcqdream/p/6196948.html   

經過gevent來實現單線程下的多socket併發

server 端,採用gevent協程

 1 import sys
 2 import socket
 3 import time
 4 import gevent
 5  
 6 from gevent import socket,monkey
 7 monkey.patch_all()
 8  
 9  
10 def server(port):
11     s = socket.socket()
12     s.bind(('0.0.0.0', port))
13     s.listen(500)
14     while True:
15         cli, addr = s.accept()
16         gevent.spawn(handle_request, cli)   #gevent.spwan調用handle參數並傳參
17  
18  
19  
20 def handle_request(conn):
21     try:
22         while True:
23             data = conn.recv(1024)
24             print("recv:", data)
25             conn.send(data)
26             if not data:             
27                 conn.shutdown(socket.SHUT_WR)
28  
29     except Exception as  ex:
30         print(ex)
31     finally:
32         conn.close()
33 if __name__ == '__main__':
34     server(8001)

client端

單線程的客戶端

 1 import socket
 2  
 3 HOST = 'localhost'    # The remote host
 4 PORT = 8001           # The same port as used by the server
 5 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 6 s.connect((HOST, PORT))
 7 while True:
 8     msg = bytes(input(">>:"),encoding="utf8")
 9     s.sendall(msg)
10     data = s.recv(1024)
11     #print(data)
12  
13     print('Received', repr(data))
14 s.close()

多線程客戶端去請求

import socket
import threading

def sock_conn():

    client = socket.socket()

    client.connect(("localhost",8001))
    count = 0
    while True:
        #msg = input(">>:").strip()
        #if len(msg) == 0:continue
        client.send( ("hello %s" %count).encode("utf-8"))

        data = client.recv(1024)

        print("[%s]recv from server:" % threading.get_ident(),data.decode()) #結果
        count +=1
    client.close()


for i in range(100):
    t = threading.Thread(target=sock_conn)
    t.start()
相關文章
相關標籤/搜索