小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

Python線程5分鐘完全解讀

 昵稱64554919 2019-06-06

線程,有時(shí)被稱為輕量進(jìn)程,是程序執(zhí)行流的最小單元。一個(gè)標(biāo)準(zhǔn)的線程由線程ID,當(dāng)前指令指針(PC),寄存器集合和堆棧組成。線程是進(jìn)程中的一個(gè)實(shí)體,是被系統(tǒng)獨(dú)立調(diào)度和分派的基本單位,線程不擁有私有的系統(tǒng)資源,但它可與同屬一個(gè)進(jìn)程的其它線程共享進(jìn)程所擁有的全部資源。一個(gè)線程可以創(chuàng)建和撤消另一個(gè)線程,同一進(jìn)程中的多個(gè)線程之間可以并發(fā)執(zhí)行。

線程是程序中一個(gè)單一的順序控制流程。進(jìn)程內(nèi)有一個(gè)相對(duì)獨(dú)立的、可調(diào)度的執(zhí)行單元,是系統(tǒng)獨(dú)立調(diào)度和分派CPU的基本單位指令運(yùn)行時(shí)的程序的調(diào)度單位。在單個(gè)程序中同時(shí)運(yùn)行多個(gè)線程完成不同的工作,稱為多線程。Python多線程用于I/O操作密集型的任務(wù),如SocketServer網(wǎng)絡(luò)并發(fā),網(wǎng)絡(luò)爬蟲。

現(xiàn)代處理器都是多核的,幾核處理器只能同時(shí)處理幾個(gè)線程,多線程執(zhí)行程序看起來(lái)是同時(shí)進(jìn)行,實(shí)際上是CPU在多個(gè)線程之間快速切換執(zhí)行,這中間就涉及到上下問(wèn)切換,所謂的上下文切換就是指一個(gè)線程Thread被分配的時(shí)間片用完了之后,線程的信息被保存起來(lái),CPU執(zhí)行另外的線程,再到CPU讀取線程Thread的信息并繼續(xù)執(zhí)行Thread的過(guò)程。

線程模塊

Python的標(biāo)準(zhǔn)庫(kù)提供了兩個(gè)模塊:_thread和threading。_thread 提供了低級(jí)別的、原始的線程以及一個(gè)簡(jiǎn)單的互斥鎖,它相比于 threading 模塊的功能還是比較有限的。Threading模塊是_thread模塊的替代,在實(shí)際的開發(fā)中,絕大多數(shù)情況下還是使用高級(jí)模塊threading,因此本書著重介紹threading高級(jí)模塊的使用。

Python創(chuàng)建Thread對(duì)象語(yǔ)法如下:

  1. import threading

  2. threading.Thread(target=None, name=None, args=())

主要參數(shù)說(shuō)明:

  • target 是函數(shù)名字,需要調(diào)用的函數(shù)。

  • name 設(shè)置線程名字。

  • args 函數(shù)需要的參數(shù),以元祖( tuple)的形式傳入

  • Thread對(duì)象主要方法說(shuō)明:

  • run(): 用以表示線程活動(dòng)的方法。

  • start():啟動(dòng)線程活動(dòng)。

  • join(): 等待至線程中止。

  • isAlive(): 返回線程是否活動(dòng)的。

  • getName(): 返回線程名。

  • setName(): 設(shè)置線程名。

Python中實(shí)現(xiàn)多線程有兩種方式:函數(shù)式創(chuàng)建線程和創(chuàng)建線程類。

第一種創(chuàng)建線程方式:

創(chuàng)建線程的時(shí)候,只需要傳入一個(gè)執(zhí)行函數(shù)和函數(shù)的參數(shù)即可完成threading.Thread實(shí)例的創(chuàng)建。下面的例子使用Thread類來(lái)產(chǎn)生2個(gè)子線程,然后啟動(dòng)2個(gè)子線程并等待其結(jié)束,

  1. import threading

  2. import time,random,math

  3. # idx 循環(huán)次數(shù)

  4. def printNum(idx):

  5. for num in range(idx ):

  6. #打印當(dāng)前運(yùn)行的線程名字

  7. print('{0}\tnum={1}'.format(threading.current_thread().getName(), num) )

  8. delay = math.ceil(random.random() * 2)

  9. time.sleep(delay)

  10. if __name__ == '__main__':

  11. th1 = threading.Thread(target=printNum, args=(2,),name='thread1' )

  12. th2 = threading.Thread(target=printNum, args=(3,),name='thread2' )

  13. #啟動(dòng)2個(gè)線程

  14. th1.start()

  15. th2.start()

  16. #等待至線程中止

  17. th1.join()

  18. th2.join()

  19. print('{0} 線程結(jié)束'.format(threading.current_thread().getName()))

運(yùn)行腳本得到以下結(jié)果。

  1. thread1 num=0

  2. thread2 num=0

  3. thread1 num=1

  4. thread2 num=1

  5. thread2 num=2

  6. MainThread 線程結(jié)束

運(yùn)行腳本默認(rèn)會(huì)啟動(dòng)一個(gè)線程,把該線程稱為主線程,主線程有可以啟動(dòng)新的線程,Python的threading模塊有個(gè)current_thread()函數(shù),它將返回當(dāng)前線程的示例。從當(dāng)前線程的示例可以獲得前運(yùn)行線程名字,核心代碼如下。

  1. threading.current_thread().getName()

啟動(dòng)一個(gè)線程就是把一個(gè)函數(shù)和參數(shù)傳入并創(chuàng)建Thread實(shí)例,然后調(diào)用start()開始執(zhí)行

  1. th1 = threading.Thread(target=printNum, args=(2,),name='thread1' )

  2. th1.start()

從返回結(jié)果可以看出主線程示例的名字叫MainThread,子線程的名字在創(chuàng)建時(shí)指定,本例創(chuàng)建了2個(gè)子線程,名字叫thread1和thread2。如果沒(méi)有給線程起名字,Python就自動(dòng)給線程命名為Thread-1,Thread-2…等等。在本例中定義了線程函數(shù)printNum(),打印idx次記錄后退出,每次打印使用time.sleep()讓程序休眠一段時(shí)間。

第二種創(chuàng)建線程方式:創(chuàng)建線程類

直接創(chuàng)建threading.Thread的子類來(lái)創(chuàng)建一個(gè)線程對(duì)象,實(shí)現(xiàn)多線程。通過(guò)繼承Thread類,并重寫Thread類的run()方法,在run()方法中定義具體要執(zhí)行的任務(wù)。在Thread類中,提供了一個(gè)start()方法用于啟動(dòng)新進(jìn)程,線程啟動(dòng)后會(huì)自動(dòng)調(diào)用run()方法。

  1. import threading

  2. import time,random,math

  3. class MutliThread(threading.Thread):

  4. def __init__(self, threadName,num):

  5. threading.Thread.__init__(self)

  6. self.name = threadName

  7. self.num = num

  8. def run(self):

  9. for i in range(self.num):

  10. print('{0} i={1}'.format(threading.current_thread().getName(), i))

  11. delay = math.ceil(random.random() * 2)

  12. time.sleep(delay)

  13. if __name__ == '__main__':

  14. thr1 = MutliThread('thread1',3)

  15. thr2 = MutliThread('thread2',2)

  16. # 啟動(dòng)線程

  17. thr1.start()

  18. thr2.start()

  19. # 等待至線程中止

  20. thr1.join()

  21. thr2.join()

  22. print('{0} 線程結(jié)束'.format(threading.current_thread().getName()))

運(yùn)行腳本得到以下結(jié)果。

  1. thread1 i=0

  2. thread2 i=0

  3. thread1 i=1

  4. thread2 i=1

  5. thread1 i=2

  6. MainThread 線程結(jié)束

