首頁  >  文章  >  後端開發  >  Python開發--進程、執行緒、協程詳解

Python開發--進程、執行緒、協程詳解

零下一度
零下一度原創
2017-06-27 09:57:521470瀏覽

什麼是進程(process)?

程式並不能單獨運行,只有將程式裝載到記憶體中,系統為它分配資源才能運行,而這種執行的程式就稱之為進程。程式和進程的差別就在於,程式是指令的集合,它是進程運行的靜態描述文字;進程是程式的一次執行活動,屬於動態概念。

什麼是執行緒(thread)?

執行緒是作業系統能夠進行運算調度的最小單位。它被包含在進程中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以並發多個線程,每個線程並行執行不同的任務。

進程與執行緒的差別?

線程共享記憶體空間,進程的記憶體是獨立的。

同一個行程的執行緒之間可以直接交流,但兩個行程相互通訊必須透過一個中間代理。

建立一個新的執行緒很簡單,建立一個新的行程需要對其父行程進行一次複製。

一個執行緒可以控制和操作同一行程裡的其他線程,但是行程只能操作子行程。

Python GIL(Global Interpreter Lock)

無論開啟多少個線程,有多少個CPU,python在執行的時候在同一時刻只允許一個線程允許。

Python threading模組

直接呼叫

  1. #import threading,time
  2. def run_num(num):
  3. #    """
  4. #    定義執行緒要執行的函數
  5.     :param num:
  6. #    :return:
  7. #    """
  8.     print("
  9. running on number:%s
  10. "%num)
  11.     time.sleep(3)
  12. if
  13. __name__ == '__main__':
  14.     # 產生一個執行緒實例t1
  15.     t1 = threading.Thread(target=run_num,args=(1,) )
  16.     # 產生一個執行緒實例t2
  17. ##    t2 = threading.Thread(target =run_num,args=(2,))
  18.     # 啟動執行緒t1
  19. # t1.start()
  20.     # 啟動執行緒t2
  21. ##    t2.start()
  22.     # 取得執行緒名稱
  23.     print(t1.getName())
  24.     print(t2.getName())
  25. 輸出:

  1. ############################ ############running on number:1###################running on number:2########## ########Thread-1##################Thread-2###############繼承式調用###############import threading,time###################
  2. class MyThread(threading.Thread):
  3.     def __init__(self,num ):
  4.         threading.Thread.__init__(self)
  5. #    num
  6.     # 定義每個執行緒要執行的函數,函數名稱必須是run
  7. #    def run(self):
  8.         print("#running on number:%s"%self.num)
  9. #        time.sleep(3)
  10. if __name__ == '__main__':
  11. #    t1 = MyThread(1)
  12. #    t2 = MyThread(2)
  13. #    t1.start()

    t2.start()

#輸出:

  1. ##running on number :1
  2. running on number:2
  3. Join and DaemonJoin
  4. Join的作用是阻塞主程序,無法執行join後面的程式。
  5. 多執行緒多join的情況下,依序執行各執行緒的join方法,前面一個執行緒執行結束才能執行後面一個執行緒。 無參數時,則等待該執行緒結束,才執行後續的程式。
  6. 設定參數後,則等待該執行緒設定的時間後就執行後面的主進程,而不管該執行緒是否結束。
  7. import threading,time
  8. class MyThread(threading.Thread):
  9. #    def __init__(self,num):
  10. #        threading.Thread.__init__(self)
  11.        # 
        # 定義每個執行緒要執行的函數,函數名稱必須是run
  12.     def run(self):
  13. #        print("running on number:%s
    "%self.num)
  14. ##"%self.num)
  15. #        time.sleep(3)
  16.         print("####thread:%s### ############################if### __name__ == '__main__':########### #######    t1 = MyThread(1)###################    t2 = MyThread(2)############################################################################################################################################################################ #####    t1.start()###################    t1.join()########
  17.     t2.start()
  18. #    t2.join()
  19. ##輸出:
  20. running on number:1
  21. thread:1
  22. running on number:2
  23. thread:2

設定參數效果如下:

  1. #if __name__ == '__main__':
  2.     t1 = MyThread(1)
  3.     t2 = MyThread(2)
  4. #    t2 = MyThread(2)
  5.     t1.start()
  6.     t1.join(2)
  7. #    t1.join(2)
  8. #    t2.start()
  9. #    t2.join()
  10. 輸出:
  11. running on number:1
  12. running on number:2
  13. thread:1

    thread:2
  1. Daemon
  2. 預設情況下,主執行緒在退出時會等待所有子執行緒的結束。如果希望主線程不等待子線程,而是在退出時自動結束所有的子線程,就需要設定子線程為後台線程(daemon)。方法是透過呼叫執行緒類別的setDaemon()方法。
  3. import time,threading
  4. def run(n):
  5.     print("%s".center(20,"
    *
  6. ") %n)
  7.     time.sleep(2)
  8. ##    print("
    done
    ".center(20,"
  9. *
  10. "))
  11. def main():
  12.     
    for i in
    range(5):
  13.         t = threading.Thread(target=run,args=(i,))
  14. #    start()
  15.         t.join(1)
  16.        
  17.       #starting thread",t.getName())
  18. m = threading.Thread( target=main,args=())
  19. # 將main線程設定位Daemon線程,它作為程式主線程的守護線程,當主線程退出時, m線程也會退出,由m啟動的其它子線程會同時退出,不管是否執行完成
    ##########m.setDaemon(True)##### #############m.start()#######
  20. m.join(3)
  21. #print("main thread done". center(20,"*"))
  22. #輸出:
  23. *********0*********
  24. starting thread Thread-2
  25. #*********1*********
  26. # ********done********
  27. starting thread Thread-3
  28. *********2*********
  29. #**main thread done **

線程鎖定(互斥鎖Mutex)

一個進程下可以啟動多個執行緒,多個執行緒共享父進程的記憶體空間,也意味著每個執行緒可以存取同一份數據,此時,如果2個執行緒同時要修改同一份資料就需要執行緒鎖。

  1. import time,threading
  2. def addNum():
  3.     # 在每個執行緒中都取得這個全域變數
  4.     global num
  5. #    print("--get num:",num)
  6. ##    time.sleep(1)
  7.     # 對此公共變數進行-1操作
  8.     num -= 1
  9. ## 設定一個共享變數
  10. num = 100
  11. thread_list = []
    for
  12. i
  13. in
    range(100):
  14.     t = threading.Thread(target=addNum)
  15. #    t.start()
  16. #    thread_list.append(t)
  17. # 等待所有執行緒執行完畢
  18. for# t in
    thread_list:
  19.     t.join()

##print("
    final num:
  1. ",num)
  2. #加上鎖定版本
  3. Lock時阻塞其他執行緒對共享資源的訪問,且同一執行緒只能acquire一次,如多於一次就出現了死鎖,程式無法繼續執行。
  4. import time,threading
  5. def addNum():
  6.     # 在每個執行緒中都取得這個全域變數
        global num
  7. #    print("###--get num:###",num)################### #######    time.sleep(1)######
  8.     # 修改資料前加鎖
  9.     lock.acquire()
  10.     # 對此公共變數進行-1操作
  11.     num -= 1
  12. #    # 修改後釋放
  13.     lock.release()
  14. # 設定一個共享變數
  15. #num = 100
  16. thread_list = []
  17. ## 產生全域鎖定
  18. ##lock = threading.Lock()
  19. #for i in range (100):
  20.     t = threading.Thread(target=addNum)
  21. # t.start()
  22.     thread_list.append(t)
  23. ## 等待所有執行緒執行完畢
  24. for t in thread_list:
  25. #    t.join()
  26. print("final num: ",num)
GIL VS Lock

#GIL保證同一時間只能有一個執行緒來執行。 lock是用戶級的lock,與GIL沒有關係。

RLock(遞迴鎖定)

Rlock允許在同一線程中被多次acquire,線程對共享資源的釋放需要把所有鎖定都release。即n次acquire,需要n次release。

  1. def run1():
  2.     print("grab the first part data")
  3.     lock.acquire()
  4.     global num
  5. #    num += 1
  6.     #lock
    #    
  7. ##lock
  8. #    lock
  9. #    
    ##lock
  10. #   .release()
  11.     return num
  12. ##def run2():
  13. #    print("grab the second part data")
    ############    ###lock###.acquire()#################    global num2## ################    num2 += 1##################    ###lock###.release()# #################    ###return### num2#######
  14. def run3():
  15.     
  16. 。 lock
  17. .acquire()
        res = run1()
  18.     print ("
  19. between run1 and run2
  20. ".center(50,"
    *
    "))
  21.     res2 = run2 ()
  22.     lock
    .release()
  23.     print (res,res2)
  24. #if
    __name__ == '__main__':
  25.     num,num2 = 0,0
  26.     
  27. lock
  28. # = threading.RLock()
  29.     for i in
    range(10):
  30.         t = threading.Thread(target=run3)
  31.         t.
  32. while threading.active_count() != 1:
  33.     print(threading.active_count())

else

:

  1. #    print("all threads done
    ".center(50,"
  2. *
  3. "))
  4. #    print(num,num2)
  5. #這兩個鎖定的主要差異是,RLock允許在同一執行緒中被多次acquire。而Lock卻不允許這種情況。請注意,如果使用RLock,那麼acquire和release必須成對出現,即呼叫了n次acquire,必須呼叫n次的release才能真正釋放所佔用的鎖。 Semaphore(信號量)
  6. 互斥鎖同時只允許一個線程更改數據,而Semaphore是同時允許一定數量的線程更改數據,比如售票處有3個窗口,那最多只允許3個人同時買票,後面的人只能等前面任意窗口的人離開才能買票。
  7. import threading,time
  8. def run(n):
  9.     semaphore.acquire()
  10.     time.sleep( 1)
  11.     print("
    run the thread:%s"%n)
  12. #    semaphore.release()
  13. if
    __name__ == '__main__':##################    # 最多允許5個執行緒同時執行################################################################################################ semaphore = threading.BoundedSemaphore(5)##################    ###for### i ###in### range(20):#### ##
  14.         t = threading.Thread(target=run,args=(i,))
  15. #    ()
  16. #while threading.active_count() != 1:
  17.     # print(threading.active_count())
  18. ##    pass
  19. ##    pass
  20. else
  21. :
    #    print("all threads done
    ".center(50,"
  22. *
"))

    #Timer(計時器)
  1. Timer隔一定時間呼叫一個函數,如果想實現每隔一段時間就呼叫一個函數,就要在Timer呼叫的函數中,再設定Timer。 Timer是Thread的一個衍生類別。
  2. import threading
  3. #def hello( ):
        print("
  4. hello,world!
  5. ")
  6. delay 5秒之後執行hello函數
#t = threading.Timer(5,hello)

  1. #t.start()

Event

    #Python提供了Event物件用於線程間通信,它是有線程設置的信號標誌,如果信號標誌位為假,則線程等待指導信號被其他線程設定為真。 Event物件實作了簡單的執行緒通訊機制,它提供了設定訊號、清除訊號、等待等用於實現執行緒間的通訊。
  1. 設定訊號

    使用Event的set()方法可以設定Event物件內部的訊號標誌為真。 Event物件提供了isSet()方法來判斷其內部訊號標誌的轉態,當使用event物件的set()方法後,isSet()方法傳回為真。
  1. 清除訊號

使用Event的clear()方法可以清除Event物件內部的訊號標誌,即將其設為假,當使用Event的clear()方法後,isSet()方法回傳假。

  1. 等待
  2. Event的wait()方法只有在內部訊號為真的時候才會很快的執行並完成回傳。當Event物件的內部訊號標誌為假時,則wait()方法一直等待其為真時才傳回。
  3. 透過Event來實現兩個或多個線程間的交互,下面以紅綠燈為例,即啟動一個線程做交通指揮燈,生成幾個線程做車輛,車輛行駛按紅停綠行的規則。
  4. import threading,time,random
  5. #def light():
  6.     if
    not
  7. event
  8. .isSet():
            #event
    .###set###()##################    count = 0 ##################    ###while### 真:##################        ####if#####        ####if## ## count < 5:##################            print("###\033[42;1m--green light on--\033[0m## #".center(50,"###*###"))######
  9.         elif 計數 < 8:
  10.             print("\033[43;1m--黃燈亮--\033[0m".center( 50 ,"*"))
  11.         elif 計數 < 13:
  12.             ifevent.isSet():
  13. #event.isSet():
  14. #event
  15. .isSet():
                    
    event
  16. .clear()
  17.     燈亮--\033[0m
    ".center(50,"
  18. *
  19. "))
  20.         
    else :
                數= 0
  21. # .
    set
  22. ()
  23.         time.sleep(1)
  24. ##        計數+= 1
  25. #def汽車(n):
  26.     while 1:
  27. ## time.sleep(random.randrange(10))
  28. #        
    if
  29. event
    .isSet():
  30.             print("汽車%s 正在運作..."%n)
  31. ##        
    #else##:
  32.             print("c%s 正在等待紅燈%
    "%n)##################################if### __name__ == "# ## __main__###":##################    ###event### = threading.Event()############ ## #####    Light = threading.Thread(target=light,)##################    Light.start()#############    Light.start()######### ## ###################    ###for### i ### 在### 範圍內(3):######### ### #######        t = threading.Thread(target=car,args=(i,))##################     t. ### ###

queue佇列

Python中佇列是執行緒間最常用的交換資料的形式。 Queue模組是提供佇列操作的模組。

建立一個佇列物件

  1. import queue
  2. ##q = queue.Queue(maxsize = 10)
queue.Queue類別是一個佇列的同步實作。隊列長度可以無限或有限。可以透過Queue的建構函數的可選參數maxsize來設定佇列長度。如果maxsize小於1表示佇列長度無限。

將一個值放入佇列中

  1. q.put("a")
呼叫佇列物件的put()方法在隊尾插入一個項目。 put()有兩個參數,第一個item為必需的,為插入項目的值;第二個block為可選參數,預設為1。如果佇列目前為空且block為1,put()方法就會使呼叫執行緒暫停,直到空出一個資料單元。如果block為0,put()方法將引發Full異常。

將一個值從佇列中取出

  1. q.get()
呼叫佇列物件的get()方法從隊頭刪除並傳回一個項目。可選參數為block,預設為True。如果佇列為空且block為True,get()就會使呼叫執行緒暫停,直到有項目可用。如果佇列為空且block為False,佇列將引發Empty異常。

Python Queue模組有三種佇列及建構子

  1. ## 先進先出
  2. ##class queue.Queue(maxsize=0)
  3. # 先進後出
  4. class queue.LifoQueue(maxsize=0)
  5. # 優先權佇列等級越低越先出
  6. class queue.PriorityQueue(maxsize=0)
  7. 常用方法

  1. q = queue.Queue()
  2. # 傳回佇列的大小
  3. q.qsize()
  4. # 若佇列為空,則傳回True,反之False
  5. q.empty()
  6. ## 若佇列滿了,返回True,反之False
  7. q.full()
  8. ## 取得佇列,timeout等待時間
  9. q.
    get([block[,timeout]])
  10. # 相當於q.
    get(False)
  11. #q.get_nowait()
  12. # 等到佇列為空再執行別的操作
  13. #q.join()

生產者消費者模型

在開發程式設計中使用生產者和消費者模式能夠解決絕大多數並發問題。此模式透過平衡生產線程和消費線程的工作能力來提高程式的整體處理資料的速度。

為什麼要使用生產者和消費者模式

在線程世界裡,生產者就是生產資料的線程,而消費者就是消費資料的線程。在多執行緒開發當中,如果生產者處理速度很快,而消費者處理速度很慢,那麼生產者就必須等待消費者處理完,才能繼續生產資料。同樣的道理,如果消費者的處理能力大於生產者,那麼消費者就必須等待生產者。為了解決這個問題於是引進了生產者和消費者模式。

什麼是生產者消費者模式

生產者消費者模式是透過一個容器來解決生產者和消費者的強耦合問題。生產者和消費彼此之間不直接通訊,而透過阻塞隊列來進行通訊,所以生產者生產完數據之後不再等待消費者處理,直接丟給阻塞隊列,消費者不找生產者要數據,而是直接從阻塞隊列取,阻塞隊列就相當於一個緩衝區,平衡了生產者和消費者的處理能力。

最基本的生產者消費者模式的例子。

  1. import queue,threading,time
  2. q = queue.Queue(maxsize=10)
  3. #def Producer():
  4.     count = 1
  5.     while 真:
  6. #        q.put("骨%s"%count)
  7. ##      "生產了骨頭",count)
  8. #        count += 1
  9. ##def Consumer(name):
  10. #    
    while q.qsize () > 0:
  11.         print("
  12. [%s] 取到[%s]並且吃了它...
  13. "%(name,q.
    get()))
  14. #        time.sleep(1)
  15. #p = threading.Thread(target=Producer,)
  16. #c1 = threading.Thread(target=Consumer,args=("旺財
    ",))
  17. ##c2 = threading.Thread (target=Consumer,args=("
    來福
  18. ",))
  19. p.start()

c1.start()

#c2.start()

## 

 

 

### ###### ###### ###### ####### #### ### ###### ####

以上是Python開發--進程、執行緒、協程詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn