程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

Python中線程間通信

編輯:Python

Python中線程間通信

  • 一、前言
  • 二、什麼是互斥鎖
  • 三、使用互斥鎖
  • 四、使用隊列在線程間通信
  • 五、關於線程需要注意的兩點


一、前言

我們已經知道進程之間不能直接共享信息,那麼線程之間可以共享信息嗎?我們通過一個例子來驗證一下。定義一個全局變量g_num,分別創建2個子進程對g_num執行不同的操作,並輸出操作後的結果。代碼如下:

# _*_ coding:utf-8 _*_
from threading import Thread # 導入線程
import time
def plus():
print("-------子線程1開始---------")
global g_num # 定義全局變量
g_num += 50 # 全局變量值加50
print("g_num is %d" % g_num)
print("-------子線程1結束---------")
def minus():
time.sleep(2)
print("-------子線程2開始---------")
global g_num # 定義全局變量
g_num -= 50 # 全局變量值減50
print("g_num is %d" % g_num)
print("-------子線程2結束---------")
g_num = 100 # 定義一個全局變量
if __name__ == "__main__":
print("----------主線程開始-------------")
print("g_num is %d" % g_num)
t1 = Thread(target=plus) # 實例化線程t1
t2 = Thread(target=minus) # 實例化線程t2
t1.start() # 開啟線程t1
t2.start() # 開啟線程t2
t1.join() # 等待t1線程結束
t2.join() # 等待t2線程結束
print("--------主線程結束-------------")

上述代碼中,定義一個全局變量g_num,賦值為100,然後創建2個線程。一個線程將g_num增加50,一個線程將g_num減少50。如果g_num的最終結果為100,則說明線程之間可以共享數據,運行結果如下圖所示:

從上面的例子可以得出,在一個進程內的所有線程共享全局變量,能夠在不使用其他方式的前提下完成多線程之間的數據共享。


二、什麼是互斥鎖

由於線程可以對全局變量隨意修改,這就可能造成多線程之間對線程的混亂操作。以房子為例,當房子內只有一個居住者時(單線程),他可以任意時刻使用任意一個房間,如廚房、臥室和衛生間等。但是,當這個房子有多個居住者時(多線程),他就不能在任意時刻使用某些房間,如衛生間,否則會造成混亂。

如何解決這個問題呢?一個防止他人進入的簡單方法,就是門上加一把鎖。就是先到的人在門口排隊,等鎖打開再進去。如圖所示:

這就是“互斥鎖”(Mutual exclude,縮寫Mutex),防止多個線程同時讀寫某一塊內存區域。互斥鎖為資源引入一個狀態:鎖定和非鎖定。某個線程要更改共享數據時,先將其鎖定,此時資源的狀態為“鎖定”,其他線程不能更改;直到該線程釋放資源,將資源的狀態變成“非鎖定”時,其他的線程才能再次鎖定該資源。互斥鎖保證了每次只有一個線程進行寫入操作,從而保證了多線程情況下數據的准確性。


三、使用互斥鎖

在threading模塊中使用Lock類可以方便地處理鎖定。Lock類有兩個方法:acquire()鎖定和release()釋放鎖。實例用法如下:

mutex = threading.Lock() # 創建鎖
mutex.acquire([blocking]) # 鎖定
mutex.release() # 釋放鎖

語法如下:

acquire([blocking]) :獲取鎖定,如果有必要,需要阻塞到鎖定釋放為止。如果提供blocking參數並將設置為False,當無法獲取鎖定時將立即返回False;如果成功獲取鎖定則返回True。

release():釋放一個鎖定,當鎖定處於未鎖定狀態時,或者從與原本調用acquire()方法的不同線程調用此方法,將出現錯誤。

下面,通過一個示例學習一下如何使用互斥鎖。這裡使用多線程和互斥鎖模擬實現多人同時訂購電影票的功能,假設電影院某個場次只有100張電影票,10個用戶同時搶購該電影票。每出售一張,顯示一次剩余的電影票張數。代碼如下:

# _*_ coding:utf-8 _*_
from threading import Thread, Lock
import time
n = 100 # 共100張票
def task():
global n
mutex.acquire() # 上鎖
temp = n # 賦值給臨時變量
time.sleep(0.1) # 休眠0.1秒
n = temp - 1 # 數量減1
print("購買成功,剩余%d張電影票" % n)
mutex.release() # 釋放鎖
if __name__ == "__main__":
mutex = Lock() # 實例化Lock類
t_l = [] # 初始化一個列表
for i in range(10):
t = Thread(target=task) # 實例化線程類
t_l.append(t) # 將線程實例存入列表中
t.start() # 創建線程
for t in t_l:
t.join() # 等待子線程結束

上述代碼中,創建了10個線程,全部執行task()函數。為了解決資源競爭問題,使用mutex.acquire() 函數實現資源鎖定,第一個獲取資源的線程鎖定後,其他線程等待 mutex.release() 解鎖,所以每次只有一個線程執行task()函數。運行結果如下圖所示:


注意:
使用互斥鎖時,要避免死鎖。在多任務系統下,當一個或多個線程等待系統資源,而資源又被線程本身或其他線程占用時,就行程了死鎖,如圖所示:


四、使用隊列在線程間通信

我們知道multiprocessing 模塊的Queue隊列可以實現進程間通信,同樣在線程間,也可以使用Queue隊列實現線程間通信。不同之處在於我們需要使用queue 模塊的Queue隊列,而不是multiprocessing模塊的Queue隊列,但是Queue的使用方法相同。

使用Queue在線程間通信通常應用於生產這消費模式。產生數據的模塊稱為生產者,而處理數據的模塊稱為消費者。在生產者與消費者之間的緩沖區稱之為倉庫。生產者負責往倉庫運輸商品,而消費者負責從倉庫裡取出商品,這就構成了生產消費模式。下面通過一個示例學習一下使用Queue在線程間的通信。

定義一個生產者類Producer,定義一個消費者類Consumer。生產者生成5件產品,依次寫入隊列,而消費者依次從隊列中取出產品,代碼如下:

# _*_ coding:utf-8 _*_
from queue import Queue
import random, threading, time
# 生產者類
class Producer(threading.Thread):
def __init__(self, name, queue):
threading.Thread.__init__(self, name=name)
self.data = queue
def run(self):
for i in range(5):
print("生產者%s將產品%d加入隊列!" % (self.getName(), i))
self.data.put(i)
time.sleep(random.random())
print("生產者%s完成" % self.getName())
# 消費者類
class Consumer(threading.Thread):
def __init__(self, name, queue):
threading.Thread.__init__(self, name=name)
self.data = queue
def run(self):
for i in range(5):
val = self.data.get()
print("消費者%s將產品%d從隊列中取出!" % (self.getName(), val))
time.sleep(random.random())
print("消費者%s完成" % self.getName())
if __name__ == "__main__":
print("----------主線程開始------------")
queue = Queue() # 實例化隊列
producer = Producer("Producer", queue) # 實例化線程Producer,並傳入隊列作為參數
consumer = Consumer("Consumer", queue) # 實例化線程Consumer,並傳入隊列作為參數
producer.start() # 啟動線程Producer
consumer.start() # 啟動線程Consumer
producer.join() # 等待線程Producer結束
consumer.join() # 等待線程Consumer結束
print("----------主線程結束--------------")

運行結果如下圖所示:

注意:
由於程序中使用了random.random()函數生成0-1之間的隨機數,所以大家運行結果可能與圖不一樣。


五、關於線程需要注意的兩點

【1】進程和線程的區別

進程和線程的區別主要有:

(1)進程是系統進行資源分配和調度的一個單位,線程是一個實體,是CPU調度和分派的基本單位。

(2)進程之間相互獨立的,多進程中,同一個變量,各自有一份備份存在於每個進程中,但互不影響;而同一個進程的多個線程是內存共享的,所有變量都由所有線程共享。

(3)由於進程間是獨立的,因此一個進程的奔潰不會影響到其他進程;而線程是包含在進程之內的,線程奔潰會引發進程的奔潰,繼而導致同一進程內的其他進程也奔潰。


【2】多線程非全局變量是否要加鎖

在多線程開發中,全局變量是多個線程都共享的數據,為了防止數據混亂,通常使用互斥鎖。而局部變量等是各自線程的,是非共享的,所以不需要使用互斥鎖。



  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved