python系統(tǒng)編程3

線程

1.同步概念

1.多線程開發(fā)可能遇到的問題

同步不是一起的意思,是協(xié)同步調(diào)


假設(shè)兩個(gè)線程t1和t2都要對(duì)num=0進(jìn)行增1運(yùn)算,t1和t2都各對(duì)num修改10次,num的最終的結(jié)果應(yīng)該為20。

但是由于是多線程訪問,有可能出現(xiàn)下面情況:

在num=0時(shí),t1取得num=0。此時(shí)系統(tǒng)把t1調(diào)度為”sleeping”狀態(tài),把t2轉(zhuǎn)換為”running”狀態(tài),t2也獲得num=0。然后t2對(duì)得到的值進(jìn)行加1并賦給num,使得num=1。然后系統(tǒng)又把t2調(diào)度為”sleeping”,把t1轉(zhuǎn)為”running”。線程t1又把它之前得到的0加1后賦值給num。這樣,明明t1和t2都完成了1次加1工作,但結(jié)果仍然是num=1。

from threading import Thread

import time


g_num = 0


def test1():

? ? global g_num

? ? for i in range(1000000):

? ? ? ? g_num += 1


? ? print("---test1---g_num=%d"%g_num)


def test2():

? ? global g_num

? ? for i in range(1000000):

? ? ? ? g_num += 1


? ? print("---test2---g_num=%d"%g_num)



p1 = Thread(target=test1)

p1.start()


# time.sleep(3) #取消屏蔽之后 再次運(yùn)行程序,結(jié)果會(huì)不一樣,,,為啥呢?


p2 = Thread(target=test2)

p2.start()


print("---g_num=%d---"%g_num)

運(yùn)行結(jié)果(可能不一樣,但是結(jié)果往往不是2000000):

---g_num=284672---

---test1---g_num=1166544

---test2---g_num=1406832

取消屏蔽之后,再次運(yùn)行結(jié)果如下:

---test1---g_num=1000000

---g_num=1041802---

---test2---g_num=2000000

問題產(chǎn)生的原因就是沒有控制多個(gè)線程對(duì)同一資源的訪問,對(duì)數(shù)據(jù)造成破壞,使得線程運(yùn)行的結(jié)果不可預(yù)期。這種現(xiàn)象稱為“線程不安全”。


2. 什么是同步

同步就是協(xié)同步調(diào),按預(yù)定的先后次序進(jìn)行運(yùn)行。如:你說完,我再說。

"同"字從字面上容易理解為一起動(dòng)作

其實(shí)不是,"同"字應(yīng)是指協(xié)同、協(xié)助、互相配合。

如進(jìn)程、線程同步,可理解為進(jìn)程或線程A和B一塊配合,A執(zhí)行到一定程度時(shí)要依靠B的某個(gè)結(jié)果,于是停下來,示意B運(yùn)行;B依言執(zhí)行,再將結(jié)果給A;A再繼續(xù)操作。

3. 解決問題的思路

對(duì)于本小節(jié)提出的那個(gè)計(jì)算錯(cuò)誤的問題,可以通過線程同步來進(jìn)行解決

思路,如下:

1.系統(tǒng)調(diào)用t1,然后獲取到num的值為0,此時(shí)上一把鎖,即不允許其他現(xiàn)在操作num

2.對(duì)num的值進(jìn)行+1

3.解鎖,此時(shí)num的值為1,其他的線程就可以使用num了,而且是num的值不是0而是1

4.同理其他線程在對(duì)num進(jìn)行修改時(shí),都要先上鎖,處理完后再解鎖,在上鎖的整個(gè)過程中不允許其他線程訪問,就保證了數(shù)據(jù)的正確性


2.互斥鎖

當(dāng)多個(gè)線程幾乎同時(shí)修改某一個(gè)共享數(shù)據(jù)的時(shí)候,需要進(jìn)行同步控制

線程同步能夠保證多個(gè)線程安全訪問競(jìng)爭(zhēng)資源,最簡(jiǎn)單的同步機(jī)制是引入互斥鎖。

互斥鎖為資源引入一個(gè)狀態(tài):鎖定/非鎖定。

