Python開發【第九篇】:進程、線程、協程

来源:http://www.cnblogs.com/yinshoucheng-golden/archive/2017/06/27/7083280.html
-Advertisement-
Play Games

什麼是進程(process)? 程式並不能單獨運行,只有將程式裝載到記憶體中,系統為它分配資源才能運行,而這種執行的程式就稱之為進程。程式和進程的區別就在於,程式是指令的集合,它是進程運行的靜態描述文本;進程是程式的一次執行活動,屬於動態概念。 什麼是線程(thread)? 線程是操作系統能夠進行運算... ...


什麼是進程(process)?

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

什麼是線程(thread)?

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

進程與線程的區別?

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

同一個進程的線程之間可以直接交流,但兩個進程相互通信必須通過一個中間代理。

創建一個新的線程很簡單,創建一個新的進程需要對其父進程進行一次克隆。

一個線程可以控制和操作同一進程里的其他線程,但是進程只能操作子進程。

Python GIL(Global Interpreter Lock)

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

Python threading模塊

直接調用

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

繼承式調用

  1. import threading,time
  2.  
  3. class MyThread(threading.Thread):
  4.     def __init__(self,num):
  5.         threading.Thread.__init__(self)
  6.         self.num = num
  7.     # 定義每個線程要運行的函數,函數名必須是run
  8.     def run(self):
  9.         print("running on number:%s"%self.num)
  10.         time.sleep(3)
  11.  
  12. if __name__ == '__main__':
  13.     t1 = MyThread(1)
  14.     t2 = MyThread(2)
  15.     t1.start()
  16.     t2.start()
  17. 輸出:
  18. running on number:1
  19. running on number:2

Join and Daemon

Join

Join的作用是阻塞主進程,無法執行join後面的程式。

多線程多join的情況下,依次執行各線程的join方法,前面一個線程執行結束才能執行後面一個線程。

無參數時,則等待該線程結束,才執行後續的程式。

設置參數後,則等待該線程設定的時間後就執行後面的主進程,而不管該線程是否結束。

  1. import threading,time
  2.  
  3. class MyThread(threading.Thread):
  4.     def __init__(self,num):
  5.         threading.Thread.__init__(self)
  6.         self.num = num
  7.     # 定義每個線程要運行的函數,函數名必須是run
  8.     def run(self):
  9.         print("running on number:%s"%self.num)
  10.         time.sleep(3)
  11.         print("thread:%s"%self.num)
  12.  
  13. if __name__ == '__main__':
  14.     t1 = MyThread(1)
  15.     t2 = MyThread(2)
  16.     t1.start()
  17.     t1.join()
  18.     t2.start()
  19.     t2.join()
  20. 輸出:
  21. running on number:1
  22. thread:1
  23. running on number:2
  24. thread:2

設置參數效果如下:

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

Daemon

預設情況下,主線程在退出時會等待所有子線程的結束。如果希望主線程不等待子線程,而是在退出時自動結束所有的子線程,就需要設置子線程為後臺線程(daemon)。方法是通過調用線程類的setDaemon()方法。

  1. import time,threading
  2.  
  3. def run(n):
  4.     print("%s".center(20,"*")%n)
  5.     time.sleep(2)
  6.     print("done".center(20,"*"))
  7.  
  8. def main():
  9.     for i in range(5):
  10.         t = threading.Thread(target=run,args=(i,))
  11.         t.start()
  12.         t.join(1)
  13.         print("starting thread",t.getName())
  14.  
  15. m = threading.Thread(target=main,args=())
  16. # 將main線程設置位Daemon線程,它作為程式主線程的守護線程,當主線程退出時,m線程也會退出,由m啟動的其它子線程會同時退出,不管是否執行完成
  17. m.setDaemon(True)
  18. m.start()
  19. m.join(3)
  20. print("main thread done".center(20,"*"))
  21. 輸出:
  22. *********0*********
  23. starting thread Thread-2
  24. *********1*********
  25. ********done********
  26. starting thread Thread-3
  27. *********2*********
  28. **main thread done**

線程鎖(互斥鎖Mutex)