從返回結(jié)果可以看出,通過(guò)創(chuàng)建Thread類來(lái)產(chǎn)生2個(gè)線程對(duì)象thr1和thr2,重寫Thread類的run()函數(shù),把業(yè)務(wù)邏輯放入其中,通過(guò)調(diào)用線程對(duì)象的start()方法啟動(dòng)線程。通過(guò)調(diào)用線程對(duì)象的join()函數(shù),等待該線程完成,在繼續(xù)下面的操作。

在本例中,主線程MainThread等待子線程thread1和thread2線程運(yùn)行結(jié)束后才輸出” MainThread 線程結(jié)束”。如果子線程thread1和thread2不調(diào)用join()函數(shù),那么主線程MainThread和2個(gè)子線程是并行執(zhí)行任務(wù)的,2個(gè)子線程加上join()函數(shù)后,程序就變成順序執(zhí)行了。所以子線程用到j(luò)oin()的時(shí)候,通常都是主線程等到其他多個(gè)子線程執(zhí)行完畢后再繼續(xù)執(zhí)行,其他的多個(gè)子線程并不需要互相等待。

守護(hù)線程

在線程模塊中,使用子線程對(duì)象用到j(luò)oin()函數(shù),主線程需要依賴子線程執(zhí)行完畢后才繼續(xù)執(zhí)行代碼。如果子線程不使用join()函數(shù),主線程和子線程是并行運(yùn)行的,沒(méi)有依賴關(guān)系,主線程執(zhí)行了,子線程也在執(zhí)行。

在多線程開發(fā)中,如果子線程設(shè)定為了守護(hù)線程,守護(hù)線程會(huì)等待主線程運(yùn)行完畢后被銷毀。一個(gè)主線程可以設(shè)置多個(gè)守護(hù)線程,守護(hù)線程運(yùn)行的前提是,主線程必須存在,如果主線程不存在了,守護(hù)線程會(huì)被銷毀。

在本例中創(chuàng)建1個(gè)主線程3個(gè)子線程,讓主線程和子線程并行執(zhí)行。內(nèi)容如下。

  1. import threading, time

  2. def run(taskName):

  3. print('任務(wù):', taskName)

  4. time.sleep(2)

  5. print('{0} 任務(wù)執(zhí)行完畢'.format(taskName)) # 查看每個(gè)子線程

  6. if __name__ == '__main__':

  7. start_time = time.time()

  8. for i in range(3):

  9. thr = threading.Thread(target=run, args=('task-{0}'.format(i),))

  10. # 把子線程設(shè)置為守護(hù)線程

  11. thr.setDaemon(True)

  12. thr.start()

  13. # 查看主線程和當(dāng)前活動(dòng)的所有線程數(shù)

  14. print('{0}線程結(jié)束,當(dāng)線程數(shù)量={1}'.format( threading.current_thread().getName(), threading.active_count()))

  15. print('消耗時(shí)間:', time.time() - start_time)

  16. 。

運(yùn)行腳本得到以下結(jié)果:

  1. 任務(wù): task-0

  2. 任務(wù): task-1

  3. 任務(wù): task-2

  4. MainThread線程結(jié)束,當(dāng)線程數(shù)量=4

  5. 消耗時(shí)間: 0.0009751319885253906

  6. task-2 任務(wù)執(zhí)行完畢

  7. task-0 任務(wù)執(zhí)行完畢

  8. task-1 任務(wù)執(zhí)行完畢

從返回結(jié)果可以看出,當(dāng)前的線程個(gè)數(shù)是4,線程個(gè)數(shù)=主線程數(shù) 子線程數(shù),在本例中有1個(gè)主線程和3個(gè)子線程。主線程執(zhí)行完畢后,等待子線程執(zhí)行完畢,程序才會(huì)退出。

在本例的基礎(chǔ)上,把所有的子線程都設(shè)置為守護(hù)線程。子線程變成守護(hù)線程后,只要主線程執(zhí)行完畢,程序不管子線程有沒(méi)有執(zhí)行完畢,程序都會(huì)退出。使用線程對(duì)象的setDaemon(True)函數(shù)來(lái)設(shè)置守護(hù)線程。

  1. import threading, time

  2. def run(taskName):

  3. print('任務(wù):', taskName)

  4. time.sleep(2)

  5. print('{0} 任務(wù)執(zhí)行完畢'.format(taskName))

  6. if __name__ == '__main__':

  7. start_time = time.time()

  8. for i in range(3):

  9. thr = threading.Thread(target=run, args=('task-{0}'.format(i),))

  10. # 把子線程設(shè)置為守護(hù)線程,在啟動(dòng)線程前設(shè)置

  11. thr.setDaemon(True)

  12. thr.start()

  13. # 查看主線程和當(dāng)前活動(dòng)的所有線程數(shù)

  14. thrName = threading.current_thread().getName()

  15. thrCount = threading.active_count()

  16. print('{0}線程結(jié)束,當(dāng)線程數(shù)量={1}'.format(thrName, thrCount))

  17. print('消耗時(shí)間:', time.time() - start_time)

運(yùn)行腳本得到以下結(jié)果。

  1. 任務(wù): task-0

  2. 任務(wù): task-1

  3. 任務(wù): task-2

  4. MainThread線程結(jié)束,當(dāng)線程數(shù)量=4

  5. 消耗時(shí)間: 0.0010023117065429688

從本例的返回結(jié)果可以看出,主線程執(zhí)行完畢后,程序不會(huì)等待守護(hù)線程執(zhí)行完畢后就退出了。設(shè)置線程對(duì)象為守護(hù)線程,一定要在線程對(duì)象調(diào)用start()函數(shù)前設(shè)置。

多線程的鎖機(jī)制

多線程編程訪問(wèn)共享變量時(shí)會(huì)出現(xiàn)問(wèn)題,但是多進(jìn)程編程訪問(wèn)共享變量不會(huì)出現(xiàn)問(wèn)題。因?yàn)槎噙M(jìn)程中,同一個(gè)變量各自有一份拷貝存在于每個(gè)進(jìn)程中,互不影響,而多線程中,所有變量都由所有線程共享。

多個(gè)進(jìn)程之間對(duì)內(nèi)存中的變量不會(huì)產(chǎn)生沖突,一個(gè)進(jìn)程由多個(gè)線程組成,多線程對(duì)內(nèi)存中的變量進(jìn)行共享時(shí)會(huì)產(chǎn)生影響,所以就產(chǎn)生了死鎖問(wèn)題,怎么解決死鎖問(wèn)題是本節(jié)主要介紹的內(nèi)容。

1、變量的作用域

一般在函數(shù)體外定義的變量稱為全局變量,在函數(shù)內(nèi)部定義的變量稱為局部變量。全局變量所有作用域都可讀,局部變量只能在本函數(shù)可讀。函數(shù)在讀取變量時(shí),優(yōu)先讀取函數(shù)本身自有的局部變量,再去讀全局變量。 
內(nèi)容如下。

  1. # 全局變量

  2. balance = 1

  3. def change():

  4. # 定義全局變量

  5. global balance

  6. balance = 100

  7. # 定義局部變量

  8. num = 20

  9. print('change() balance={0}'.format(balance) )

  10. if __name__ == '__main__' :

  11. change()

  12. print('修改后的 balance={0}'.format(balance) )

運(yùn)行腳本得到以下結(jié)果。

  1. change() balance=100

  2. 修改后的 balance=100

如果注釋掉change()函數(shù)里的 global

  1. v1,那么得到的返回值是。

  2. change() balance=100

  3. 修改后的 balance=1

在本例中在change()函數(shù)外定義的變量balance是全局變量,在change()函數(shù)內(nèi)定義的變量num是局部變量,全局變量默認(rèn)是可讀的,可以在任何函數(shù)中使用,如果需要改變?nèi)肿兞康闹担枰诤瘮?shù)內(nèi)部使用global定義全局變量,本例中在change()函數(shù)內(nèi)部使用global定義全局變量balance,在函數(shù)里就可以改變?nèi)肿兞苛恕?/p>

