6,線程-線程隊列-線程池-協程-greenlet-gevent

線程隊列-線程池-協程-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

 

Python標準模塊--concurrent.futures

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


Greenlet模塊

安裝:在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在兩個函數之間的使用
 

用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()寫在其餘模塊導入以前
相關文章
相關標籤/搜索