線程隊列-線程池-協程-greenlet-geventcss
隊列的各類方法:前端
put:往隊列裏面放數據python
get:從隊列取數據web
put_nowait:面試
get_nowait,不阻塞,隊列還有值的時候,直接跳過,沒有值的時候報錯(但要是get的話,會阻塞),正則表達式
full,檢查隊列是否滿了,返回bool值安全
empty:檢查隊列是否空了,返回bool值多線程
qsize:輸出隊列的大小,併發
自帶鎖,數據安全,app
queue隊列:使用import queue,用法與進程Queue同樣,先進先出
queue.LifoQueue:後進先出,(棧)
queue.Priority():存儲數據時可設置優先級的隊列,
from queue import PriorityQueue q=PriorityQueue() #優先級隊列 q.put(12,'bbb') q.put(2,'abb') q.put(20,'vbb') q.put(1,'bbb') print(q.get()) print(q.get()) print(q.get()) print(q.get()) 輸出: 按數字大小輸出,其實是按ascii碼的前後輸出的, 1 2 12 20
Threading 沒有線程池,multiprocessing,有進程池,concurrent.futures:幫助開發人員管理線程池和進程池,
from threading import currentThread,get_ident
from concurrent.futures import ThreadPoolExecutor(啓動線程池的類)
from concurrent.futures import ProcssPoolExecutor(啓動進程池的類)
基本介紹
1 #1 介紹 2 concurrent.futures模塊提供了高度封裝的異步調用接口 3 ThreadPoolExecutor:線程池,提供異步調用 4 ProcessPoolExecutor: 進程池,提供異步調用 5 Both implement the same interface, which is defined by the abstract Executor class. 6 7 #2 基本方法 8 #submit(fn, *args, **kwargs) 9 異步提交任務 10 11 #map(func, *iterables, timeout=None, chunksize=1) 12 取代for循環submit的操做 13 14 #shutdown(wait=True) 15 至關於進程池的pool.close()+pool.join()操做 16 wait=True,等待池內全部任務執行完畢回收完資源後才繼續 17 wait=False,當即返回,並不會等待池內的任務執行完畢 18 但無論wait參數爲什麼值,整個程序都會等到全部任務執行完畢 19 submit和map必須在shutdown以前 20 21 #result(timeout=None) 22 取得結果 23 24 #add_done_callback(fn) 25 回調函數
submit,map,shutdown的結合用法
1 import time 2 from threading import currentThread,get_ident #查看當前的線程(currentThread) 3 from concurrent.futures import ThreadPoolExecutor 4 5 def func(i): 6 print('in %s' %currentThread(),get_ident(),i)#顯示當前的線程(包含線程ID,跟線程名) 7 #輸出的線程ID有重複的,證實用到了回收的線程 8 9 t=ThreadPoolExecutor(5)#實例化一個線程,開啓線程池,這裏5個線程 10 11 for i in range(20): 12 t.submit(func,i)#異步提交任務,這裏進源碼看submit,函數傳參的問題 13 t.shutdown()#至關於進程池的Pool.close()+Pool.join(),默認爲True,爲真時,才起做用, 14 print('main:',currentThread(),get_ident()) #主線程 15 16 17 兩個實現起線程池的方法的區別,對比輸出結果, 18 19 20 from threading import current_thread,get_ident 21 from concurrent.futures import ThreadPoolExecutor 22 23 def func(i): #必需要進行傳參, 24 print(888,current_thread()) 25 26 t=ThreadPoolExecutor(5)#線程池,起5個線程, 27 t.map(func,range(20))#map至關於,把可迭代對象的每一個值取出來傳給func函數,
result(timeout)
1 import os,time 2 from concurrent.futures import ThreadPoolExecutor 3 4 def func(i): 5 time.sleep(0.5) 6 print(i,) 7 return i+20 8 9 t=ThreadPoolExecutor(5) 10 lst=[] 11 for i in range(20): 12 ret=t.submit(func,i) 13 # print(ret.result()) #取得結果, 14 lst.append(ret) 15 t.shutdown() 16 for ret in lst: #對比上面的結果,上面的又變成同步的了,因此須要建立一個了列表 17 print(ret.result()) 18 print('主線程')
add_done_callback(fn):回調函數
線程池的回調函數執行,是在線程池裏的各個線程.
from threading import get_ident from concurrent.futures import ThreadPoolExecutor def func(i): print(i,get_ident()) return get_ident() def back(fn): print('in back',fn.result())#返回的是函數的地址,取值的話,須要.result() t=ThreadPoolExecutor(5) for i in range(20): t.submit(func,i).add_done_callback(back)#回調函數,
進程版本的各個例子
import os from concurrent.futures import ProcessPoolExecutor def func(i): print(i,os.getpid()) return i*3 def back(fn): print(fn.result(),os.getpid()) if __name__ == '__main__': p=ProcessPoolExecutor(5) lst=[] for i in range(20): ret=p.submit(func,i)#得到返回值 lst.append(ret) # p.submit(func,i).add_done_callback(back)#回調函數 p.shutdown() for ret in lst: ret.result()
進程/線程池總結:
multiprocessing模塊自帶進程池的
threading模塊是沒有線程池的
concurrent.futures 進程池 和 線程池
高度封裝
進程池/線程池的統一的使用方式
建立線程池/進程池 ProcessPoolExecutor ThreadPoolExecutor
ret = t.submit(func,arg1,arg2....) 異步提交任務
ret.result() 獲取結果,若是要想實現異步效果,應該是使用列表
map(func,iterable)
shutdown close+join 同步控制的
add_done_callback 回調函數,在回調函數內接收的參數是一個對象,須要經過result來獲取返回值
回調函數仍然在主進程中執行 進程池
回調函數是在池子裏的各個線程執行 線程池
概念性的面試題
web框架
爬蟲/自動化開發
爬蟲 : 訪問大量的網頁,對網頁代碼進行處理
正則表達式
字符串處理
前端
併發
運維 : 一堆機器 一堆程序 你去維護
自動化開發/運維開發 : 開發一些程序 讓機器的維護/程序的維護自動化起來
運維基礎
python的基礎開發
併發
前端
協程
以前咱們學習了線程、進程的概念,瞭解了在操做系統中進程是資源分配的最小單位,線程是CPU調度的最小單位。按道理來講咱們已經算是把cpu的利用率提升不少了。可是咱們知道不管是建立多進程仍是建立多線程來解決問題,都要消耗必定的時間來建立進程、建立線程、以及管理他們之間的切換。
隨着咱們對於效率的追求不斷提升,基於單線程來實現併發又成爲一個新的課題,即只用一個主線程(很明顯可利用的cpu只有一個)狀況下實現併發。這樣就能夠節省建立線進程所消耗的時間。
爲此咱們須要先回顧下併發的本質:切換+保存狀態
cpu正在運行一個任務,會在兩種狀況下切走去執行其餘的任務(切換由操做系統強制控制),一種狀況是該任務發生了阻塞,另一種狀況是該任務計算的時間過長
ps:在介紹進程理論時,說起進程的三種執行狀態,而線程纔是執行單位,因此也能夠將上圖理解爲線程的三種狀態
對於單線程下,咱們不可避免程序中出現io操做,但若是咱們能在本身的程序中(即用戶程序級別,而非操做系統級別)控制單線程下的多個任務能在一個任務遇到io阻塞時就切換到另一個任務去計算,這樣就保證了該線程可以最大限度地處於就緒態,即隨時均可以被cpu執行的狀態,至關於咱們在用戶程序級別將本身的io操做最大限度地隱藏起來,從而能夠迷惑操做系統,讓其看到:該線程好像是一直在計算,io比較少,從而更多的將cpu的執行權限分配給咱們的線程。
協程的本質就是在單線程下,由用戶本身控制一個任務遇到io阻塞了就切換另一個任務去執行,以此來提高效率。爲了實現它,咱們須要找尋一種能夠同時知足如下條件的解決方案:
#1. 能夠控制多個任務之間的切換,切換以前將任務的狀態保存下來,以便從新運行時,能夠基於暫停的位置繼續執行。 #2. 做爲1的補充:能夠檢測io操做,在遇到io操做的狀況下才發生切換
協程介紹
協程:是單線程下的併發,又稱微線程,纖程.英文名:Coroutine,
協程是一種用戶態的輕量級線程,即協程是由用戶程序本身控制調度的.
注意:
python的線程屬於內核級別的,即由操做系統控制調度,(如單線程遇到io或執行時間過長就會被迫交出cpu執行權限,切換其餘線程執行)
單線程內開啓協程,一旦遇到io,就會從應用程序級別(而非操做系統)控制切換,依次來提高效率(非io操做的切換去效率無關)
對比操做系統控制線程的切換,用戶在多線程內控制協程的切換,
優勢:
1,協程的切換開銷更小,屬於程序級別的切換,操做系統徹底感知不到,於是更加輕量級
2,多線程內就能夠實現併發的效果,最大限度地利用cpu.
缺點:
1,協程的本質是單線程下,沒法利用多核,能夠是一個程序開啓多個進程,每一個進程內開啓多個線程,每一個線程內開啓協程,
2,協程指的是單個線程,於是一旦協程出現阻塞,將會阻塞整個線程,
總結協程特色:
1,必須在只有一個單線程裏實現併發
2,修改共享數據不需加鎖
3,用戶程序裏本身保存多個控制流的上下文棧
4,附加:一個協程遇到IO操做自動切換到其它協程(如何實現檢測IO,yield,greenlet都沒法實現,就用到了gevent模塊)
安裝:在cmd裏輸入命令:pep3 install greenlet
yield在兩個函數之間的應用.
解說yield的執行
1 def func(): 2 print(22) 3 x=yield 'aaa' 4 print(x) 5 print(555) 6 y=yield 'bbb' 7 print(y) 8 print(666) 9 10 g=func() #拿到一個生成器函數,執行到此行,沒有打印任何東西 11 print(next(g)) #執行到此,打印22,拿到yield的返回值:aaa,共打印:222,aaa,而後阻塞,沒有再輸出 12 print(g.send(333)) #執行到此,遇到send,繼續執行把333,傳給第三行的yield,而後打印333,打印555,拿到yield的返回值:bbb,共輸出:333,555,bbb 13 14 # 輸出: 15 # 22 16 # aaa 17 # 333 18 # 555 19 # bbb
1 def func1(): 2 while 1: 3 print(222) 4 x=yield 5 print(x) 6 print(666) 7 8 def func2(): 9 g=func1() 10 next(g) 11 for i in range(3): 12 g.send(i) 13 func2() 14 15 # 輸出: 16 # 222 17 # 0 18 # 666 19 # 222 20 # 1 21 # 666 22 # 222 23 # 2 24 # 666 25 # 222
用yield實現消費者和生產者模型,
1 def consumer(): 2 while True: 3 x = yield 4 print(x) 5 def producer(): 6 g = consumer() 7 next(g) # 預激 8 for i in range(10): 9 g.send(i) 10 producer()
yield只有程序之間的切換,沒有重利用任何IO操做的時間,
程序執行的上下文切換的工具.
greenlet,gevent,第三方模塊,安裝,
1,cmd命令行:輸出命令:pip3 insatll greenlet(若是是python2版本的,就是pip2)
2,在pycharm裏面,文件-->設置-->項目-->Project Iterpreter-->右上角(加號)-->在輸入框輸入要安裝的模塊-->左下角-->Install Package-->回上一級菜單-->肯定-->重啓pycharm(或者筆記本)
greenlet程序上下文切換
使用greenlet模塊
1 import time 2 from greenlet import greenlet 3 def eat(): 4 print('吃') 5 time.sleep(1) 6 g2.switch()#執行到此,切換到play函數,且下次今後處開始接着執行 7 print('吃完了') 8 time.sleep(1) 9 g2.switch()#執行到此,切換到play函數, 10 def play(): 11 print('玩兒') 12 time.sleep(1) 13 g1.switch() #執行到此,切換到eat函數, 14 print('玩美了') 15 time.sleep(1) 16 g1=greenlet(eat)#greenlet是一個類,此行是實例化了一個對象,傳一個eat函數 17 g2=greenlet(play) 18 g1.switch() #切換,開啓g1,此處再也不是start(),要注意, 19 20 # 輸出: 21 # 吃 22 # 玩兒 23 # 吃完了 24 # 玩美了
gevent模塊
安裝:pip3 install gevent(cmd命令行處輸入命令安裝)
greenlet是gevent的低層
gevent是基於greenlet實現的
python代碼在控制程序的切換
使用gevent來寫greenlet的例子
import gevent import time def eat(): print('吃') time.sleep(2) print('吃完了') def play(): print('玩兒') time.sleep(1) print('玩兒美了') g1 = gevent.spawn(eat) #此處沒有詳細解釋spawn g2 = gevent.spawn(play) #單純的代碼寫到這裏,執行的話,什麼都沒有打印(輸出), # gevent.sleep(0.5)#可使用此行代碼啓動,遇到IO才切換, # g1.join()#使用join是等待eat函數執行完, # g2.join() gevent.joinall([g1,g2])#同上面的g1或g2.join()是同樣的, # 輸出: # 吃 # 玩兒 # 玩兒美了 # 吃完了
gevent幫你作了切換,作切換是有條件的,遇到IO才切換
gevent不認識除了gevent這個模塊內之外的IO操做
使用join能夠一直阻塞直到協程任務完成
幫助gevent來認識其餘模塊中的阻塞
from gevent import monkey;monkey.patch_all()寫在其餘模塊導入以前