在函數(shù)里可以使用全局變量,但是在函數(shù)里不能改變?nèi)肿兞?。想?shí)現(xiàn)多個(gè)線程共享變量,需要使用全局變量。在方法里加上全局關(guān)鍵字 global定義全局變量,多線程才可以修改全局變量來(lái)共享變量。

2、多線程中的鎖

多線程同時(shí)修改全局變量時(shí)會(huì)出現(xiàn)數(shù)據(jù)安全問(wèn)題,線程不安全就是不提供數(shù)據(jù)訪問(wèn)保護(hù),有可能出現(xiàn)多個(gè)線程先后更改數(shù)據(jù)造成所得到的數(shù)據(jù)是臟數(shù)據(jù)。在本例中我們生成2個(gè)線程同時(shí)修改change()函數(shù)里的全局變量balance時(shí),會(huì)出現(xiàn)數(shù)據(jù)不一致問(wèn)題。

本案例文件名為PythonFullStack\Chapter03\threadDemo03.py,內(nèi)容如下。

  1. import threading

  2. balance = 100

  3. def change(num, counter):

  4. global balance

  5. for i in range(counter):

  6. balance = num

  7. balance -= num

  8. if balance != 100:

  9. # 如果輸出這句話,說(shuō)明線程不安全

  10. print('balance=%d' % balance)

  11. break

  12. if __name__ == '__main__':

  13. thr1 = threading.Thread(target=change,args=(100,500000),name='t1')

  14. thr2 = threading.Thread(target=change,args=(100,500000),name='t2')

  15. thr1.start()

  16. thr2.start()

  17. thr1.join()

  18. thr2.join()

  19. print('{0} 線程結(jié)束'.format(threading.current_thread().getName()))

運(yùn)行以上腳本,當(dāng)2個(gè)線程運(yùn)行次數(shù)達(dá)到500000次時(shí),會(huì)出現(xiàn)以下結(jié)果。

  1. balance=200

  2. MainThread 線程結(jié)束

在本例中定義了一個(gè)全局變量balance,初始值為100,當(dāng)啟動(dòng)2個(gè)線程后,先加后減,理論上balance應(yīng)該為100。線程的調(diào)度是由操作系統(tǒng)決定的,當(dāng)線程t1和t2交替執(zhí)行時(shí),只要循環(huán)次數(shù)足夠多,balance結(jié)果就不一定是100了。從結(jié)果可以看出,在本例中線程t1和t2同時(shí)修改全局變量balance時(shí),會(huì)出現(xiàn)數(shù)據(jù)不一致問(wèn)題。

注意

在多線程情況下,所有的全局變量有所有線程共享。所以,任何一個(gè)變量都可以被任何一個(gè)線程修改,因此,線程之間共享數(shù)據(jù)最大的危險(xiǎn)在于多個(gè)線程同時(shí)改一個(gè)變量,把內(nèi)容給改亂了。

在多線程情況下,使用全局變量并不會(huì)共享數(shù)據(jù),會(huì)出現(xiàn)線程安全問(wèn)題。線程安全就是多線程訪問(wèn)時(shí),采用了加鎖機(jī)制,當(dāng)一個(gè)線程訪問(wèn)該類的某個(gè)數(shù)據(jù)時(shí),進(jìn)行保護(hù),其他線程不能進(jìn)行訪問(wèn)直到該線程讀取完,其他線程才可使用。不會(huì)出現(xiàn)數(shù)據(jù)不一致

在單線程運(yùn)行時(shí)沒(méi)有代碼安全問(wèn)題。寫多線程程序時(shí),生成一個(gè)線程并不代表多線程。在多線程情況下,才會(huì)出現(xiàn)安全問(wèn)題。

