多任務可以由多進程完成呵哨,也可以由一個進程內的多線程完成。
我們前面提到了進程是由若干線程組成的轨奄,一個進程至少有一個線程孟害。由于線程是操作系統(tǒng)直接支持的執(zhí)行單元,因此挪拟,高級語言通常都內置多線程的支持挨务,Python也不例外,并且玉组,Python的線程是真正的Posix Thread谎柄,而不是模擬出來的線程。
Python的標準庫提供了兩個模塊:_thread和threading惯雳,_thread是低級模塊朝巫,threading是高級模塊,對_thread進行了封裝石景。絕大多數(shù)情況下劈猿,我們只需要使用threading這個高級模塊。
啟動一個線程就是把一個函數(shù)傳入并創(chuàng)建Thread實例潮孽,然后調用start()開始執(zhí)行
以下是單線程的例子:
import time
import threading
def saySorry():
print("親愛的揪荣,我錯了,我能吃飯了嗎往史?")
time.sleep(1)
if __name__ == "__main__":
for i in range(5):
#打印當前線程的名字
print(threading.current_thread().name)
saySorry()
結果如下:
這全部都是main線程執(zhí)行的仗颈,運行時間比較慢。接下來采用多線程進行上面的操作椎例。
import threading
import time
def saySorry():
print(threading.current_thread().name)
print("親愛的挨决,我錯了请祖,我能吃飯了嗎?")
time.sleep(1)
if __name__ == "__main__":
for i in range(5):
t = threading.Thread(target=saySorry)
t.start() # 啟動線程凰棉,即讓線程開始執(zhí)行
結果如下:
可以明顯看出使用了多線程并發(fā)的操作损拢,花費時間要短很多
創(chuàng)建好的線程,需要調用start()方法來啟動撒犀。主線程會等待所有的子線程運行結束才會結束福压。
import threading
import time
def sing():
for i in range(3):
print("正在唱歌...%d"%i)
time.sleep(1)
def dance():
for i in range(3):
print("正在跳舞...%d"%i)
time.sleep(1)
if __name__ == '__main__':
print('---開始---:%s'%time.ctime())
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
#time.sleep(5)
print('---結束---:%s'%time.ctime())
結果如下:
使用threading.enumerate可以查看線程數(shù)量。
import threading
import time
def sing():
for i in range(3):
print("正在唱歌...%d"%i)
time.sleep(1)
def dance():
for i in range(3):
print("正在跳舞...%d"%i)
time.sleep(1)
if __name__ == '__main__':
print('---開始---:%s'%time.ctime())
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
while True:
length = len(threading.enumerate())
print('當前運行的線程數(shù)為:%d'%length)
if length<=1:
break
time.sleep(0.5)
結果如下:
從這個例子也可以看出主線程會等待所有的子線程運行結束或舞。
根據(jù)我們對進程的學習荆姆,線程同樣可以通過繼承的方法創(chuàng)建。
import threading
import time
class MyThread(threading.Thread):
def run(self):
for i in range(3):
time.sleep(1)
msg = "I'm "+self.name+' @ '+str(i) #name屬性中保存的是當前線程的名字
print(msg)
if __name__ == '__main__':
t = MyThread()
t.start()
結果如下:
跟進程一樣映凳,可以在創(chuàng)建線程的時候進行參數(shù)的傳遞胆筒。比如修改線程的名字,代碼如下:
import threading
import time
class MyThread(threading.Thread):
def __init__(self,name):
super().__init__()
self.name = name
def run(self):
for i in range(3):
time.sleep(1)
msg = "I'm "+self.name+' @ '+str(i) #name屬性中保存的是當前線程的名字
print(msg)
if __name__ == '__main__':
t = MyThread('xx')
t.start()
結果如下:
python的threading.Thread類有一個run方法诈豌,用于定義線程的功能函數(shù)仆救,可以在自己的線程類中覆蓋該方法。而創(chuàng)建自己的線程實例后矫渔,通過Thread類的start方法彤蔽,可以啟動該線程,交給python虛擬機進行調度庙洼,當該線程獲得執(zhí)行的機會時顿痪,就會調用run方法執(zhí)行線程。
關于線程的執(zhí)行順序油够,看以下代碼:
import threading
import time
class MyThread(threading.Thread):
def run(self):
for i in range(3):
time.sleep(1)
msg = "I'm "+self.name+' @ '+str(i)
print(msg)
def test():
for i in range(5):
t = MyThread()
t.start()
if __name__ == '__main__':
test()
結果如下:
從代碼和執(zhí)行結果我們可以看出蚁袭,多線程程序的執(zhí)行順序是不確定的。當執(zhí)行到sleep語句時石咬,線程將被阻塞(Blocked)揩悄,到sleep結束后,線程進入就緒(Runnable)狀態(tài)鬼悠,等待調度虏束。而線程調度將自行選擇一個線程執(zhí)行。上面的代碼中只能保證每個線程都運行完整個run函數(shù)厦章,但是線程的啟動順序镇匀、run函數(shù)中每次循環(huán)的執(zhí)行順序都不能確定。
從以上例子我們可以總結出:每個線程一定會有一個名字袜啃,盡管上面的例子中沒有指定線程對象的name汗侵,但是python會自動為線程指定一個名字。當線程的run()方法結束時該線程完成。無法控制線程調度程序晰韵,但可以通過別的方式來影響線程調度的方式发乔。
線程的幾種狀態(tài)
線程在運行時具有三種基本狀態(tài):
1、執(zhí)行狀態(tài)雪猪,表示線程已獲得處理機而正在運行栏尚;
2、就緒狀態(tài)只恨,指線程已具備了各種執(zhí)行條件译仗,只須再獲得CPU便可立即執(zhí)行;
3官觅、阻塞狀態(tài):指線程在執(zhí)行中因某事件受阻而處于暫停狀態(tài)纵菌,例如,當一個線程執(zhí)行從鍵盤讀入數(shù)據(jù)的系統(tǒng)調用時休涤,該線程就被阻塞咱圆。
在學習進程的時候,我們知道功氨,進程的全局變量是不共享的序苏,每個進程各運行各的,互不影響捷凄,也就是說進程無法修改全局變量忱详。那線程全局變量是否可以共享、修改呢纵势?看如下代碼:
import threading
import time
# 全局變量
g_num = 100
# 修改全局變量
def work1():
# 如果想修改全局變量踱阿,必須加global
global g_num
for i in range(3):
g_num += 1
print("----in work1, g_num is %d---" % g_num)
# 獲取全局變量
def work2():
global g_num
print("----in work2, g_num is %d---" % g_num)
# 將功能包裝成函數(shù)方法
def main():
print("---線程創(chuàng)建之前g_num is %d---" % g_num)
# 創(chuàng)建一個線程
t1 = threading.Thread(target=work1)
# 就緒
t1.start()
# 延時一會管钳,保證t1線程中的事情做完
time.sleep(1)
t2 = threading.Thread(target=work2)
t2.start()
if __name__ == '__main__':
# 調用方法
main()
結果如下:
多線程和多進程最大的不同在于钦铁,多進程中,同一個變量才漆,各自有一份拷貝存在于每個進程中牛曹,互不影響,而多線程中醇滥,所有變量都由所有線程共享黎比,所以,任何一個變量都可以被任何一個線程修改鸳玩,因此阅虫,線程之間共享數(shù)據(jù)最大的危險在于多個線程同時改一個變量,把內容給改亂了不跟。來看看多個線程同時操作一個變量怎么把內容給改亂了:
import threading
import time
# 全局變量
g_num = 0
# 函數(shù)1
def test1():
global g_num
for i in range(1000000):
g_num = g_num+1
print("---test1---g_num=%d" % g_num)
# 函數(shù)2
def test2():
global g_num
for i in range(1000000):
g_num = g_num + 1
print("---test2---g_num=%d" % g_num)
def main():
#創(chuàng)建線程
t1 = threading.Thread(target=test1)
#就緒
t1.start()
# 創(chuàng)建線程
t2 = threading.Thread(target=test2)
# 就緒
t2.start()
time.sleep(5)
print("---main---g_num=%d" % g_num)
if __name__ == '__main__':
main()
結果如下:
多線程公用全局變量可能存在的問題假設兩個線程t1和t2都要對g_num=0進行增1運算颓帝,t1和t2都各對g_num修改1000000次,g_num的最終的結果應該為2000000。
為什么會出現(xiàn)這樣的結果购城,原因在于高級語言的一條語句在CPU執(zhí)行時是若干條語句吕座,即使是這么簡單的一個計算:g_num = g_num+1也會分成兩部:
計算g_num+1,存入臨時變量中瘪板;
將臨時變量的值賦給g_num吴趴。
可以看成是:
temp = g_num+1
g_num = temp
由于temp是局部變量,兩個線程各自都有自己的temp侮攀。按理想狀態(tài)下锣枝,代碼是這么運行的:
初始值 g_num = 0
t1: temp = g_num + 1 # temp = 0 + 1 = 1
t1: g_num = temp # g_num = 1
t2: temp = g_num + 1 # temp = 1 + 1 = 2
t2: g_num = temp # g_num = 2
就這樣一直循環(huán),最后將得到2000000的結果魏身。
但是t1和t2是交替運行的惊橱,如果操作系統(tǒng)以下面的順序執(zhí)行t1、t2:
初始值 g_num = 0
t1: temp = g_num + 1 # temp = 0 + 1 = 1
t2: temp = g_num + 1 # temp = 0 + 1 = 1
t2: g_num = temp # g_num = 1
t1: g_num = temp # g_num = 1
結果就是我們剛才看到的箭昵,這就是由于多線程訪問税朴,有可能出現(xiàn)下面情況:在g_num=0時,t1取得g_num=0家制。此時系統(tǒng)把t1調度為”sleeping”狀態(tài)正林,把t2轉換為”running”狀態(tài),t2也獲得g_num=0颤殴。然后t2對得到的值進行加1并賦給g_num觅廓,使得g_num=1。然后系統(tǒng)又把t2調度為”sleeping”涵但,把t1轉為”running”杈绸。線程t1又把它之前得到的0加1后賦值給g_num。這樣矮瘟,明明t1和t2都完成了1次加1工作瞳脓,但結果仍然是g_num=1。
問題產(chǎn)生的原因就是沒有控制多個線程對同一資源的訪問澈侠,對數(shù)據(jù)造成破壞劫侧,使得線程運行的結果不可預期。這種現(xiàn)象稱為“線程不安全”哨啃。
究其原因烧栋,是因為修改g_num需要多條語句,而執(zhí)行這幾條語句時拳球,線程可能中斷审姓,從而導致多個線程把同一個對象的內容改亂了。所以祝峻,我們必須確保一個線程在修改g_num的時候魔吐,別的線程一定不能改次坡。系統(tǒng)調用t1,然后獲取到num的值為0画畅,此時上一把鎖砸琅,即不允許其他線程操作num,對num的值進行+1轴踱,解鎖症脂。此時num的值為1,其他的線程就可以使用num了淫僻,而且是num的值不是0而是1诱篷。同理其他線程在對num進行修改時,都要先上鎖雳灵,處理完后再解鎖棕所,在上鎖的整個過程中不允許其他線程訪問,就保證了數(shù)據(jù)的正確性悯辙。
import threading
# 全局變量
g_num = 0
myLock = threading.Lock()
# 函數(shù)1
def test1():
global g_num
# 上鎖
myLock.acquire()
print('test1...上鎖...')
for i in range(1000000):
g_num = g_num+1
# 開鎖
myLock.release()
print('test1...開鎖...')
print("---test1---g_num=%d" % g_num)
# 函數(shù)2
def test2():
print('test2...')
'''
print('g_num:%s'%g_num)
如使用這句話將報錯SyntaxWarning: name 'g_num' is used
prior to global declaration global g_num
'''
global g_num
myLock.acquire()
print('test2...上鎖...')
print('g_num:%s'%g_num)
for i in range(1000000):
g_num = g_num + 1
# 開鎖
myLock.release()
print('test2...開鎖...')
print("---test2---g_num=%d" % g_num)
def main():
#創(chuàng)建線程
t1 = threading.Thread(target=test1)
#就緒
t1.start()
# 創(chuàng)建線程
t2 = threading.Thread(target=test2)
# 就緒
t2.start()
print("---main---g_num=%d" % g_num)
if __name__ == '__main__':
main()
結果如下:
上鎖是為了保護線程訪問數(shù)據(jù)的唯一性琳省,并不妨礙上鎖后,cpu對線程的切換躲撰,當上鎖的代碼段執(zhí)行過程中针贬,cpu切換走,其他線程如果是上鎖等待拢蛋,則cpu繼續(xù)切換桦他,如果cpu切換的其他線程不是上鎖等待,則其他線程依然可執(zhí)行谆棱。cpu的線程切換是不固定的快压。并不是一定要等待上鎖的程序走完。這也就解釋了為什么打印test1...上鎖...后又打印了test2...垃瞧。
當一個線程調用鎖的acquire()方法獲得鎖時蔫劣,鎖就進入“l(fā)ocked”狀態(tài)。每次只有一個線程可以獲得鎖皆警。如果此時另一個線程試圖獲得這個鎖拦宣,該線程就會變?yōu)椤癰locked”狀態(tài)截粗,稱為“阻塞”信姓,直到擁有鎖的線程調用鎖的release()方法釋放鎖之后,鎖進入“unlocked”狀態(tài)绸罗。
鎖的好處:
確保了某段關鍵代碼只能由一個線程從頭到尾完整地執(zhí)行
鎖的壞處:
阻止了多線程并發(fā)執(zhí)行意推,包含鎖的某段代碼實際上只能以單線程模式執(zhí)行,效率就大大地下降了珊蟀。由于可以存在多個鎖菊值,不同的線程持有不同的鎖外驱,并試圖獲取對方持有的鎖時,可能會造成死鎖腻窒。
從剛才的例子中可以看出對于全局變量昵宇,在多線程中要格外小心,否則容易造成數(shù)據(jù)錯亂的情況發(fā)生儿子。那對于非全局變量是否需要加鎖呢瓦哎?看如下代碼:
import threading
import time
class MyThread(threading.Thread):
# 重寫 構造方法
def __init__(self, num, sleepTime):
# 重寫父類的__init__()方法
threading.Thread.__init__(self)
self.num = num
self.sleepTime = sleepTime
def run(self):
self.num += 1
time.sleep(self.sleepTime)
print('線程(%s),num=%d' % (self.name, self.num))
if __name__ == '__main__':
mutex = threading.Lock()
t1 = MyThread(100, 5)
t1.start()
t2 = MyThread(200, 1)
t2.start()
結果如下:
再觀察如下代碼:
import threading
import time
def test(sleepTime):
num = 1
time.sleep(sleepTime)
num += 1
print('---(%s)--num=%d' % (threading.current_thread(), num))
t1 = threading.Thread(target=test, args=(5,))
t2 = threading.Thread(target=test, args=(1,))
t1.start()
t2.start()
結果如下:
也就是說在多線程開發(fā)中,全局變量是多個線程都共享的數(shù)據(jù)柔逼,而局部變量等是各自線程的蒋譬,是非共享的。
在剛才講鎖的時候說到了死鎖愉适,那么什么是死鎖呢犯助?在線程間共享多個資源的時候,如果兩個線程分別占有一部分資源并且同時等待對方的資源维咸,就會造成死鎖剂买。盡管死鎖很少發(fā)生,但一旦發(fā)生就會造成應用的停止響應癌蓖。下面看一個死鎖的例子:
import threading
import time
class MyThread1(threading.Thread):
def run(self):
if mutexA.acquire():
print(self.name + '----do1---up----')
time.sleep(1)
if mutexB.acquire():
print(self.name + '----do1---down----')
mutexB.release()
mutexA.release()
class MyThread2(threading.Thread):
def run(self):
if mutexB.acquire():
print(self.name + '----do2---up----')
time.sleep(1)
if mutexA.acquire():
print(self.name + '----do2---down----')
mutexA.release()
mutexB.release()
mutexA = threading.Lock()
mutexB = threading.Lock()
if __name__ == '__main__':
t1 = MyThread1()
t2 = MyThread2()
t1.start()
t2.start()
結果如下:
那我們如何避免死鎖呢雷恃?常用的是在程序設計時要盡量避免(比如采用銀行家算法),要么添加超時時間等费坊。
有了以上的知識倒槐,就可以讓線程按我們的需求的順序執(zhí)行:
import threading
import time
class Task1(threading.Thread):
def run(self):
while True:
if lock1.acquire():
print("------Task 1 -----")
time.sleep(0.5)
lock2.release()
class Task2(threading.Thread):
def run(self):
while True:
if lock2.acquire():
print("------Task 2 -----")
time.sleep(0.5)
lock3.release()
class Task3(threading.Thread):
def run(self):
while True:
if lock3.acquire():
print("------Task 3 -----")
time.sleep(0.5)
lock1.release()
# 使用Lock創(chuàng)建出的鎖默認沒有“鎖上”
lock1 = threading.Lock()
# 創(chuàng)建另外一把鎖,并且“鎖上”
lock2 = threading.Lock()
lock2.acquire()
# 創(chuàng)建另外一把鎖附井,并且“鎖上”
lock3 = threading.Lock()
lock3.acquire()
t1 = Task1()
t2 = Task2()
t3 = Task3()
t1.start()
t2.start()
t3.start()
結果如下:
也就是說我們可以可以使用互斥鎖完成多個任務讨越,有序的進程工作,這就是線程的同步永毅。接下來把跨,我們模擬一下車站的賣票:
import threading
import time
import os
def doChore(): # 作為間隔 每次調用間隔0.5s
time.sleep(0.5)
def booth(tid):
global i
global lock
while True:
lock.acquire() # 得到一個鎖,鎖定
if i != 0:
i = i - 1 # 售票 售出一張減少一張
print(tid, ':now left:', i) # 剩下的票數(shù)
doChore()
else:
print("Thread_id", tid, " No more tickets")
os._exit(0) # 票售完 退出程序
lock.release() # 釋放鎖
doChore()
# 全局變量
i = 15 # 初始化票數(shù)
lock = threading.Lock() # 創(chuàng)建鎖
def main():
# 總共設置了3個線程
for k in range(3):
# 創(chuàng)建線程; Python使用threading.Thread對象來代表線程
new_thread = threading.Thread(target=booth, args=(k,))
# 調用start()方法啟動線程
new_thread.start()
if __name__ == '__main__':
main()
結果如下:
以上就是關于線程同步的總結沼死。
Python的Queue模塊中提供了同步的着逐、線程安全的隊列類,包括FIFO(先入先出)隊列Queue意蛀,LIFO(后入先出)隊列LifoQueue耸别,和優(yōu)先級隊列PriorityQueue。這些隊列都實現(xiàn)了鎖原語(可以理解為原子操作县钥,即要么不做秀姐,要么就做完),能夠在多線程中直接使用若贮∈∮校可以使用隊列來實現(xiàn)線程間的同步痒留。
用FIFO隊列實現(xiàn)上述生產(chǎn)者與消費者問題的代碼如下:
import threading
import time
import queue
class Producer(threading.Thread):
def run(self):
global queue
count = 0
while True:
if queue.qsize() < 1000:
for i in range(100):
count = count + 1
msg = '生成產(chǎn)品' + str(count)
queue.put(msg)
print(msg)
time.sleep(0.5)
class Consumer(threading.Thread):
def run(self):
global queue
while True:
if queue.qsize() > 100:
for i in range(3):
msg = self.name + '消費了 ' + queue.get()
print(msg)
time.sleep(1)
if __name__ == '__main__':
queue = queue.Queue()
for i in range(500):
queue.put('初始產(chǎn)品' + str(i))
for i in range(2):
p = Producer()
p.start()
for i in range(5):
c = Consumer()
c.start()
結果如下:
為什么要使用生產(chǎn)者和消費者模式?
在線程世界里蠢沿,生產(chǎn)者就是生產(chǎn)數(shù)據(jù)的線程伸头,消費者就是消費數(shù)據(jù)的線程。在多線程開發(fā)當中舷蟀,如果生產(chǎn)者處理速度很快熊锭,而消費者處理速度很慢,那么生產(chǎn)者就必須等待消費者處理完雪侥,才能繼續(xù)生產(chǎn)數(shù)據(jù)碗殷。同樣的道理,如果消費者的處理能力大于生產(chǎn)者速缨,那么消費者就必須等待生產(chǎn)者锌妻。為了解決這個問題于是引入了生產(chǎn)者和消費者模式。
什么是生產(chǎn)者消費者模式旬牲?
生產(chǎn)者消費者模式是通過一個容器來解決生產(chǎn)者和消費者的強耦合問題仿粹。生產(chǎn)者和消費者彼此之間不直接通訊,而通過阻塞隊列來進行通訊原茅,所以生產(chǎn)者生產(chǎn)完數(shù)據(jù)之后不用等待消費者處理吭历,直接扔給阻塞隊列,消費者不找生產(chǎn)者要數(shù)據(jù)擂橘,而是直接從阻塞隊列里取晌区,阻塞隊列就相當于一個緩沖區(qū),平衡了生產(chǎn)者和消費者的處理能力通贞。
這個阻塞隊列就是用來給生產(chǎn)者和消費者解耦的朗若。縱觀大多數(shù)設計模式昌罩,都會找一個第三者出來進行解耦哭懈。
- Queue的說明
對于Queue,在多線程通信之間扮演重要的角色
1茎用、添加數(shù)據(jù)到隊列中遣总,使用put()方法
2、從隊列中取數(shù)據(jù)轨功,使用get()方法
3旭斥、判斷隊列中是否還有數(shù)據(jù),使用qsize()方法
在多線程中夯辖,如果使用全局變量琉预,會出問題董饰,一旦一個線程修改了蒿褂,會影響其它線程圆米。如果使用局部變量,一旦想讓這個局部變量啄栓,一直使用下去娄帖,一直傳參,比較麻煩昙楚。既不想使用全局變量近速,又一直想用這個局部變量】熬桑可以將這個局部變量綁定到對應的線程中削葱,以后此線程一直用個。與其它線程中的無關淳梦。
嘗試用一個全局dict存放所有的Student對象析砸,然后以thread自身作為key獲得線程對應的Student對象:
import threading
# 創(chuàng)建字典對象
myDict = {}
def process_student():
# 獲取當前線程關聯(lián)的student
std = myDict[threading.current_thread()]
print('Hello, %s (in %s)' % (std, threading.current_thread().name))
def process_thread(name):
# 綁定ThreadLocal的student
myDict[threading.current_thread()] = name
process_student()
t1 = threading.Thread(target=process_thread, args=('yongGe',), name='Thread-A')
t2 = threading.Thread(target=process_thread, args=('老王',), name='Thread-B')
t1.start()
t2.start()
結果如下:
這種方式理論上是可行的,它最大的優(yōu)點是消除了std對象在每層函數(shù)中的傳遞問題爆袍,但是首繁,每個函數(shù)獲取std的代碼有點low≡赡遥看下面的代碼:
import threading
# 創(chuàng)建全局ThreadLocal對象
local_school = threading.local()
def process_student():
# 獲取當前線程關聯(lián)的student
std = local_school.student
print('Hello, %s (in %s)' % (std, threading.current_thread().name))
def process_thread(name):
# 綁定ThreadLocal的student
local_school.student = name
process_student()
t1 = threading.Thread(target=process_thread, args=('yongGe',), name='Thread-A')
t2 = threading.Thread(target=process_thread, args=('老王',), name='Thread-B')
t1.start()
t2.start()
結果如下:
全局變量local_school就是一個ThreadLocal對象弦疮,每個Thread對它都可以讀寫student屬性,但互不影響蜘醋。你可以把local_school看成全局變量胁塞,但每個屬性如local_school.student都是線程的局部變量,可以任意讀寫而互不干擾压语,也不用管理鎖的問題闲先,ThreadLocal內部會處理∥薹洌可以理解為全局變量local_school是一個dict伺糠,不但可以用local_school.student,還可以綁定其他變量斥季,如local_school.teacher等等训桶。ThreadLocal最常用的地方就是為每個線程綁定一個數(shù)據(jù)庫連接,HTTP請求酣倾,用戶身份信息等舵揭,這樣一個線程的所有調用到的處理函數(shù)都可以非常方便地訪問這些資源。一個ThreadLocal變量雖然是全局變量躁锡,但每個線程都只能讀寫自己線程的獨立副本午绳,互不干擾。ThreadLocal解決了參數(shù)在一個線程中各個函數(shù)之間互相傳遞的問題映之。
異步
from multiprocessing import Pool
import time
import os
def test():
print("---進程池中的進程---pid=%d,ppid=%d--" % (os.getpid(), os.getppid()))
for i in range(3):
print("----%d---" % i)
return "老王"
def test2(args):
print('1...')
print("---callback func--pid=%d" % os.getpid())
print("---callback func--args=%s" % args)
print('2...')
if __name__ == '__main__':
pool = Pool(3)
# callback表示前面的func方法執(zhí)行完拦焚,再執(zhí)行callback,并且可以獲取func的返回值作為callback的參數(shù)
pool.apply_async(func=test, callback=test2)
# pool.apply_async(func=test)
# 模擬主進程在做任務
time.sleep(5)
print("----主進程-pid=%d.....----" % os.getpid())
結果如下:
可以看到執(zhí)行callback的是主進程蜡坊。也就是說當主進程在做任務時,進程又分出了一個線程去做其他任務赎败。
多線程編程秕衙,模型復雜,容易發(fā)生沖突僵刮,必須用鎖加以隔離据忘,同時,又要小心死鎖的發(fā)生搞糕。