Day39:threading模块、ThreadLocal
一、threading模塊
1、線程對象的創(chuàng)建
1.1 Thread類直接創(chuàng)建
import threading import timedef countNum(n): # 定義某個線程要運(yùn)行的函數(shù)print("running on number:%s" %n)time.sleep(3)if __name__ == '__main__':t1 = threading.Thread(target=countNum,args=(23,)) #生成一個線程實(shí)例t2 = threading.Thread(target=countNum,args=(34,))t1.start() #啟動線程t2.start()print("ending!")1.2 Thread類繼承式創(chuàng)建
#繼承Thread式創(chuàng)建import threading import timeclass MyThread(threading.Thread):def __init__(self,num):threading.Thread.__init__(self)self.num=numdef run(self):print("running on number:%s" %self.num)time.sleep(3)t1=MyThread(56) t2=MyThread(78)t1.start() t2.start() print("ending")2、Thread類的實(shí)例方法
2.1 join() 和 steDaemon()
# join():在子線程完成運(yùn)行之前,這個子線程的父線程將一直被阻塞。# setDaemon(True):'''將線程聲明為守護(hù)線程,必須在start() 方法調(diào)用之前設(shè)置,如果不設(shè)置為守護(hù)線程程序會被無限掛起。當(dāng)我們在程序運(yùn)行中,執(zhí)行一個主線程,如果主線程又創(chuàng)建一個子線程,主線程和子線程 就分兵兩路,分別運(yùn)行,那么當(dāng)主線程完成想退出時,會檢驗(yàn)子線程是否完成。如果子線程未完成,則主線程會等待子線程完成后再退出。但是有時候我們需要的是只要主線程完成了,不管子線程是否完成,都要和主線程一起退出,這時就可以 用setDaemon方法啦'''import threading from time import ctime,sleep import timedef Music(name):print ("Begin listening to {name}. {time}".format(name=name,time=ctime()))sleep(3)print("end listening {time}".format(time=ctime()))def Blog(title):print ("Begin recording the {title}. {time}".format(title=title,time=ctime()))sleep(5)print('end recording {time}'.format(time=ctime()))threads = []t1 = threading.Thread(target=Music,args=('FILL ME',)) t2 = threading.Thread(target=Blog,args=('',))threads.append(t1) threads.append(t2) if __name__ == '__main__':for t in threads:t.start()print ("all over %s" %ctime()) ''' 運(yùn)行結(jié)果 Begin listening to FILL ME. Tue Jul 18 16:15:06 2017 Begin recording the . Tue Jul 18 16:15:06 2017 all over Tue Jul 18 16:15:06 2017 end listening Tue Jul 18 16:15:09 2017 end recording Tue Jul 18 16:15:11 2017前三行瞬間執(zhí)行完畢,后兩行等待3秒和5秒執(zhí)行''' if __name__ == '__main__':for t in threads:t.setDaemon(True) #注意:一定在start之前設(shè)置t.start()print("all over %s" % ctime()) ''' 運(yùn)行結(jié)果: Begin listening to FILL ME. Tue Jul 18 16:31:23 2017 Begin recording the . Tue Jul 18 16:31:23 2017 all over Tue Jul 18 16:31:23 201每個線程都是守護(hù)線程,跟隨主線程一塊掛掉 ''' if __name__ == '__main__':# t2.setDaemon(True)for t in threads:# t.setDaemon(True) #注意:一定在start之前設(shè)置t.start()t1.join()print("all over %s" % ctime()) ''' 運(yùn)行結(jié)果: Begin listening to FILL ME. Tue Jul 18 16:34:41 2017 Begin recording the . Tue Jul 18 16:34:41 2017 end listening Tue Jul 18 16:34:44 2017 all over Tue Jul 18 16:34:44 2017 end recording Tue Jul 18 16:34:46 2017前兩行瞬間執(zhí)行完成后等待,第二三行一起執(zhí)行,最后一行最后出現(xiàn)。 在t1.join()處阻塞,t1線程運(yùn)行完畢,主線程才繼續(xù)執(zhí)行。 ''' daemon A boolean value indicating whether this thread is a daemon thread (True) or not (False). This must be set before start() is called, otherwise RuntimeError is raised. Its initial value is inherited from the creating thread; the main thread is not a daemon thread and therefore all threads created in the main thread default to daemon = False.The entire Python program exits when no alive non-daemon threads are left.當(dāng)daemon被設(shè)置為True時,如果主線程退出,那么子線程也將跟著退出,反之,子線程將繼續(xù)運(yùn)行,直到正常退出。 daemon2.2 其他方法
Thread實(shí)例對象的方法# isAlive(): 返回線程是否活動的。# getName(): 返回線程名。# setName(): 設(shè)置線程名。threading模塊提供的一些方法:# threading.currentThread(): 返回當(dāng)前的線程變量。# threading.enumerate(): 返回一個包含正在運(yùn)行的線程的list。正在運(yùn)行指線程啟動后、結(jié)束前,不包括啟動前和終止后的線程。# threading.activeCount(): 返回正在運(yùn)行的線程數(shù)量,與len(threading.enumerate())有相同的結(jié)果。3、GIL(全局解釋器鎖)
''' 定義: In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)翻譯: 在CPython中,全局解釋器鎖是一個互斥鎖,它可以防止多個本機(jī)線程同時執(zhí)行Python的編譯器。這個鎖是必需的,主要是因?yàn)镃Python的內(nèi)存管理不是線程安全的。(然而,由于GIL的存在,其他的特性已經(jīng)發(fā)展到依賴于它的保證。) '''Python中的線程是操作系統(tǒng)的原生線程,Python虛擬機(jī)使用一個全局解釋器鎖(Global Interpreter Lock)來互斥線程對Python虛擬機(jī)的使用。為了支持多線程機(jī)制,一個基本的要求就是需要實(shí)現(xiàn)不同線程對共享資源訪問的互斥,所以引入了GIL。
GIL:在一個線程擁有了解釋器的訪問權(quán)之后,其他的所有線程都必須等待它釋放解釋器的訪問權(quán),即使這些線程的下一條指令并不會互相影響。
在調(diào)用任何Python C API之前,要先獲得GIL
GIL缺點(diǎn):多處理器退化為單處理器;優(yōu)點(diǎn):避免大量的加鎖解鎖操作。
3.1 GIL的早期設(shè)計(jì)
Python支持多線程,而解決多線程之間數(shù)據(jù)完整性和狀態(tài)同步的最簡單方法自然就是加鎖。 于是有了GIL這把超級大鎖,而當(dāng)越來越多的代碼庫開發(fā)者接受了這種設(shè)定后,他們開始大量依賴這種特性(即默認(rèn)python內(nèi)部對象是thread-safe的,無需在實(shí)現(xiàn)時考慮額外的內(nèi)存鎖和同步操作)。慢慢的這種實(shí)現(xiàn)方式被發(fā)現(xiàn)是蛋疼且低效的。但當(dāng)大家試圖去拆分和去除GIL的時候,發(fā)現(xiàn)大量庫代碼開發(fā)者已經(jīng)重度依賴GIL而非常難以去除了。有多難?做個類比,像MySQL這樣的“小項(xiàng)目”為了把Buffer Pool Mutex這把大鎖拆分成各個小鎖也花了從5.5到5.6再到5.7多個大版為期近5年的時間,并且仍在繼續(xù)。MySQL這個背后有公司支持且有固定開發(fā)團(tuán)隊(duì)的產(chǎn)品走的如此艱難,那又更何況Python這樣核心開發(fā)和代碼貢獻(xiàn)者高度社區(qū)化的團(tuán)隊(duì)呢?
3.2 GIL的影響
無論你啟多少個線程,你有多少個cpu, Python在執(zhí)行一個進(jìn)程的時候會淡定的在同一時刻只允許一個線程運(yùn)行。
所以,python是無法利用多核CPU實(shí)現(xiàn)多線程的。
這樣,python對于計(jì)算密集型的任務(wù)開多線程的效率甚至不如串行(沒有大量切換),但是,對于IO密集型的任務(wù)效率還是有顯著提升的。
3.3 解決方案
用multiprocessing替代Thread multiprocessing庫的出現(xiàn)很大程度上是為了彌補(bǔ)thread庫因?yàn)镚IL而低效的缺陷。它完整的復(fù)制了一套thread所提供的接口方便遷移。唯一的不同就是它使用了多進(jìn)程而不是多線程。每個進(jìn)程有自己的獨(dú)立的GIL,因此也不會出現(xiàn)進(jìn)程之間的GIL爭搶。
#coding:utf8 from multiprocessing import Process import timedef counter():i = 0for _ in range(40000000):i = i + 1return Truedef main():l=[]start_time = time.time()for _ in range(2):t=Process(target=counter)t.start()l.append(t)#t.join()for t in l:t.join()end_time = time.time()print("Total time: {}".format(end_time - start_time))if __name__ == '__main__':main()'''py2.7:串行:6.1565990448 s并行:3.1639978885 spy3.5:串行:6.556925058364868 s并發(fā):3.5378448963165283 s''' 使用multiprocessing當(dāng)然multiprocessing也不是萬能良藥。它的引入會增加程序?qū)崿F(xiàn)時線程間數(shù)據(jù)通訊和同步的困難。就拿計(jì)數(shù)器來舉例子,如果我們要多個線程累加同一個變量,對于thread來說,申明一個global變量,用thread.Lock的context包裹住三行就搞定了。而multiprocessing由于進(jìn)程之間無法看到對方的數(shù)據(jù),只能通過在主線程申明一個Queue,put再get或者用share memory的方法。這個額外的實(shí)現(xiàn)成本使得本來就非常痛苦的多線程程序編碼,變得更加痛苦了。
總結(jié):因?yàn)镚IL的存在,只有IO Bound場景下得多線程會得到較好的性能 - 如果對并行計(jì)算性能較高的程序可以考慮把核心部分換成C模塊,或者索性用其他語言實(shí)現(xiàn) - GIL在較長一段時間內(nèi)將會繼續(xù)存在,但是會不斷對其進(jìn)行改進(jìn)。
所以對于GIL,既然不能反抗,那就學(xué)會去享受它吧!
4、同步鎖(Lock)
import time import threading def addNum():global num #在每個線程中都獲取這個全局變量#num-=1temp=numtime.sleep(0.1)num =temp-1 # 對此公共變量進(jìn)行-1操作num = 100 #設(shè)定一個共享變量thread_list = []s=time.time() for i in range(100):t = threading.Thread(target=addNum)t.start()thread_list.append(t)for t in thread_list: #等待所有線程執(zhí)行完畢t.join()print('Result: ', num ,'cost time: ' ,time.time()-s) #運(yùn)行結(jié)果 #Result: 99 cost time: 0.11100625991821289分析結(jié)果:
綠色框代表進(jìn)程,藍(lán)色框代表子線程,一共開了100個子線程(不包括主線程)。
開啟一個子線程并運(yùn)行后,temp被賦值為100,然后遇到阻塞,其他子線程搶到CPU進(jìn)行執(zhí)行,此時num沒有執(zhí)行-1操作,所以線程2 的temp也被賦值為100,然后遇到阻塞。其他線程搶CPU執(zhí)行。0.1秒的時間足夠100個線程都將temp賦值為100,然后再執(zhí)行-1操作。所以num = 100 - 1 ,到結(jié)束num=99。
解決方法:使用同步鎖對數(shù)據(jù)進(jìn)行保護(hù)
鎖通常被用來實(shí)現(xiàn)對共享資源的同步訪問。為每一個共享資源創(chuàng)建一個Lock對象,當(dāng)你需要訪問該資源時,調(diào)用acquire方法來獲取鎖對象(如果其它線程已經(jīng)獲得了該鎖,則當(dāng)前線程需等待其被釋放),待資源訪問完后,再調(diào)用release方法釋放鎖:
import threadingR=threading.Lock()R.acquire() ''' 對公共數(shù)據(jù)的操作 ''' R.release() import time import threading def addNum():global num #在每個線程中都獲取這個全局變量#num-=1R.acquire() #保護(hù)數(shù)據(jù),串行執(zhí)行temp=numtime.sleep(0.1)num =temp-1 # 對此公共變量進(jìn)行-1操作R.release()num = 100 #設(shè)定一個共享變量 thread_list = []R=threading.Lock() #實(shí)例化鎖對象s=time.time() for i in range(100):t = threading.Thread(target=addNum)t.start()thread_list.append(t)for t in thread_list: #等待所有線程執(zhí)行完畢t.join()print('Result: ', num ,'cost time: ' ,time.time()-s) #運(yùn)行結(jié)果 #Result: 0 cost time: 10.023573398590088 ''' 1、為什么有了GIL,還需要線程同步?多線程環(huán)境下必須存在資源的競爭,那么如何才能保證同一時刻只有一個線程對共享資源進(jìn)行存取?加鎖, 對, 加鎖可以保證存取操作的唯一性, 從而保證同一時刻只有一個線程對共享數(shù)據(jù)存取.通常加鎖也有2種不同的粒度的鎖:coarse-grained(粗粒度): python解釋器層面維護(hù)著一個全局的鎖機(jī)制,用來保證線程安全。內(nèi)核級通過GIL實(shí)現(xiàn)的互斥保護(hù)了內(nèi)核的共享資源。fine-grained(細(xì)粒度): 那么程序員需要自行地加,解鎖來保證線程安全,用戶級通過自行加鎖保護(hù)的用戶程序的共享資源。2、GIL為什么限定在一個進(jìn)程上?你寫一個py程序,運(yùn)行起來本身就是一個進(jìn)程,這個進(jìn)程是由解釋器來翻譯的,所以GIL限定在當(dāng)前進(jìn)程;如果又創(chuàng)建了一個子進(jìn)程,那么兩個進(jìn)程是完全獨(dú)立的,這個字進(jìn)程也是有python解釋器來運(yùn)行的,所以這個子進(jìn)程上也是受GIL影響的 ''' 擴(kuò)展思考5、死鎖與遞歸鎖
所謂死鎖: 是指兩個或兩個以上的進(jìn)程或線程在執(zhí)行過程中,因爭奪資源而造成的一種互相等待的現(xiàn)象,若無外力作用,它們都將無法推進(jìn)下去。此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠(yuǎn)在互相等待的進(jìn)程稱為死鎖進(jìn)程。
import threading,timeLockA=threading.Lock() #定義鎖A LockB=threading.Lock() #定義鎖Bclass MyThread(threading.Thread):def __init__(self):threading.Thread.__init__(self)def run(self):self.foo()self.bar()def foo(self):LockA.acquire() #加A鎖print('I am foo %s get LOCKA-------%s'%(self.name,time.ctime()))LockB.acquire() #加B鎖print('I am foo %s get LOCKB-------%s' % (self.name, time.ctime()))LockB.release() #解B鎖LockA.release() #解A鎖,A鎖被線程2拿到def bar(self):LockB.acquire() #加B鎖print('I am bar %s get LOCKB-------%s' % (self.name, time.ctime()))LockA.acquire() #需要加A鎖,但A鎖被線程2占用,線程2需要加B鎖,相互拿不到鎖,造成死鎖print('I am bar %s get LOCKA-------%s' % (self.name, time.ctime()))LockA.release()LockB.release()for i in range(10):t=MyThread()t.start() ''' 運(yùn)行結(jié)果: I am foo Thread-1 get LOCKA-------Tue Jul 18 18:22:26 2017 I am foo Thread-1 get LOCKB-------Tue Jul 18 18:22:26 2017 I am bar Thread-1 get LOCKB-------Tue Jul 18 18:22:26 2017 I am foo Thread-2 get LOCKA-------Tue Jul 18 18:22:26 2017 '''?解決方案:使用遞歸鎖
import threading,timeRlock=threading.RLock() #定義遞歸鎖class MyThread(threading.Thread):def __init__(self):threading.Thread.__init__(self)def run(self):self.foo()self.bar()def foo(self):Rlock.acquire() #遞歸鎖counter+1print('I am foo %s get LOCKA-------%s'%(self.name,time.ctime()))Rlock.acquire() #遞歸鎖counter+1print('I am foo %s get LOCKB-------%s' % (self.name, time.ctime()))Rlock.release() #遞歸鎖counter-1Rlock.release() #遞歸鎖counter-1 遞歸鎖counter為零,可被其他線程獲取def bar(self):Rlock.acquire() #遞歸鎖counter+1print('I am bar %s get LOCKB-------%s' % (self.name, time.ctime()))Rlock.acquire() #遞歸鎖counter+1print('I am bar %s get LOCKA-------%s' % (self.name, time.ctime()))Rlock.release() #遞歸鎖counter-1Rlock.release() #遞歸鎖counter-1for i in range(10):t=MyThread()t.start()?
''' 運(yùn)行結(jié)果: I am foo Thread-1 get LOCKA-------Tue Jul 18 18:30:01 2017 I am foo Thread-1 get LOCKB-------Tue Jul 18 18:30:01 2017 I am foo Thread-2 get LOCKA-------Tue Jul 18 18:30:01 2017 I am foo Thread-2 get LOCKB-------Tue Jul 18 18:30:01 2017 I am bar Thread-1 get LOCKB-------Tue Jul 18 18:30:01 2017 I am bar Thread-1 get LOCKA-------Tue Jul 18 18:30:01 2017 I am foo Thread-3 get LOCKA-------Tue Jul 18 18:30:01 2017 I am foo Thread-3 get LOCKB-------Tue Jul 18 18:30:01 2017 I am bar Thread-2 get LOCKB-------Tue Jul 18 18:30:01 2017 I am bar Thread-2 get LOCKA-------Tue Jul 18 18:30:01 2017 I am foo Thread-4 get LOCKA-------Tue Jul 18 18:30:01 2017 I am foo Thread-4 get LOCKB-------Tue Jul 18 18:30:01 2017 I am bar Thread-4 get LOCKB-------Tue Jul 18 18:30:01 2017 I am bar Thread-4 get LOCKA-------Tue Jul 18 18:30:01 2017 I am bar Thread-3 get LOCKB-------Tue Jul 18 18:30:01 2017 I am bar Thread-3 get LOCKA-------Tue Jul 18 18:30:01 2017 I am foo Thread-6 get LOCKA-------Tue Jul 18 18:30:01 2017 I am foo Thread-6 get LOCKB-------Tue Jul 18 18:30:01 2017 I am bar Thread-6 get LOCKB-------Tue Jul 18 18:30:01 2017 I am bar Thread-6 get LOCKA-------Tue Jul 18 18:30:01 2017 I am foo Thread-5 get LOCKA-------Tue Jul 18 18:30:01 2017 I am foo Thread-5 get LOCKB-------Tue Jul 18 18:30:01 2017 I am bar Thread-5 get LOCKB-------Tue Jul 18 18:30:01 2017 I am bar Thread-5 get LOCKA-------Tue Jul 18 18:30:01 2017 I am foo Thread-9 get LOCKA-------Tue Jul 18 18:30:01 2017 I am foo Thread-9 get LOCKB-------Tue Jul 18 18:30:01 2017 I am bar Thread-9 get LOCKB-------Tue Jul 18 18:30:01 2017 I am bar Thread-9 get LOCKA-------Tue Jul 18 18:30:01 2017 I am foo Thread-7 get LOCKA-------Tue Jul 18 18:30:01 2017 I am foo Thread-7 get LOCKB-------Tue Jul 18 18:30:01 2017 I am bar Thread-7 get LOCKB-------Tue Jul 18 18:30:01 2017 I am bar Thread-7 get LOCKA-------Tue Jul 18 18:30:01 2017 I am foo Thread-10 get LOCKA-------Tue Jul 18 18:30:01 2017 I am foo Thread-10 get LOCKB-------Tue Jul 18 18:30:01 2017 I am bar Thread-10 get LOCKB-------Tue Jul 18 18:30:01 2017 I am bar Thread-10 get LOCKA-------Tue Jul 18 18:30:01 2017 I am foo Thread-8 get LOCKA-------Tue Jul 18 18:30:01 2017 I am foo Thread-8 get LOCKB-------Tue Jul 18 18:30:01 2017 I am bar Thread-8 get LOCKB-------Tue Jul 18 18:30:01 2017 I am bar Thread-8 get LOCKA-------Tue Jul 18 18:30:01 2017 ''' 運(yùn)行結(jié)果?
在Python中為了支持在同一線程中多次請求同一資源,python提供了可重入鎖RLock。這個RLock內(nèi)部維護(hù)著一個Lock和一個counter變量,counter記錄了acquire的次數(shù),從而使得資源可以被多次require。直到一個線程所有的acquire都被release,其他的線程才能獲得資源。
6、Semaphore(信號量)
Semaphore管理一個內(nèi)置的計(jì)數(shù)器,
每當(dāng)調(diào)用acquire()時內(nèi)置計(jì)數(shù)器-1;
調(diào)用release() 時內(nèi)置計(jì)數(shù)器+1;
計(jì)數(shù)器不能小于0;當(dāng)計(jì)數(shù)器為0時,acquire()將阻塞線程直到其他線程調(diào)用release()。
實(shí)例:(同時只有5個線程可以獲得semaphore,即可以限制最大連接數(shù)為5):
import threading import timesemaphore = threading.Semaphore(5)def func():if semaphore.acquire():print (threading.currentThread().getName() + ' get semaphore')time.sleep(2)semaphore.release()for i in range(20):t1 = threading.Thread(target=func)t1.start() ''' 運(yùn)行結(jié)果: Thread-1 get semaphore Thread-2 get semaphore Thread-3 get semaphore Thread-4 get semaphore Thread-5 get semaphore Thread-6 get semaphore Thread-8 get semaphore Thread-7 get semaphore Thread-10 get semaphore Thread-9 get semaphore Thread-12 get semaphore Thread-11 get semaphore Thread-13 get semaphore Thread-14 get semaphore Thread-15 get semaphore Thread-16 get semaphore Thread-17 get semaphore Thread-18 get semaphore Thread-19 get semaphore Thread-20 get semaphore每5個一起打印 '''應(yīng)用:連接池
二、ThreadLocal
在多線程環(huán)境下,每個線程都有自己的數(shù)據(jù)。一個線程使用自己的局部變量比使用全局變量好,因?yàn)榫植孔兞恐挥芯€程自己能看見,不會影響其他線程,而全局變量的修改必須加鎖。
但是局部變量也有問題,就是在函數(shù)調(diào)用的時候,傳遞起來很麻煩:
def process_student(name):std = Student(name)# std是局部變量,但是每個函數(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)每個函數(shù)一層一層調(diào)用都這么傳參數(shù)那還得了?用全局變量?也不行,因?yàn)槊總€線程處理不同的Student對象,不能共享。
如果用一個全局dict存放所有的Student對象,然后以thread自身作為key獲得線程對應(yīng)的Student對象如何?
global_dict = {}def std_thread(name):std = Student(name)# 把std放到全局變量global_dict中:global_dict[threading.current_thread()] = stddo_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對象在每層函數(shù)中的傳遞問題,但是,每個函數(shù)獲取std的代碼有點(diǎn)丑。
有沒有更簡單的方式?
ThreadLocal應(yīng)運(yùn)而生,不用查找dict,ThreadLocal幫你自動做這件事:
import threading# 創(chuàng)建全局ThreadLocal對象: local_school = threading.local()def process_student():# 獲取當(dāng)前線程關(guān)聯(lián)的student:std = local_school.studentprint('Hello, %s (in %s)' % (std, threading.current_thread().name))def process_thread(name):# 綁定ThreadLocal的student:local_school.student = nameprocess_student()t1 = threading.Thread(target= process_thread, args=('Alice',), name='Thread-A') t2 = threading.Thread(target= process_thread, args=('Bob',), name='Thread-B') t1.start() t2.start() t1.join() t2.join() ''' 執(zhí)行結(jié)果:Hello, Alice (in Thread-A) Hello, Bob (in Thread-B) '''全局變量local_school就是一個ThreadLocal對象,每個Thread對它都可以讀寫student屬性,但互不影響。你可以把local_school看成全局變量,但每個屬性如local_school.student都是線程的局部變量,可以任意讀寫而互不干擾,也不用管理鎖的問題,ThreadLocal內(nèi)部會處理。
可以理解為全局變量local_school是一個dict,不但可以用local_school.student,還可以綁定其他變量,如local_school.teacher等等。
ThreadLocal最常用的地方就是為每個線程綁定一個數(shù)據(jù)庫連接,HTTP請求,用戶身份信息等,這樣一個線程的所有調(diào)用到的處理函數(shù)都可以非常方便地訪問這些資源。
小結(jié)
一個ThreadLocal變量雖然是全局變量,但每個線程都只能讀寫自己線程的獨(dú)立副本,互不干擾。ThreadLocal解決了參數(shù)在一個線程中各個函數(shù)之間互相傳遞的問題。
參考博客(海峰):
http://www.cnblogs.com/linhaifeng/articles/6817679.html
?
轉(zhuǎn)載于:https://www.cnblogs.com/zzn91/p/7300119.html
總結(jié)
以上是生活随笔為你收集整理的Day39:threading模块、ThreadLocal的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JAVA 疯狂讲义 学习笔记
- 下一篇: halcon对光源打光不均匀进行平场矫正