python 實(shí)現(xiàn)線程之間的通信示例
前言:因?yàn)镚IL的限制,python的線程是無法真正意義上并行的。相對于異步編程,其性能可以說不是一個等量級的。為什么我們還要學(xué)習(xí)多線程編程呢,雖然說異步編程好處多,但編程也較為復(fù)雜,邏輯不容易理解,學(xué)習(xí)成本和維護(hù)成本都比較高。畢竟我們大部分人還是適應(yīng)同步編碼的,除非一些需要高性能處理的地方采用異步。
首先普及下進(jìn)程和線程的概念:
進(jìn)程:進(jìn)程是操作系統(tǒng)資源分配的基本單位。
線程:線程是任務(wù)調(diào)度和執(zhí)行的基本單位。
一個應(yīng)用程序至少一個進(jìn)程,一個進(jìn)程至少一個線程。
兩者區(qū)別:同一進(jìn)程內(nèi)的線程共享本進(jìn)程的資源如內(nèi)存、I/O、cpu等,但是進(jìn)程之間的資源是獨(dú)立的。
一、多線程
python 可以通過 thread 或 threading 模塊實(shí)現(xiàn)多線程,threading 相比 thread 提供了更高階、更全面的線程管理。我們下文主要以 threading 模塊介紹多線程的基本用法。
import threading
import time
class thread(threading.Thread):
def __init__(self, threadname):
threading.Thread.__init__(self, name='線程' + threadname)
def run(self):
print('%s:Now timestamp is %s'%(self.name,time.time()))
threads = []
for a in range(int(5)): # 線程個數(shù)
threads.append(thread(str(a)))
for t in threads: # 開啟線程
t.start()
for t in threads: # 阻塞線程
t.join()
print('END')
輸出:
線程3:Now timestamp is 1557386184.7574518
線程2:Now timestamp is 1557386184.7574518
線程0:Now timestamp is 1557386184.7574518
線程1:Now timestamp is 1557386184.7574518
線程4:Now timestamp is 1557386184.7582724
END
start() 方法開啟子線程。運(yùn)行多次 start() 方法代表開啟多個子線程。
join() 方法用來阻塞主線程,等待子線程執(zhí)行完成。舉個例子,主線程A創(chuàng)建了子線程B,并使用了 join() 方法,主線程A在 join() 處就被阻塞了,等待子線程B完成后,主線程A才能執(zhí)行 print('END')。如果沒有使用 join() 方法,主線程A創(chuàng)建子線程B后,不會等待子線程B,直接執(zhí)行 print('END'),如下:
import threading
import time
class thread(threading.Thread):
def __init__(self, threadname):
threading.Thread.__init__(self, name='線程' + threadname)
def run(self):
time.sleep(1)
print('%s:Now timestamp is %s'%(self.name,time.time()))
threads = []
for a in range(int(5)): # 線程個數(shù)
threads.append(thread(str(a)))
for t in threads: # 開啟線程
t.start()
# for t in threads: # 阻塞線程
# t.join()
print('END')
輸出:
END
線程0:Now timestamp is 1557386321.376941
線程3:Now timestamp is 1557386321.377937
線程1:Now timestamp is 1557386321.377937
線程2:Now timestamp is 1557386321.377937
線程4:Now timestamp is 1557386321.377937
二、線程之間的通信
1.threading.Lock()
如果多個線程對某一資源同時進(jìn)行修改,可能會存在不可預(yù)知的情況。為了修改數(shù)據(jù)的正確性,需要把這個資源鎖住,只允許線程依次排隊(duì)進(jìn)去獲取這個資源。當(dāng)線程A操作完后,釋放鎖,線程B才能進(jìn)入。如下腳本是開啟多個線程修改變量的值,但輸出結(jié)果每次都不一樣。
import threading
money = 0
def Order(n):
global money
money = money + n
money = money - n
class thread(threading.Thread):
def __init__(self, threadname):
threading.Thread.__init__(self, name='線程' + threadname)
self.threadname = int(threadname)
def run(self):
for i in range(1000000):
Order(self.threadname)
t1 = thread('1')
t2 = thread('5')
t1.start()
t2.start()
t1.join()
t2.join()
print(money)
接下來我們用 threading.Lock() 鎖住這個變量,等操作完再釋放這個鎖。lock.acquire() 給資源加一把鎖,對資源處理完成之后,lock.release() 再釋放鎖。以下腳本執(zhí)行結(jié)果都是一樣的,但速度會變慢,因?yàn)榫€程只能一個個的通過。
import threading
money = 0
def Order(n):
global money
money = money + n
money = money - n
class thread(threading.Thread):
def __init__(self, threadname):
threading.Thread.__init__(self, name='線程' + threadname)
self.threadname = int(threadname)
def run(self):
for i in range(1000000):
lock.acquire()
Order(self.threadname)
lock.release()
# print('%s:Now timestamp is %s'%(self.name,time.time()))
lock = threading.Lock()
t1 = thread('1')
t2 = thread('5')
t1.start()
t2.start()
t1.join()
t2.join()
print(money)
2.threading.Rlock()
用法和 threading Lock() 一致,區(qū)別是 threading.Rlock() 允許多次鎖資源,acquire() 和 release() 必須成對出現(xiàn),也就是說加了幾把鎖就得釋放幾把鎖。
lock = threading.Lock()
# 死鎖
lock.acquire()
lock.acquire()
print('...')
lock.release()
lock.release()
rlock = threading.RLock()
# 同一線程內(nèi)不會阻塞線程
rlock.acquire()
rlock.acquire()
print('...')
rlock.release()
rlock.release()
3.threading.Condition()
threading.Condition() 可以理解為更加高級的鎖,比 Lock 和 Rlock 的用法更高級,能處理一些復(fù)雜的線程同步問題。threading.Condition() 創(chuàng)建一把資源鎖(默認(rèn)是Rlock),提供 acquire() 和 release() 方法,用法和 Rlock 一致。此外 Condition 還提供 wait()、Notify() 和 NotifyAll() 方法。
wait():線程掛起,直到收到一個 Notify() 通知或者超時(可選參數(shù)),wait() 必須在線程得到 Rlock 后才能使用。
Notify() :在線程掛起的時候,發(fā)送一個通知,讓 wait() 等待線程繼續(xù)運(yùn)行,Notify() 也必須在線程得到 Rlock 后才能使用。 Notify(n=1),最多喚醒 n 個線程。
NotifyAll() :在線程掛起的時候,發(fā)送通知,讓所有 wait() 阻塞的線程都繼續(xù)運(yùn)行。
舉例說明下 Condition() 使用
import threading,time
def TestA():
cond.acquire()
print('李白:看見一個敵人,請求支援')
cond.wait()
print('李白:好的')
cond.notify()
cond.release()
def TestB():
time.sleep(2)
cond.acquire()
print('亞瑟:等我...')
cond.notify()
cond.wait()
print('亞瑟:我到了,發(fā)起沖鋒...')
if __name__=='__main__':
cond = threading.Condition()
testA = threading.Thread(target=TestA)
testB = threading.Thread(target=TestB)
testA.start()
testB.start()
testA.join()
testB.join()
輸出
李白:看見一個敵人,請求支援
亞瑟:等我...
李白:好的
亞瑟:我到了,發(fā)起沖鋒...
4.threading.Event()
threading.Event() 原理是在線程中立了一個 Flag ,默認(rèn)值是 False ,當(dāng)一個或多個線程遇到 event.wait() 方法時阻塞,直到 Flag 值 變?yōu)?True 。threading.Event() 通常用來實(shí)現(xiàn)線程之間的通信,使一個線程等待其他線程的通知 ,把 Event 傳遞到線程對象中。
event.wait() :阻塞線程,直到 Flag 值變?yōu)?True
event.set() :設(shè)置 Flag 值為 True
event.clear() :修改 Flag 值為 False
event.isSet() : 僅當(dāng) Flag 值為 True 時返回
下面這個例子,主線程啟動子線程后 sleap 2秒,子線程因?yàn)?event.wait() 被阻塞。當(dāng)主線程醒來后執(zhí)行 event.set() ,子線程才繼續(xù)運(yùn)行,兩者輸出時間差 2s。
import threading
import datetime,time
class thread(threading.Thread):
def __init__(self, threadname):
threading.Thread.__init__(self, name='線程' + threadname)
self.threadname = int(threadname)
def run(self):
event.wait()
print('子線程運(yùn)行時間:%s'%datetime.datetime.now())
if __name__ == '__main__':
event = threading.Event()
t1 = thread('0')
#啟動子線程
t1.start()
print('主線程運(yùn)行時間:%s'%datetime.datetime.now())
time.sleep(2)
# Flag設(shè)置成True
event.set()
t1.join()
輸出
主線程運(yùn)行時間:2019-05-30 15:51:49.690872
子線程運(yùn)行時間:2019-05-30 15:51:51.691523
5.其他方法
threading.active_count():返回當(dāng)前存活的線程對象的數(shù)量
threading.current_thread():返回當(dāng)前線程對象
threading.enumerate():返回當(dāng)前所有線程對象的列表
threading.get_ident():返回線程pid
threading.main_thread():返回主線程對象
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
python處理自動化任務(wù)之同時批量修改word里面的內(nèi)容的方法
在本篇文章里小編給各位整理的是一篇關(guān)于利用python處理自動化任務(wù)之同時批量修改word里面的內(nèi)容的文章,需要的可以參考學(xué)習(xí)下。2019-08-08
Python進(jìn)度條可視化之監(jiān)測程序運(yùn)行速度
Tqdm是一個快速,可擴(kuò)展的Python進(jìn)度條,可以在Python長循環(huán)中添加一個進(jìn)度提示信息,用戶只需要封裝任意的迭代器即可。本文就主要介紹了通過進(jìn)度條檢測程序運(yùn)行速度,感興趣的同學(xué)可以學(xué)習(xí)一下2021-12-12
淺談Python從全局與局部變量到裝飾器的相關(guān)知識
今天給大家?guī)淼氖顷P(guān)于Python的相關(guān)知識,文章圍繞著Python從全局與局部變量到裝飾器的相關(guān)知識展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06
Python的Flask框架中集成CKeditor富文本編輯器的教程
在用Flask搭建網(wǎng)站時的后臺文章編輯器可以使用CKeditor,CKeditor所支持的文本樣式較多且開源,這里我們就來看一下Python的Flask框架中集成CKeditor富文本編輯器的教程2016-06-06
Python機(jī)器學(xué)習(xí)入門(四)之Python選擇模型
這篇文章主要介紹了Python機(jī)器學(xué)習(xí)入門知識,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-08-08

