久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

Python線程5分鐘完全解讀

 昵稱64554919 2019-06-06

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

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

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

線程模塊

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

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

  1. import threading

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

主要參數(shù)說明:

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

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

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

  • Thread對象主要方法說明:

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

  • start():啟動線程活動,。

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

  • isAlive(): 返回線程是否活動的,。

  • getName(): 返回線程名,。

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

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

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

創(chuàng)建線程的時候,,只需要傳入一個執(zhí)行函數(shù)和函數(shù)的參數(shù)即可完成threading.Thread實例的創(chuàng)建。下面的例子使用Thread類來產(chǎn)生2個子線程,,然后啟動2個子線程并等待其結(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. #啟動2個線程

  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)會啟動一個線程,,把該線程稱為主線程,,主線程有可以啟動新的線程,,Python的threading模塊有個current_thread()函數(shù),,它將返回當(dāng)前線程的示例,。從當(dāng)前線程的示例可以獲得前運(yùn)行線程名字,,核心代碼如下。

  1. threading.current_thread().getName()

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

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

  2. th1.start()

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

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

直接創(chuàng)建threading.Thread的子類來創(chuàng)建一個線程對象,實現(xiàn)多線程,。通過繼承Thread類,,并重寫Thread類的run()方法,在run()方法中定義具體要執(zhí)行的任務(wù),。在Thread類中,,提供了一個start()方法用于啟動新進(jìn)程,,線程啟動后會自動調(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. # 啟動線程

  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é)果可以看出,,通過創(chuàng)建Thread類來產(chǎn)生2個線程對象thr1和thr2,重寫Thread類的run()函數(shù),,把業(yè)務(wù)邏輯放入其中,,通過調(diào)用線程對象的start()方法啟動線程。通過調(diào)用線程對象的join()函數(shù),,等待該線程完成,,在繼續(xù)下面的操作。

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

守護(hù)線程

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

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

在本例中創(chuàng)建1個主線程3個子線程,,讓主線程和子線程并行執(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)) # 查看每個子線程

  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)前活動的所有線程數(shù)

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

  15. print('消耗時間:', 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. 消耗時間: 0.0009751319885253906

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

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

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

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

在本例的基礎(chǔ)上,把所有的子線程都設(shè)置為守護(hù)線程,。子線程變成守護(hù)線程后,,只要主線程執(zhí)行完畢,程序不管子線程有沒有執(zhí)行完畢,,程序都會退出,。使用線程對象的setDaemon(True)函數(shù)來設(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ù)線程,,在啟動線程前設(shè)置

  11. thr.setDaemon(True)

  12. thr.start()

  13. # 查看主線程和當(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('消耗時間:', 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. 消耗時間: 0.0010023117065429688

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

多線程的鎖機(jī)制

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

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

1、變量的作用域

一般在函數(shù)體外定義的變量稱為全局變量,,在函數(shù)內(nèi)部定義的變量稱為局部變量,。全局變量所有作用域都可讀,局部變量只能在本函數(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)肿兞俊O雽崿F(xiàn)多個線程共享變量,,需要使用全局變量,。在方法里加上全局關(guān)鍵字 global定義全局變量,,多線程才可以修改全局變量來共享變量。

2,、多線程中的鎖

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

本案例文件名為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. # 如果輸出這句話,說明線程不安全

  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個線程運(yùn)行次數(shù)達(dá)到500000次時,,會出現(xiàn)以下結(jié)果。

  1. balance=200

  2. MainThread 線程結(jié)束

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

注意

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

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

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

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

互斥鎖的核心代碼如下:

  1. # 創(chuàng)建鎖

  2. mutex = threading.Lock()

  3. # 鎖定

  4. mutex.acquire()

  5. # 釋放

  6. mutex.release()

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

當(dāng)某個線程執(zhí)行change()函數(shù)時,通過lock.acquire()獲取鎖,,那么其他線程就不能執(zhí)行同步代碼塊了,,只能等待知道鎖被釋放了,獲得鎖才能執(zhí)行同步代碼塊,。由于鎖只有一個,,無論多少線程,同一個時刻最多只有一個線程持有該鎖,,所以修改全局變量balance不會產(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. # 如果輸出這句話,,說明線程不安全

  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個線程同時運(yùn)行l(wèi)ock.acquire()時,,只有一個線程能成功的獲取鎖,然后執(zhí)行代碼,其他線程就繼續(xù)等待直到獲得鎖位置,。獲得鎖的線程用完后一定要釋放鎖,,否則其他線程就會一直等待下去,成為死線程,。

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

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

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

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多