1. 程式人生 > >Python的Socket知識6:執行緒、執行緒鎖、執行緒池、上下文管理

Python的Socket知識6:執行緒、執行緒鎖、執行緒池、上下文管理

1、程序、執行緒

     程序(process)是cpu資源分配的最小單位,執行緒(thread)是cpu排程的最小單位。多執行緒和多程序的應用目的是為了提高併發。一個應用程式可以包含多個程序,而一個程序又可以包含多個執行緒。預設一個應用程式是單程序、單執行緒。

1)什麼是程序(process)

    程序:指在系統中能獨立執行並作為資源分配的基本單位,它是由一組機器指令、資料和堆疊等組成的,是一個能獨立執行的活動實體。每一個程序都有一個自己的地址空間,即程序空間或(虛空間)。程序空間的大小隻與處理機的位數有關,一個 16 位長處理機的程序空間大小為 216 ,而 32 位處理機的程序空間大小為 232 。程序至少有 5 種基本狀態,它們是:初始態,執行態,等待狀態,就緒狀態,終止狀態。比如開啟一個QQ算是一個程序。

2)什麼是執行緒(thread)

    執行緒,在網路或多使用者環境下,一個伺服器通常需要接收大量且不確定數量使用者的併發請求,為每一個請求都建立一個程序顯然是行不通的,無論是從系統資源開銷方面或是響應使用者請求的效率方面來看。因此,作業系統中執行緒的概念便被引進了。執行緒,是程序的一部分,一個沒有執行緒的程序可以被看作是單執行緒的。執行緒有時又被稱為輕量級程序,也是CPU 排程的一個基本單位。

3)程序和執行緒的區別:

  • 程序是資源分配和擁有的單位,擁有獨立的地址空間,同一個程序內的執行緒共享程序的資源,但執行緒並不擁有資源,只是使用他們

  • 執行緒是處理器排程的基本單位,但程序不是。每個程序在建立時,至少需要同時為該程序建立一個執行緒。即程序中至少要有一個或一個以上的執行緒,否則該程序無法被排程執行。執行緒是程序內的一個相對獨立的可執行的單元。

  • 由於共享資源【包括資料和檔案】,所以執行緒間需要通訊和同步機制,且需要時執行緒可以建立其他執行緒,但執行緒間不存在父子關係。

4)python中關於執行緒和程序的使用經驗:

  • 因為IO操作不佔用CPU,因此,IO操作可以使用多執行緒提高併發。

  • 計算性操作,佔用CPU,可以使用多程序提高併發。

5)建立執行緒

threading提供了一個比thread模組更高層的API來提供執行緒的併發性

建立執行緒的基本方法有兩種:

1)利用threading.Thread建立物件,在它的初始化函式(__init__)中將可呼叫物件作為引數傳入。

案例1:建立一個子執行緒,但子執行緒什麼時候被執行,需要服從系統的呼叫。

雖然程式碼解釋到了t.start(),但實際執行時間取決於系統。

import  time
#定義一個主執行緒
def f1(i):
time.sleep(3) # 需要3秒執行完畢
   
print(i)
#建立的子執行緒
import threading
#寫完下面一句,就代表建立執行緒完畢
#target是需要執行的函式,args是傳入函式的引數
t=threading.Thread(target=f1,args=(123,))
#執行子執行緒
t.start()#執行時間待定
#執行主執行緒
f1(456)
print('end')

執行結果:

640?wx_fmt=png

案例2:使用setDaemon(True)控制主執行緒不等子執行緒。

    setDaemon()方法。當引數為True時,把主執行緒設定為守護執行緒,主執行緒A執行結束了,就不管子執行緒是否完成,一併和主執行緒退出,也即主執行緒不等子執行緒。此方法必須在start() 方法呼叫之前設定,如果不設定為守護執行緒,程式會被無限掛起。

import  time
#定義一個主執行緒
def f1(i):
time.sleep(3) # 需要3秒執行完畢
   
print(i)
#建立的子執行緒
import threading
#寫完下面一句,就代表建立執行緒完畢
#target是需要執行的函式,args是傳入函式的引數
t=threading.Thread(target=f1,args=(123,))
#True表示主執行緒不等子執行緒,主執行緒結束則程式結束。
t.setDaemon(True)
#執行子執行緒
t.start()#執行時間待定
#執行主執行緒
f1(456)
print('end')

效果:

640?wx_fmt=png

案例3:join的引數使用,控制主執行緒等待子執行緒多長時間

    當一個執行緒操作需要等待另一個執行緒執行完畢之後才能繼續進行時,使用Join()方法。Join方法會等到使用該方法的執行緒結束後再執行下面的程式碼。

import  time
#定義一個主執行緒
def f1(i):
time.sleep(3) # 需要3秒執行完畢
   
print(i)
#建立的子執行緒
import threading
#寫完下面一句,就代表建立執行緒完畢
#target是需要執行的函式,args是傳入函式的引數
t=threading.Thread(target=f1,args=(123,))
#True表示主執行緒不等子執行緒,主執行緒結束則程式結束。
t.setDaemon(True)
#執行子執行緒
t.start()#執行時間待定
#主執行緒執行到join時,等待,直到子執行緒執行完畢,再繼續執行join後的程式
t.join(2)#2表示最多等2秒
print('end')
print('end')

效果:

640?wx_fmt=png

假設join中的秒數大一點,可能就會有子執行緒執行完畢

t.join(4)#2表示最多等4秒

640?wx_fmt=png

2)第二種建立方式,自定義類,通過繼承Thread類,重寫它的run方法

案例4:自定義執行緒的使用

#自定義f2方法
def f2(arg):
print(arg)
#建立執行緒
import threading
class mythread(threading.Thread):#繼承threading.Thread方法
   
def __init__(self,func,args):
self.func=func
self.args=args
# 執行父類的構造方法
       
super(mythread,self).__init__()
def run(self):
self.func(self.args)
#例項化執行緒
obj=mythread(f2,123)
#執行執行緒
obj.start()

threading.Thread類的使用方法:

  • 在自己的執行緒類的__init__裡呼叫threading.Thread.__init__(self, name = threadname)

  • Threadname為執行緒的名字

  • run(),通常需要重寫,編寫程式碼實現做需要的功能。

  • getName(),獲得執行緒物件名稱

  • setName(),設定執行緒物件名稱

  • start(),啟動執行緒

  • jion([timeout]),等待另一執行緒結束後再執行。

  • setDaemon(bool),設定子執行緒是否隨主執行緒一起結束,必須在start()之前呼叫。預設為False,引數為True時表示主執行緒不等子執行緒結束

  • isDaemon(),判斷執行緒是否隨主執行緒一起結束。

  • isAlive(),檢查執行緒是否在執行中。

2、執行緒鎖,解決資料共享

    同一時刻允許一個執行緒執行操作。由於執行緒呼叫隨機,當多個執行緒都要去修改某一個共享資料的時候,需要對資料訪問進行同步。

    假設兩個執行緒物件t1和t2都要對num=0進行增1運算,t1和t2都各對num修改10次,num的最終的結果應該為20。但是由於是多執行緒訪問,有可能出現下面情況:在num=0時,t1取得num=0。系統此時把t1排程為”sleeping”狀態,把t2轉換為”running”狀態,t2頁獲得num=0。然後t2對得到的值進行加1並賦給num,使得num=1。然後系統又把t2排程為”sleeping”,把t1轉為”running”。執行緒t1又把它之前得到的0加1後賦值給num。這樣,明明t1和t2都完成了1次加1工作,但結果仍然是num=1。

    threading.Lock類物件,在run方法裡,使用lock.acquire()獲得了這個鎖。此時,其他的執行緒就無法再獲得該鎖了,他們就會阻塞在“if lock.acquire()”這裡,直到鎖被另一個執行緒釋放:lock.release()。

案例5:使用threading.Lock(),上一把鎖。

640?wx_fmt=png

執行結果如下:

640?wx_fmt=png

案例6:使用threading.RLock(),上多把鎖。

640?wx_fmt=png

執行效果:

640?wx_fmt=png

3、訊號量(Semaphore):批量放行

    互斥鎖是同時只允許一個執行緒更改資料,而Semaphore是同時允許一定數量的執行緒更改資料,比如廁所有三個坑,最多隻允許三個人同時上廁所,後面的人只能等裡面有人出來才能再進去。

案例7:Semaphore批量放行,同時允許5個人同時修改。

640?wx_fmt=png

執行效果:

640?wx_fmt=png

5、事件(Event):全部放行或擋住。

    python執行緒的事件用於主執行緒控制其他執行緒的執行。事件提供了三個方法set、wait、clear。事件處理的機制,全域性定義了一個“Flag”值為False,那麼當程式執行evevt.wait方法時,就會阻塞,如果“Flag”值為True,那麼event.wait方法時便不再阻塞。

    clear:將Flag設定為False,set:將Flag設定為True

案例8:事件event,全部擋住或放行,按照程式碼從上到下的執行原則,可以寫出其執行順序。

640?wx_fmt=png

執行結果:

640?wx_fmt=png

6、條件:使得執行緒等待,只有滿足某條件時,才釋放n個執行緒。

案例9:條件,直接條用condition。

640?wx_fmt=png

案例10:自己寫條件,使用wait_for獲取

640?wx_fmt=png

效果:

640?wx_fmt=png

7、執行緒池。

    Python標準庫為我們提供了threading和multiprocessing模組編寫相應的多執行緒/多程序程式碼,但是當專案達到一定的規模,頻繁建立/銷燬程序或者執行緒是非常消耗資源的,這個時候我們就要編寫自己的執行緒池/程序池,以空間換時間。

    執行緒池需要實現的功能:

  • 設定容量

  • 取一個少一個

  • 無執行緒時等待

  • 執行緒執行完畢,交還執行緒

案例11:使用佇列自定義執行緒池,執行緒池中放的執行緒,但是執行緒不能重複利用

640?wx_fmt=png

案例12:自定義執行緒池,線上程池中放任務,執行緒池的任務存放是用列表的形式,任務有兩種型別,一種是實際的任務,一種是在尾部放幾個空的任務,空的任務作用是用於識別任務執行結束。

640?wx_fmt=png640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

執行結果:列印0-29

8、上下文管理

    contextlib模組的contextmanager裝飾器可以更方便的實現上下文管理器。任何能夠被yield關鍵詞分割成兩部分的函式,都能夠通過裝飾器裝飾的上下文管理器來實現。任何在yield之前的內容都可以看做在程式碼塊執行前的操作,而任何yield之後的操作都可以放在finally函式中。

案例13:上下文管理案例,看執行順序

640?wx_fmt=png

執行結果:

640?wx_fmt=png

案例14:利用上下文管理,實現socket自動關閉

import contextlib
import socket
@contextlib.contextmanager
def context_socket(host,port):
sk=socket.socket()
sk.bind((host,port))
sk.listen(5)
try:
yield sk#返回sk給with
finally:
sk.close()
#sk返回後,給到sock
with context_socket('127.0.0.1',8888) as sock:
print('執行其他操作')

執行效果:

640?wx_fmt=png

案例連結:https://pan.baidu.com/s/1mj2U6MG 密碼:lxs2