針對(duì)線程安全問(wèn)題,需要使用”互斥鎖”,就像數(shù)據(jù)庫(kù)里操縱數(shù)據(jù)一樣,也需要使用鎖機(jī)制。某個(gè)線程要更改共享數(shù)據(jù)時(shí),先將其鎖定,此時(shí)資源的狀態(tài)為“鎖定”,其他線程不能更改;直到該線程釋放資源,將資源的狀態(tài)變成“非鎖定”,其他的線程才能再次鎖定該資源?;コ怄i保證了每次只有一個(gè)線程進(jìn)行寫入操作,從而保證了多線程情況下數(shù)據(jù)的正確性。

互斥鎖的核心代碼如下:

  1. # 創(chuàng)建鎖

  2. mutex = threading.Lock()

  3. # 鎖定

  4. mutex.acquire()

  5. # 釋放

  6. mutex.release()

如果要確保balance計(jì)算正確,使用threading.Lock()來(lái)創(chuàng)建鎖對(duì)象lock,把 lock.acquire()和lock.release()加在同步代碼塊里,本例的同步代碼塊就是對(duì)全局變量balance進(jìn)行先加后減操作。

當(dāng)某個(gè)線程執(zhí)行change()函數(shù)時(shí),通過(guò)lock.acquire()獲取鎖,那么其他線程就不能執(zhí)行同步代碼塊了,只能等待知道鎖被釋放了,獲得鎖才能執(zhí)行同步代碼塊。由于鎖只有一個(gè),無(wú)論多少線程,同一個(gè)時(shí)刻最多只有一個(gè)線程持有該鎖,所以修改全局變量balance不會(huì)產(chǎn)生沖突。改良后的代碼內(nèi)容如下。

  1. import threading

  2. balance = 100

  3. lock = threading.Lock()

  4. def change(num, counter):

  5. global balance

  6. for i in range(counter):

  7. # 先要獲取鎖

  8. lock.acquire()

  9. balance = num

  10. balance -= num

  11. # 釋放鎖

  12. lock.release()

  13. if balance != 100:

  14. # 如果輸出這句話,說(shuō)明線程不安全

  15. print('balance=%d' % balance)

  16. break

  17. if __name__ == '__main__':

  18. thr1 = threading.Thread(target=change,args=(100,500000),name='t1')

  19. thr2 = threading.Thread(target=change,args=(100,500000),name='t2')

  20. thr1.start()

  21. thr2.start()

  22. thr1.join()

  23. thr2.join()

  24. print('{0} 線程結(jié)束'.format(threading.current_thread().getName()))

在本例中2個(gè)線程同時(shí)運(yùn)行l(wèi)ock.acquire()時(shí),只有一個(gè)線程能成功的獲取鎖,然后執(zhí)行代碼,其他線程就繼續(xù)等待直到獲得鎖位置。獲得鎖的線程用完后一定要釋放鎖,否則其他線程就會(huì)一直等待下去,成為死線程。

在運(yùn)行上面腳本就不會(huì)產(chǎn)生輸出信息,證明代碼是安全的。把 lock.acquire()和lock.release()加在同步代碼塊里,還要注意鎖的力度不要加的太大了。第一個(gè)線程只有運(yùn)行完了,第二個(gè)線程才能運(yùn)行,所以鎖要在需要同步代碼里加上。

留言回復(fù)你在機(jī)器學(xué)習(xí)方面做過(guò)哪些有趣的應(yīng)用,我們會(huì)在留言中隨機(jī)抽取一位讀者免費(fèi)送出北京大學(xué)出版社出版的《Python 3.x全棧開發(fā)從入門到精通》圖書一本。通過(guò)“拆解式”講解Python全棧開發(fā)全過(guò)程,本書集理論、技術(shù)、案例、項(xiàng)目開發(fā)經(jīng)驗(yàn)為一體,通過(guò)海量示例展示開發(fā)過(guò)程中的重點(diǎn)、疑點(diǎn)、難點(diǎn),是一本寶典式大全教程。京東年中購(gòu)物節(jié),每滿100減50.

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多