某個(gè)線程要更改共享數(shù)據(jù)時(shí),先將其鎖定,此時(shí)資源的狀態(tài)為“鎖定”,其他線程不能更改;直到該線程釋放資源,將資源的狀態(tài)變成“非鎖定”,其他的線程才能再次鎖定該資源。互斥鎖保證了每次只有一個(gè)線程進(jìn)行寫入操作,從而保證了多線程情況下數(shù)據(jù)的正確性。

threading模塊中定義了Lock類,可以方便的處理鎖定:

#創(chuàng)建鎖

mutex = threading.Lock()

#鎖定

mutex.acquire([blocking])

#釋放

mutex.release()

其中,鎖定方法acquire可以有一個(gè)blocking參數(shù)。

?如果設(shè)定blocking為True,則當(dāng)前線程會(huì)堵塞,直到獲取到這個(gè)鎖為止(如果沒有指定,那么默認(rèn)為True)

?如果設(shè)定blocking為False,則當(dāng)前線程不會(huì)堵塞

使用互斥鎖實(shí)現(xiàn)上面的例子的代碼如下:

例子1:

from threading import Thread, Lock

import time


g_num = 0


def test1():

? ? global g_num

? ? for i in range(1000000):

? ? ? ? #True表示堵塞 即如果這個(gè)鎖在上鎖之前已經(jīng)被上鎖了,那么這個(gè)線程會(huì)在這里一直等待到解鎖為止?

? ? ? ? #False表示非堵塞,即不管本次調(diào)用能夠成功上鎖,都不會(huì)卡在這,而是繼續(xù)執(zhí)行下面的代碼

? ? ? ? mutexFlag = mutex.acquire(True)?

? ? ? ? if mutexFlag:

? ? ? ? ? ? g_num += 1

? ? ? ? ? ? mutex.release()


? ? print("---test1---g_num=%d"%g_num)


def test2():

? ? global g_num

? ? for i in range(1000000):

? ? ? ? mutexFlag = mutex.acquire(True) #True表示堵塞

? ? ? ? if mutexFlag:

? ? ? ? ? ? g_num += 1

? ? ? ? ? ? mutex.release()


? ? print("---test2---g_num=%d"%g_num)


#創(chuàng)建一個(gè)互斥鎖

#這個(gè)所默認(rèn)是未上鎖的狀態(tài)

mutex = Lock()


p1 = Thread(target=test1)

p1.start()



p2 = Thread(target=test2)

p2.start()


print("---g_num=%d---"%g_num)

運(yùn)行結(jié)果:

---g_num=61866---

---test1---g_num=1861180

---test2---g_num=2000000

可以看到,加入互斥鎖后,運(yùn)行結(jié)果與預(yù)期相符。


# Python主要通過標(biāo)準(zhǔn)庫中的threading包來實(shí)現(xiàn)多線程

import threading ?

import time

import os



def doChore(): ?# 作為間隔 ?每次調(diào)用間隔0.5s

? ? time.sleep(0.5)



def booth(tid):

? ? global i

? ? global lock

? ? while True:

? ? ? ? lock.acquire() ? ? ? ? ? ? ? ? ? ? ?# 得到一個(gè)鎖,鎖定

? ? ? ? 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():

? ? # 總共設(shè)置了3個(gè)線程

? ? for k in range(3):

? ? ? ? # 創(chuàng)建線程; Python使用threading.Thread對(duì)象來代表線程

? ? ? ? new_thread = threading.Thread(target=booth, args=(k,))

? ? ? ? # 調(diào)用start()方法啟動(dòng)線程

? ? ? ? new_thread.start()


if __name__ == '__main__':

main()


上鎖解鎖過程

當(dāng)一個(gè)線程調(diào)用鎖的acquire()方法獲得鎖時(shí),鎖就進(jìn)入“l(fā)ocked”狀態(tài)。

每次只有一個(gè)線程可以獲得鎖。如果此時(shí)另一個(gè)線程試圖獲得這個(gè)鎖,該線程就會(huì)變?yōu)椤癰locked”狀態(tài),稱為“阻塞”,直到擁有鎖的線程調(diào)用鎖的release()方法釋放鎖之后,鎖進(jìn)入“unlocked”狀態(tài)。

線程調(diào)度程序從處于同步阻塞狀態(tài)的線程中選擇一個(gè)來獲得鎖,并使得該線程進(jìn)入運(yùn)行(running)狀態(tài)。


總結(jié)

鎖的好處:

?確保了某段關(guān)鍵代碼只能由一個(gè)線程從頭到尾完整地執(zhí)行

鎖的壞處:

?阻止了多線程并發(fā)執(zhí)行,包含鎖的某段代碼實(shí)際上只能以單線程模式執(zhí)行,效率就大大地下降了

?由于可以存在多個(gè)鎖,不同的線程持有不同的鎖,并試圖獲取對(duì)方持有的鎖時(shí),可能會(huì)造成死鎖

3.多線程-共享數(shù)據(jù)

對(duì)于全局變量,在多線程中要格外小心,否則容易造成數(shù)據(jù)錯(cuò)亂的情況發(fā)生

1. 非全局變量是否要加鎖呢?

? ? #coding=utf-8

? ? import threading

? ? import time


? ? class MyThread(threading.Thread):

? ? ? ? # 重寫 構(gòu)造方法

? ? ? ? def __init__(self,num,sleepTime):

? ? ? ? ? ? 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()


再看一個(gè)

import threading

? ? from time import sleep


? ? def test(sleepTime):

? ? ? ? num=1

? ? ? ? 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()

小總結(jié)

?在多線程開發(fā)中,全局變量是多個(gè)線程都共享的數(shù)據(jù),而局部變量等是各自線程的,是非共享的

3.死鎖

1. 死鎖

在線程間共享多個(gè)資源的時(shí)候,如果兩個(gè)線程分別占有一部分資源并且同時(shí)等待對(duì)方的資源,就會(huì)造成死鎖。

盡管死鎖很少發(fā)生,但一旦發(fā)生就會(huì)造成應(yīng)用的停止響應(yīng)。下面看一個(gè)死鎖的例子

#coding=utf-8

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()

此時(shí)已經(jīng)進(jìn)入到了死鎖狀態(tài),可以使用ctrl-z退出

避免死鎖

?程序設(shè)計(jì)時(shí)要盡量避免(銀行家算法)

?添加超時(shí)時(shí)間等

附錄-銀行家算法

[背景知識(shí)]

一個(gè)銀行家如何將一定數(shù)目的資金安全地借給若干個(gè)客戶,使這些客戶既能借到錢完成要干的事,同時(shí)銀行家又能收回全部資金而不至于破產(chǎn),這就是銀行家問題。這個(gè)問題同操作系統(tǒng)中資源分配問題十分相似:銀行家就像一個(gè)操作系統(tǒng),客戶就像運(yùn)行的進(jìn)程,銀行家的資金就是系統(tǒng)的資源。

[問題的描述]

一個(gè)銀行家擁有一定數(shù)量的資金,有若干個(gè)客戶要貸款。每個(gè)客戶須在一開始就聲明他所需貸款的總額。若該客戶貸款總額不超過銀行家的資金總數(shù),銀行家可以接收客戶的要求。客戶貸款是以每次一個(gè)資金單位(如1萬RMB等)的方式進(jìn)行的,客戶在借滿所需的全部單位款額之前可能會(huì)等待,但銀行家須保證這種等待是有限的,可完成的。

例如:有三個(gè)客戶C1,C2,C3,向銀行家借款,該銀行家的資金總額為10個(gè)資金單位,其中C1客戶要借9各資金單位,C2客戶要借3個(gè)資金單位,C3客戶要借8個(gè)資金單位,總計(jì)20個(gè)資金單位。某一時(shí)刻的狀態(tài)如圖所示。


對(duì)于a圖的狀態(tài),按照安全序列的要求,我們選的第一個(gè)客戶應(yīng)滿足該客戶所需的貸款小于等于銀行家當(dāng)前所剩余的錢款,可以看出只有C2客戶能被滿足:C2客戶需1個(gè)資金單位,小銀行家手中的2個(gè)資金單位,于是銀行家把1個(gè)資金單位借給C2客戶,使之完成工作并歸還所借的3個(gè)資金單位的錢,進(jìn)入b圖。同理,銀行家把4個(gè)資金單位借給C3客戶,使其完成工作,在c圖中,只剩一個(gè)客戶C1,它需7個(gè)資金單位,這時(shí)銀行家有8個(gè)資金單位,所以C1也能順利借到錢并完成工作。最后(見圖d)銀行家收回全部10個(gè)資金單位,保證不賠本。那麼客戶序列{C1,C2,C3}就是個(gè)安全序列,按照這個(gè)序列貸款,銀行家才是安全的。否則的話,若在圖b狀態(tài)時(shí),銀行家把手中的4個(gè)資金單位借給了C1,則出現(xiàn)不安全狀態(tài):這時(shí)C1,C3均不能完成工作,而銀行家手中又沒有錢了,系統(tǒng)陷入僵持局面,銀行家也不能收回投資。

綜上所述,銀行家算法是從當(dāng)前狀態(tài)出發(fā),逐個(gè)按安全序列檢查各客戶誰能完成其工作,然后假定其完成工作且歸還全部貸款,再進(jìn)而檢查下一個(gè)能完成工作的客戶,......。如果所有客戶都能完成工作,則找到一個(gè)安全序列,銀行家才是安全的。

4.同步應(yīng)用

多個(gè)線程有序執(zhí)行


from threading import Thread,Lock

from time import sleep


class Task1(Thread):

? ? def run(self):

? ? ? ? while True:

? ? ? ? ? ? if lock1.acquire():

? ? ? ? ? ? ? ? print("------Task 1 -----")

? ? ? ? ? ? ? ? sleep(0.5)

? ? ? ? ? ? ? ? lock2.release()


class Task2(Thread):

? ? def run(self):

? ? ? ? while True:

? ? ? ? ? ? if lock2.acquire():

? ? ? ? ? ? ? ? print("------Task 2 -----")

? ? ? ? ? ? ? ? sleep(0.5)

? ? ? ? ? ? ? ? lock3.release()


class Task3(Thread):

? ? def run(self):

? ? ? ? while True:

? ? ? ? ? ? if lock3.acquire():

? ? ? ? ? ? ? ? print("------Task 3 -----")

? ? ? ? ? ? ? ? sleep(0.5)

? ? ? ? ? ? ? ? lock1.release()


#使用Lock創(chuàng)建出的鎖默認(rèn)沒有“鎖上”

lock1 = Lock()

#創(chuàng)建另外一把鎖,并且“鎖上”

lock2 = Lock()

lock2.acquire()

#創(chuàng)建另外一把鎖,并且“鎖上”

lock3 = Lock()

lock3.acquire()


t1 = Task1()

t2 = Task2()

t3 = Task3()


t1.start()

t2.start()

t3.start()

運(yùn)行結(jié)果:

------Task 1 -----

------Task 2 -----

------Task 3 -----

------Task 1 -----

------Task 2 -----

------Task 3 -----

------Task 1 -----

------Task 2 -----

------Task 3 -----

------Task 1 -----

------Task 2 -----

------Task 3 -----

------Task 1 -----

------Task 2 -----

------Task 3 -----

...省略...

總結(jié)

?可以使用互斥鎖完成多個(gè)任務(wù),有序的進(jìn)程工作,這就是線程的同步

5 生產(chǎn)者與消費(fèi)者模式

Python的Queue模塊中提供了同步的、線程安全的隊(duì)列類,包括FIFO(先入先出)隊(duì)列Queue,LIFO(后入先出)隊(duì)列LifoQueue,和優(yōu)先級(jí)隊(duì)列PriorityQueue。這些隊(duì)列都實(shí)現(xiàn)了鎖原語(可以理解為原子操作,即要么不做,要么就做完),能夠在多線程中直接使用??梢允褂藐?duì)列來實(shí)現(xiàn)線程間的同步。

用FIFO隊(duì)列實(shí)現(xiàn)上述生產(chǎn)者與消費(fèi)者問題的代碼如下:

#encoding=utf-8

import threading

import time


#python2中

#from queue import Queue


#python3中

from queue 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 + '消費(fèi)了 '+queue.get()

? ? ? ? ? ? ? ? ? ? print(msg)

? ? ? ? ? ? time.sleep(1)



if __name__ == '__main__':

? ? 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()

3. Queue的說明

1.對(duì)于Queue,在多線程通信之間扮演重要的角色

2.添加數(shù)據(jù)到隊(duì)列中,使用put()方法

3.從隊(duì)列中取數(shù)據(jù),使用get()方法

4.判斷隊(duì)列中是否還有數(shù)據(jù),使用qsize()方法

4. 生產(chǎn)者消費(fèi)者模式的說明

?為什么要使用生產(chǎn)者和消費(fèi)者模式

在線程世界里,生產(chǎn)者就是生產(chǎn)數(shù)據(jù)的線程,消費(fèi)者就是消費(fèi)數(shù)據(jù)的線程。在多線程開發(fā)當(dāng)中,如果生產(chǎn)者處理速度很快,而消費(fèi)者處理速度很慢,那么生產(chǎn)者就必須等待消費(fèi)者處理完,才能繼續(xù)生產(chǎn)數(shù)據(jù)。同樣的道理,如果消費(fèi)者的處理能力大于生產(chǎn)者,那么消費(fèi)者就必須等待生產(chǎn)者。為了解決這個(gè)問題于是引入了生產(chǎn)者和消費(fèi)者模式。

?什么是生產(chǎn)者消費(fèi)者模式

生產(chǎn)者消費(fèi)者模式是通過一個(gè)容器來解決生產(chǎn)者和消費(fèi)者的強(qiáng)耦合問題。生產(chǎn)者和消費(fèi)者彼此之間不直接通訊,而通過阻塞隊(duì)列來進(jìn)行通訊,所以生產(chǎn)者生產(chǎn)完數(shù)據(jù)之后不用等待消費(fèi)者處理,直接扔給阻塞隊(duì)列,消費(fèi)者不找生產(chǎn)者要數(shù)據(jù),而是直接從阻塞隊(duì)列里取,阻塞隊(duì)列就相當(dāng)于一個(gè)緩沖區(qū),平衡了生產(chǎn)者和消費(fèi)者的處理能力。

這個(gè)阻塞隊(duì)列就是用來給生產(chǎn)者和消費(fèi)者解耦的??v觀大多數(shù)設(shè)計(jì)模式,都會(huì)找一個(gè)第三者出來進(jìn)行解耦,

6.ThreadLocal

在多線程環(huán)境下,每個(gè)線程都有自己的數(shù)據(jù)。一個(gè)線程使用自己的局部變量比使用全局變量好,因?yàn)榫植孔兞恐挥芯€程自己能看見,不會(huì)影響其他線程,而全局變量的修改必須加鎖。

1. 使用函數(shù)傳參的方法

但是局部變量也有問題,就是在函數(shù)調(diào)用的時(shí)候,傳遞起來很麻煩:

def process_student(name):

? ? std = Student(name)

? ? # std是局部變量,但是每個(gè)函數(shù)都要用它,因此必須傳進(jìn)去:

? ? do_task_1(std)

? ? do_task_2(std)


def do_task_1(std):

? ? do_subtask_1(std)

? ? do_subtask_2(std)


def do_task_2(std):

? ? do_subtask_2(std)

? ? do_subtask_2(std)

每個(gè)函數(shù)一層一層調(diào)用都這么傳參數(shù)那還得了?用全局變量?也不行,因?yàn)槊總€(gè)線程處理不同的Student對(duì)象,不能共享。

2. 使用全局字典的方法

如果用一個(gè)全局dict存放所有的Student對(duì)象,然后以thread自身作為key獲得線程對(duì)應(yīng)的Student對(duì)象如何?

global_dict = {}


def std_thread(name):

? ? std = Student(name)

? ? # 把std放到全局變量global_dict中:

? ? global_dict[threading.current_thread()] = std

? ? do_task_1()

? ? do_task_2()


def do_task_1():

? ? # 不傳入std,而是根據(jù)當(dāng)前線程查找:

? ? std = global_dict[threading.current_thread()]

? ? ...


def do_task_2():

? ? # 任何函數(shù)都可以查找出當(dāng)前線程的std變量:

? ? std = global_dict[threading.current_thread()]

? ? ...

這種方式理論上是可行的,它最大的優(yōu)點(diǎn)是消除了std對(duì)象在每層函數(shù)中的傳遞問題,但是,每個(gè)函數(shù)獲取std的代碼有點(diǎn)low。

有沒有更簡(jiǎn)單的方式?

3. 使用ThreadLocal的方法

ThreadLocal應(yīng)運(yùn)而生,不用查找dict,ThreadLocal幫你自動(dòng)做這件事:

import threading


# 創(chuàng)建全局ThreadLocal對(duì)象:

local_school = threading.local()


def process_student():

? ? # 獲取當(dāng)前線程關(guān)聯(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()

t1.join()

t2.join()

執(zhí)行結(jié)果:

Hello, yongGe (in Thread-A)

Hello, 老王 (in Thread-B)

說明

全局變量local_school就是一個(gè)ThreadLocal對(duì)象,每個(gè)Thread對(duì)它都可以讀寫student屬性,但互不影響。你可以把local_school看成全局變量,但每個(gè)屬性如local_school.student都是線程的局部變量,可以任意讀寫而互不干擾,也不用管理鎖的問題,ThreadLocal內(nèi)部會(huì)處理。

可以理解為全局變量local_school是一個(gè)dict,不但可以用local_school.student,還可以綁定其他變量,如local_school.teacher等等。

ThreadLocal最常用的地方就是為每個(gè)線程綁定一個(gè)數(shù)據(jù)庫連接,HTTP請(qǐng)求,用戶身份信息等,這樣一個(gè)線程的所有調(diào)用到的處理函數(shù)都可以非常方便地訪問這些資源。

4. 小結(jié)

一個(gè)ThreadLocal變量雖然是全局變量,但每個(gè)線程都只能讀寫自己線程的獨(dú)立副本,互不干擾。ThreadLocal解決了參數(shù)在一個(gè)線程中各個(gè)函數(shù)之間互相傳遞的問題

7 異步

?同步調(diào)用就是你 喊 你朋友吃飯 ,你朋友在忙 ,你就一直在那等,等你朋友忙完了 ,你們一起去

?異步調(diào)用就是你 喊 你朋友吃飯 ,你朋友說知道了 ,待會(huì)忙完去找你 ,你就去做別的了。

from multiprocessing import Pool

import time

import os


def test():

? ? print("---進(jìn)程池中的進(jìn)程---pid=%d,ppid=%d--"%(os.getpid(),os.getppid()))

? ? for i in range(3):

? ? ? ? print("----%d---"%i)

? ? ? ? time.sleep(1)

? ? return "hahah"


def test2(args):

? ? print("---callback func--pid=%d"%os.getpid())

? ? print("---callback func--args=%s"%args)


pool = Pool(3)

pool.apply_async(func=test,callback=test2)


time.sleep(5)


print("----主進(jìn)程-pid=%d----"%os.getpid())

運(yùn)行結(jié)果:

---進(jìn)程池中的進(jìn)程---pid=9401,ppid=9400--

----0---

----1---

----2---

---callback func--pid=9400

---callback func--args=hahah

----主進(jìn)程-pid=9400----

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.進(jìn)程和線程 隊(duì)列:1、進(jìn)程之間的通信: q = multiprocessing.Queue()2、...
    一只寫程序的猿閱讀 1,233評(píng)論 0 17
  • 多任務(wù)可以由多進(jìn)程完成,也可以由一個(gè)進(jìn)程內(nèi)的多線程完成。我們前面提到了進(jìn)程是由若干線程組成的,一個(gè)進(jìn)程至少有一個(gè)線...
    壁花燒年閱讀 881評(píng)論 0 0
  • 基礎(chǔ)1.r''表示''內(nèi)部的字符串默認(rèn)不轉(zhuǎn)義2.'''...'''表示多行內(nèi)容3. 布爾值:True、False(...
    neo已經(jīng)被使用閱讀 1,879評(píng)論 0 5
  • 接著上節(jié) condition_varible ,本節(jié)主要介紹future的內(nèi)容,練習(xí)代碼地址。本文參考http:/...
    jorion閱讀 15,033評(píng)論 1 5
  • 我曾經(jīng)非常喜歡一則廣告,名叫 “America is Beautiful”,由 Wieden + Kennedy ...
    rivert閱讀 504評(píng)論 2 5

友情鏈接更多精彩內(nèi)容