進程是資源分配的最小單位,線程是CPU調度的最小單位python
進程
開銷大 數據隔離(資源分配的最小單位) 可以利用多核
線程
開銷小 資源共享(cpu調度的最小單位) 可以利用多核(Cpython解釋器下不行)
Cpython解釋器flask
高計算型 開多進程
高IO型 開多線程
因此大部分狀況下,基本上只要有網絡操做,開啓多線程必定能夠提升這一整個程序的cpu使用率
線程centos
GIL 全局解釋器鎖 致使了線程不能利用多核
線程之間數據安全的問題
Lock互斥鎖
Rlock遞歸鎖
死鎖現象: 同一個線程中,出現了兩把鎖,同時鎖兩個資源去進行某些操做
線程隊列
先進先出的隊列
後進先出的棧
優先級隊列
池
concurrent.futrues
處理文件操做(處理日誌文件) 處理網絡併發(請求爬蟲的頁面)
ThreadPoolExcutor ProcessPoolExcutor
submit
shutdown
map
result
add_done_callback
協程介紹安全
線程中可以放多個任務在一個線程中,其中的每個任務均可以成爲一個協程,每個協程均可以在一條線程中任意的切換 線程: cpython解釋器 只能用一核 只有有IO操做的多個任務才適合起多線程 協程的本質就是在單線程下,由用戶本身控制一個任務遇到io阻塞了就切換另一個任務去執行,以此來提高效率 優勢: 1.協程的切換開銷更小,屬於程序級別的切換,操做系統徹底感知不到,於是更加輕量級 2.單線程內就能夠實現併發的效果,最大限度地利用cpu 缺點: 1.協程的本質是單線程下,沒法利用多核,能夠是一個程序開啓多個進程,每一個進程內開啓多個線程,每一個線程內開啓協程 2.協程指的是單個線程,於是一旦協程出現阻塞,將會阻塞整個線程
串行執行網絡
import time def consumer(res): '''任務1:接收數據,處理數據'''
pass
def producer(): '''任務2:生產數據''' res=[] for i in range(10000000): res.append(i) return res start=time.time() #串行執行
res=producer() #寫成consumer(producer())會下降執行效率
consumer(res) stop=time.time() print(stop-start)
基於yield併發執行多線程
import time def consumer(): '''任務1:接收數據,處理數據'''
while True: x=yield
def producer(): '''任務2:生產數據''' g=consumer() next(g) for i in range(10000000): g.send(i) start=time.time() #基於yield保存狀態,實現兩個任務直接來回切換,即併發的效果
producer() stop=time.time() print(stop-start)
greenlet模塊介紹:併發
模塊安裝: pip install greenlet import time from greenlet import greenlet def eat(): print('start eating') # 保存當前函數的狀態 切換到對應的函數中去執行
g2.switch() time.sleep(1) print('eating finished') def sleep(): print('start sleeping') time.sleep(1) print('sleeping finished') g1.switch() # 建立了一個協程對象
g1 = greenlet(eat) # 建立了一個協程對象
g2 = greenlet(sleep) g1.switch()
yield 快
greenlet 慢app
gevent模塊介紹:框架
pip3 install gevent 使用協程 是爲了規避io,由於程序:遇到io就切換 gevent遇到IO就切換,基於greenlet進行切換 from threading import currentThread from gevent import monkey monkey.patch_all() import time # 內部使用了greenlet的切換機制 實現了遇到IO自動進行切換
import gevent def eat(): # DummyThread-1 僞線程
print('start eating',currentThread()) time.sleep(1) print('eating finished') def sleep(): # DummyThread-1 僞線程
print('start sleeping',currentThread()) time.sleep(1) print('sleeping finished') g_l = [] for i in range(5): #建立一個協程對象g1,spawn括號內第一個參數是函數名,後面能夠有多個參數,能夠是位置實參或關鍵字實參,都是傳給函數
g1 = gevent.spawn(eat) g2 = gevent.spawn(sleep) g_l.append(g1) g_l.append(g2) #等待g1結束 # g1.join() # g2.join()
gevent.joinall(g_l)
兩個協程模塊異步
gevent - 基於greenlet - 使用更方便 性能相對低 asyncio - 基於yield - 性能更好 flask 支持協程 gevent的 twisted sanic tornado 異步框架 用的是asyncio協程模塊
讓gevent識別的io操做是來自其餘模塊的(要寫在其餘模塊以前) from gevent import monkey monkey.patch_all() import gevent import time from urllib import request def get_page(url_t): ret = request.urlopen(url_t[0]) content = ret.read() with open(url_t[1]+'_new','wb') as f: f.write(content) url_lst = [ ('http://www.sogou.com','sogou'), ('http://www.baidu.com','baidu'), ('http://www.douban.com','douban'), ('http://www.cnblogs.com','cnblogs'), ('https://www.redhat.com/en','redhat'), ('https://www.centos.org/','centos'), ('https://www.liaoxuefeng.com/','lxf'), ('http://www.jd.com','jd'), ('http://www.taobao.com','tb') ] start = time.time() g_l = [] for url_t in url_lst: g = gevent.spawn(get_page,url_t) g_l.append(g) gevent.joinall(g_l) print(time.time() - start)
協程模擬socket server多客戶端通訊
server端: from gevent import monkey monkey.patch_all() import socket import gevent def talk(conn): while True: msg = conn.recv(1024).decode() ret_msg = msg.upper().encode() conn.send(ret_msg) sk = socket.socket() sk.bind(('127.0.0.1',9000)) sk.listen() while True: conn,_ = sk.accept() g = gevent.spawn(talk,conn) client端: import socket import threading def client(): sk = socket.socket() sk.connect(('127.0.0.1',9000)) while True: sk.send(b'hello') msg = sk.recv(1024) print(msg) for i in range(500): threading.Thread(target=client).start()
總結:
asyncio的狀態切換是由yield關鍵字完成的 greenlet 擴展模塊 進行函數中代碼的來回切換 在其餘編譯型語言中 因爲多線程能夠利用多核 因此協程這個概念被弱化了 對於Cpython 多線程也不能利用多核 因此協程這個概念就變得相當重要了 協程的本質是一條線程 1.不能利用多核 2.用戶級的概念,操做系統不可見 3.協程不存在數據安全問題 切換 一條線程 操做系統感知到線程一直在運行 就減小了線程進入阻塞狀態的次數,從而提升了效率 協程實際的定義: 在一條線程之間來回切換 1.yield和函數做比較 yield進行切換實際上會浪費時間,即使是程序級別的切換也會浪費時間 2.500個client併發的server的效率計算,協程的處理併發能力 很強 3.geturl爬蟲的練習,協程和函數作對比,協程的速度快 gevent去處理問題實際上很簡便 直接扔給spawn就好了 建議 進程數 cpu個數的1-2倍 對於其餘的語言 來講 進程是不常開的 線程數 20 協程數 500併發 進程 + 協程 開5個進程,在進程中開協程 既可以利用多核 也能夠提升處理IO的效率 線程 處理 文件 ,input 進程 8個進程 協程 1000個 4*500 = 8000併發