一個進程下可以啟動多個線程,多個線程共用父進程的記憶體空間,也就意味著每個線程可以訪問同一份數據,此時,如果2個線程同時要修改同一份數據就需要線程鎖。

  1. import time,threading
  2.  
  3. def addNum():
  4.     # 在每個線程中都獲取這個全局變數
  5.     global num
  6.     print("--get num:",num)
  7.     time.sleep(1)
  8.     # 對此公共變數進行-1操作
  9.     num -= 1
  10. # 設置一個共用變數
  11. num = 100
  12. thread_list = []
  13. for i 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()
  20.  
  21. print("final num:",num)

加鎖版本

Lock時阻塞其他線程對共用資源的訪問,且同一線程只能acquire一次,如多於一次就出現了死鎖,程式無法繼續執行。

  1. import time,threading
  2.  
  3. def addNum():
  4.     # 在每個線程中都獲取這個全局變數
  5.     global num
  6.     print("--get num:",num)
  7.     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.  
  27. 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.release()
  7.     return num
  8.  
  9. def run2():
  10.     print("grab the second part data")
  11.     lock.acquire()
  12.     global num2
  13.     num2 += 1
  14.     lock.release()
  15.     return num2
  16.  
  17. def run3():
  18.     lock.acquire()
  19.     res = run1()
  20.     print("between run1 and run2".center(50,"*"))
  21.     res2 = run2()
  22.     lock.release()
  23.     print(res,res2)
  24.  
  25. if __name__ == '__main__':
  26.     num,num2 = 0,0
  27.     lock = threading.RLock()
  28.     for i in range(10):
  29.         t = threading.Thread(target=run3)
  30.         t.start()
  31.  
  32. while threading.active_count() != 1:
  33.     print(threading.active_count())
  34. else:
  35.     print("all threads done".center(50,"*"))
  36.     print(num,num2)

這兩種鎖的主要區別是,RLock允許在同一線程中被多次acquire。而Lock卻不允許這種情況。註意,如果使用RLock,那麼acquire和release必須成對出現,即調用了n次acquire,必須調用n次的release才能真正釋放所占用的鎖。

Semaphore(信號量)

互斥鎖同時只允許一個線程更改數據,而Semaphore是同時允許一定數量的線程更改數據,比如售票處有3個視窗,那最多只允許3個人同時買票,後面的人只能等前面任意視窗的人離開才能買票。

  1. import threading,time
  2.  
  3. def run(n):
  4.     semaphore.acquire()
  5.     time.sleep(1)
  6.     print("run the thread:%s"%n)
  7.     semaphore.release()
  8.  
  9. if __name__ == '__main__':
  10.     # 最多允許5個線程同時運行
  11.     semaphore = threading.BoundedSemaphore(5)
  12.     for i in range(20):
  13.         t = threading.Thread(target=run,args=(i,))
  14.         t.start()
  15.  
  16. while threading.active_count() != 1:
  17.     # print(threading.active_count())
  18.     pass
  19. else:
  20.     print("all threads done".center(50,"*"))

Timer(定時器)

Timer隔一定時間調用一個函數,如果想實現每隔一段時間就調用一個函數,就要在Timer調用的函數中,再次設置Timer。Timer是Thread的一個派生類。

  1. import threading
  2.  
  3. def hello():
  4.     print("hello,world!")
  5. # delay 5秒之後執行hello函數
  6. t = threading.Timer(5,hello)
  7. t.start()

Event

Python提供了Event對象用於線程間通信,它是有線程設置的信號標誌,如果信號標誌位為假,則線程等待指導信號被其他線程設置為真。Event對象實現了簡單的線程通信機制,它提供了設置信號、清除信號、等待等用於實現線程間的通信。

  1. 設置信號

使用Event的set()方法可以設置Event對象內部的信號標誌為真。Event對象提供了isSet()方法來判斷其內部信號標誌的轉態,當使用event對象的set()方法後,isSet()方法返回真。

  1. 清除信號

使用Event的clear()方法可以清除Event對象內部的信號標誌,即將其設為假,當使用Event的clear()方法後,isSet()方法返回假。

  1. 等待

Event的wait()方法只有在內部信號為真的時候才會很快的執行並完成返回。當Event對象的內部信號標誌為假時,則wait()方法一直等待其為真時才返回。

通過Event來實現兩個或多個線程間的交互,下麵以紅綠燈為例,即啟動一個線程做交通指揮燈,生成幾個線程做車輛,車輛行駛按紅停綠行的規則。

  1. import threading,time,random
  2.  
  3. def light():
  4.     if not event.isSet():
  5.         event.set()
  6.     count = 0
  7.     while True:
  8.         if count < 5:
  9.             print("\033[42;1m--green light on--\033[0m".center(50,"*"))
  10.         elif count < 8:
  11.             print("\033[43;1m--yellow light on--\033[0m".center(50,"*"))
  12.         elif count < 13:
  13.             if event.isSet():
  14.                 event.clear()
  15.             print("\033[41;1m--red light on--\033[0m".center(50,"*"))
  16.         else:
  17.             count = 0
  18.             event.set()
  19.         time.sleep(1)
  20.         count += 1
  21.  
  22.  
  23. def car(n):
  24.     while 1:
  25.         time.sleep(random.randrange(10))
  26.         if event.isSet():
  27.             print("car %s is running..."%n)
  28.         else:
  29.             print("car %s is waiting for the red light..."%n)
  30.  
  31. if __name__ == "__main__":
  32.     event = threading.Event()
  33.     Light = threading.Thread(target=light,)
  34.     Light.start()
  35.  
  36.     for i in range(3):
  37.         t = threading.Thread(target=car,args=(i,))
  38.         t.start()

queue隊列

Python中隊列是線程間最常用的交換數據的形式。Queue模塊是提供隊列操作的模塊。

創建一個隊列對象

  1. import queue
  2.  
  3. 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)

常用方法

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

 

 

 

 

 

 

 

 

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1 var radios = Ext.create('Ext.form.Panel', { 2 title: 'RadioGroup Example', 3 width: 300, 4 height: 125, 5 bodyPadding: 10, 6 renderTo: Ext.getBody()... ...
  • 1 #-*- coding:utf-8 -*- 2 from urllib import request 3 from bs4 import BeautifulSoup 4 from urllib import parse 5 import pymysql 6 url = "http://searc ...
  • 題目描述 給出一個長度為N的非負整數序列A[i],對於所有1 ≤ k ≤ (N + 1) / 2,輸出A[1], A[3], …, A[2k - 1]的中位數。[color=red]即[/color]前1,3,5,……個數的中位數。 輸入輸出格式 輸入格式: 輸入文件median.in的第1行為一個 ...
  • 一、獲取Tag 1.find_all方法 (1)find_all(name='tag_name',attrs,recursive,text,**kwargs) name:tag對應的名稱,當包括多個tag時,返回的是一個列表,可以利用列表功能選取對應的tag。 href:tag的屬性,常用屬性cla ...
  • 1 html_doc = "" 2 sp = BeautifulSoup(html_doc,"html.parser") 3 print(sp.p['class']) 4 #['body','strikeout'] 5 print(sp.p['id']) 6 #zhangsan 7 8 html_d... ...
  • 課程中詳細演示了一個應用從單塊架構到垂直應用架構再到分散式服務架構的演進過程。講解瞭如何在前後端分離的架構下設計RESTful API。最終的系統對外提供REST風格的http服務,內部各個垂直應用通過dubbo共用無狀態的Java服務。整個系統在Web層和服務層都可以無縫的橫向擴展。 ...
  • 抓取內容: 從最好大學網上抓取中國前10的大學排名、大學名稱、總分,並數據格式左對齊。 http://www.zuihaodaxue.cn/zuihaodaxuepaiming2017.html 首先,看下網站的Robots協議,沒有找到相關協議,可以抓取。 http://www.zuihaodax ...
  • var a,f:array[1..10000000] of longint; b,i,j,n,s,x,y:longint;begin read(b,n); for i:=1 to n do a[i]:=0; for i:=1 to n do begin readln(x,y); a[x]:=1-a[ ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...