線程
threading用于提供線程相關的操作,線程是應用程式中工作的最小單元。
更多方法:
start 線程準備就緒,等待cpu排程
setname 為線程設定名稱
getname 擷取線程名稱
setdaemon 設定為背景線程或前台線程(預設)
如果是背景線程,主線程執行過程中,背景線程也在進行,主線程執行完畢後,背景線程不論成功與否,均停止
如果是前台線程,主線程執行過程中,前台線程也在進行,主線程執行完後,等待前台線程也執行完成後,停止
join 逐個執行每個線程,執行完畢後繼續往下執行,該方法使得多線程變得無意義
run 線程被cpu排程後執行thread類對象的run方法
直接調用
繼承式調用
守護線程
線程鎖
如果同一時間有多個線程操作同一資料可能會出現混亂現象,是以需要加線程鎖保證同一時間隻有一個線程操作這一資料,保證資料一緻性
加鎖:lock = threading.rlock() 解鎖:lock.release()
lock.acquire()
正常來講,這個num結果應該是0, 但在python 2.7上多運作幾次,會發現,最後列印出來的num結果不總是0,為什麼每次運作的結果不一樣呢? 哈,很簡單,假設你有a,b兩個線程,此時都 要對num 進行減1操作, 由于2個線程是并發同時運作的,是以2個線程很有可能同時拿走了num=100這個初始變量交給cpu去運算,當a線程去處完的結果是99,但此時b線程運算完的結果也是99,兩個線程同時cpu運算的結果再指派給num變量後,結果就都是99。那怎麼辦呢? 很簡單,每個線程在要修改公共資料時,為了避免自己在還沒改完的時候别人也來修改此資料,可以給這個資料加一把鎖, 這樣其它線程想修改此資料時就必須等待你修改完畢并把鎖釋放掉後才能再通路此資料。
rlock(遞歸鎖)
說白了就是在一個大鎖中還要再包含子鎖
semaphore(信号量)
互斥鎖 同時隻允許一個線程更改資料,而semaphore是同時允許一定數量的線程更改資料 ,比如廁所有3個坑,那最多隻允許3個人上廁所,後面的人隻能等裡面有人出來了才能再進去。
events
python線程的事件用于主線程控制其他線程的執行,事件主要提供了三個方法 set、wait、clear。
事件處理的機制:全局定義了一個“flag”,如果“flag”值為 false,那麼當程式執行 event.wait 方法時就會阻塞,如果“flag”值為true,那麼event.wait 方法時便不再阻塞。
clear:将“flag”設定為false
set:将“flag”設定為true
queue.qsize() 傳回隊列的大小
queue.empty() 如果隊列為空,傳回true,反之false
queue.full() 如果隊列滿了,傳回true,反之false
queue.full 與 maxsize 大小對應
queue.get([block[, timeout]])擷取隊列,timeout等待時間
queue.get_nowait() 相當queue.get(false)
<code>queue.</code><code>put</code>(item, block=true, timeout=none) 非阻塞寫入隊列,timeout等待時間
queue.put_nowait(item) 相當queue.put(item, false)
queue.task_done() 在完成一項工作之後,queue.task_done()函數向任務已經完成的隊列發送一個信号
queue.join() 實際上意味着等到隊列為空,再執行别的操作
class <code>queue.</code><code>queue</code>(maxsize=0) #先入先出
class <code>queue.</code><code>lifoqueue</code>(maxsize=0) #last in fisrt out
class <code>queue.</code><code>priorityqueue</code>(maxsize=0) #存儲資料時可設定優先級的隊列(priority_number, data)
1.多線程采用的是分時複用技術,即不存在真正的多線程,cpu做的事是快速地切換線程,以達到類似同步運作的目的,因為高密集運算方面多線程是沒有用的,但是對于存在延遲的情況(延遲io,網絡等)多線程可以大大減少等待時間,避免不必要的浪費。
2.原子操作:這件事情是不可再分的,如變量的指派,不可能一個線程在指派,到一半切到另外一個線程工作去了……但是一些資料結構的操作,如棧的push什麼的,并非是原子操作,比如要經過棧頂指針上移、指派、計數器加1等等,在其中的任何一步中斷,切換到另一線程再操作這個棧時,就會産生嚴重的問題,是以要使用鎖來避免這樣的情況。比如加鎖後的push操作就可以認為是原子的了……
3.阻塞:所謂的阻塞,就是這個線程等待,一直到可以運作為止。最簡單的例子就是一線程原子操作下,其它線程都是阻塞狀态,這是微觀的情況。對于宏觀的情況,比如伺服器等待使用者連接配接,如果始終沒有連接配接,那麼這個線程就在阻塞狀态。同理,最簡單的input語句,在等待輸入時也是阻塞狀态。
4.在建立線程後,執行p.start(),這個函數是非阻塞的,即主線程會繼續執行以後的指令,相當于主線程和子線程都并行地執行。是以非阻塞的函數立刻傳回值的~
對于資源,加鎖是個重要的環節。因為python原生的list,dict等,都是not thread safe的。而queue,是線程安全的,是以在滿足使用條件下,建議使用隊列。
隊列适用于 “生産者-消費者”模型。雙方無論數量多少,産生速度有何差異,都可以使用queue。
在上面的例子中,producer在随機的時間内生産一個“産品”,放入隊列中。consumer發現隊列中有了“産品”,就去消費它。本例中,由于producer生産的速度快于consumer消費的速度,是以往往producer生産好幾個“産品”後,consumer才消費一個産品。
queue子產品實作了一個支援多producer和多consumer的fifo隊列。當共享資訊需要安全的在多線程之間交換時,queue非常有用。queue的預設長度是無限的,但是可以設定其構造函數的maxsize參數來設定其長度。queue的put方法在隊尾插入,該方法的原型是:
put( item[, block[, timeout]])
如果可選參數block為true并且timeout為none(預設值),線程被block,直到隊列空出一個資料單元。如果timeout大于0,在timeout的時間内,仍然沒有可用的資料單元,full exception被抛出。反之,如果block參數為false(忽略timeout參數),item被立即加入到空閑資料單元中,如果沒有空閑資料單元,full exception被抛出。
queue的get方法是從隊首取資料,其參數和put方法一樣。如果block參數為true且timeout為none(預設值),線程被block,直到隊列中有資料。如果timeout大于0,在timeout時間内,仍然沒有可取資料,empty exception被抛出。反之,如果block參數為false(忽略timeout參數),隊列中的資料被立即取出。如果此時沒有可取資料,empty exception也會被抛出。
協程
線程和程序的操作是由程式觸發系統接口,最後的執行者是系統;協程的操作則是程式員。
協程存在的意義:對于多線程應用,cpu通過切片的方式來切換線程間的執行,線程切換時需要耗時(儲存狀态,下次繼續)。協程,則隻使用一個線程,在一個線程中規定某個代碼塊執行順序。
協程擁有自己的寄存器上下文和棧。協程排程切換時,将寄存器上下文和棧儲存到其他地方,在切回來的時候,恢複先前儲存的寄存器上下文和棧。是以:
協程能保留上一次調用時的狀态(即所有局部狀态的一個特定組合),每次過程重入時,就相當于進入上一次調用的狀态,換種說法:進入上一次離開時所處邏輯流的位置。
協程的适用場景:當程式中存在大量不需要cpu的操作時(io),适用于協程。
協程的好處:
無需線程上下文切換的開銷
無需原子操作鎖定及同步的開銷
友善切換控制流,簡化程式設計模型
高并發+高擴充性+低成本:一個cpu支援上萬的協程都不是問題。是以很适合用于高并發處理。
缺點:
無法利用多核資源:協程的本質是個單線程,它不能同時将 單個cpu 的多個核用上,協程需要和程序配合才能運作在多cpu上.當然我們日常所編寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用。
進行阻塞(blocking)操作(如io時)會阻塞掉整個程式
greenlet
gevent
gevent 是一個第三方庫,可以輕松通過gevent實作并發同步或異步程式設計,在gevent中用到的主要模式是greenlet, 它是以c擴充子產品形式接入python的輕量級協程。 greenlet全部運作在主程式作業系統程序的内部,但它們被協作式地排程。
遇到io操作自動切換: