上下文切換:當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是一個用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中用到的主要模式是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的時候。自動切換。這裏就用到了猴子補丁(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
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()