Python用threading實現(xiàn)多線程詳解
多線程
多線程是個提高程序運行效率的好辦法,本來要順序執(zhí)行的程序現(xiàn)在可以并行執(zhí)行,可想而知效率要提高很多。但是多線程也不是能提高所有程序的效率。程序的兩個極端是‘CPU 密集型'和‘I/O 密集型'兩種,多線程技術比較適用于后者,因為在串行結構中當你去讀寫磁盤或者網(wǎng)絡通信的時候 CPU 是閑著的,畢竟網(wǎng)絡比磁盤要慢幾個數(shù)量級,磁盤比內存慢幾個數(shù)量級,內存又比 CPU 慢幾個數(shù)量級。多線程技術就可以同時執(zhí)行,比如你的程序需要發(fā)送 N 個 http 數(shù)據(jù)包(10 秒),還需要將文件從一個位置復制到另一個位置(20 秒),然后還需要統(tǒng)計另一個文件中'hello,world'字符串的出現(xiàn)次數(shù)(4 秒),現(xiàn)在一共是要用 34 秒。但是因為這些操作之間沒有關聯(lián),所以可以寫成多線程程序,幾乎只需要 20 秒就完成了。這是針對 I/O 密集型的,如果是 CPU 密集型的就不行了。比如我的程序要計算 1000 的階乘(10 秒),還要計算 100000 的累加(5 秒),那么即使程序是并行的,還是會要用 15 秒,甚至更多。因為當程序使用 CPU 的時候 CPU 是通過輪轉來執(zhí)行的,IO 密集型的程序可以在 IO 的同時用 CPU 計算,但是這里的 CPU 密集型就只能先執(zhí)行一會兒線程 1 再執(zhí)行一會兒線程 2。所以就需要 15 秒,甚至會更多,因為 CPU 在切換的時候需要耗時。解決 CPU 密集型程序的多線程問題就是 CPU 的事情了,比如 Intel 的超線程技術,可以在同一個核心上真正的并行兩個線程,所以稱之為‘雙核四線程'或者‘四核八線程',我們這里具體的先不談,談我也不知道。
Python 騙人
說了這么多多線程的好處,但是其實 Python 不支持真正意義上的多線程編程。在 Python 中有一個叫做 GIL 的東西,中文是 全局解釋器 ,這東西控制了 Python,讓 Python 只能同時運行一個線程。相當于說真正意義上的多線程是由 CPU 來控制的,Python 中的多線程由 GIL 控制。如果有一個 CPU 密集型程序,用 C 語言寫的,運行在一個四核處理器上,采用多線程技術的話最多可以獲得 4 倍的效率提升,但是如果用 Python 寫的話并不會有提高,甚至會變慢,因為線程切換的問題。所以 Python 多線程相對更加適合寫 I/O 密集型程序,再說了真正的對效率要求很高的 CPU 密集型程序都用 C/C++ 去了。
第一個多線程
Python 中多線程的庫一般用thread和threading這兩個,thread不推薦新手和一般人使用,threading模塊就相當夠用了。
有一個程序,如下。兩個循環(huán),分別休眠 3 秒和 5 秒,串行執(zhí)行的話需要 8 秒。
#!/usr/bin/env python # coding=utf-8 import time def sleep_3(): time.sleep(3) def sleep_5(): time.sleep(5) if __name__ == '__main__': start_time = time.time() print 'start sleep 3' sleep_3() print 'start sleep 5' sleep_5() end_time = time.time() print str(end_time - start_time) + ' s'
輸出是這樣的
start sleep 3 start sleep 5 8.00100016594 s
然后我們對它進行修改,使其變成多線程程序,雖然改動沒有幾行。首先引入了 threading 的庫,然后實例化一個 threading.Thread
對象,將一個函數(shù)傳進構造方法就行了。然后調用 Thread 的 start 方法開始一個線程。join()
方法可以等待該線程結束,就像我下面用的,如果我不加那兩個等待線程結束的代碼,那么就會直接執(zhí)行輸出時間的語句,這樣一來統(tǒng)計的時間就不對了。
#!/usr/bin/env python # coding=utf-8 import time import threading # 引入threading def sleep_3(): time.sleep(3) def sleep_5(): time.sleep(5) if __name__ == '__main__': start_time = time.time() print 'start sleep 3' thread_1 = threading.Thread(target=sleep_3) # 實例化一個線程對象,使線程執(zhí)行這個函數(shù) thread_1.start() # 啟動這個線程 print 'start sleep 5' thread_2 = threading.Thread(target=sleep_5) # 實例化一個線程對象,使線程執(zhí)行這個函數(shù) thread_2.start() # 啟動這個線程 thread_1.join() # 等待thread_1結束 thread_2.join() # 等待thread_2結束 end_time = time.time() print str(end_time - start_time) + ' s'
執(zhí)行結果是這樣的
start sleep 3 start sleep 5 5.00099992752 s
daemon 守護線程
在我們理解中守護線程應該是很重要的,類比于 Linux 中的守護進程。但是在threading.Thread
中偏偏不是。
如果把一個線程設置為守護線程,就表示這個線程是不重要的,進程退出的時候不需要等待這個線程執(zhí)行完成。 ---------《Python 核心編程 第三版》
在 Thread 對象中默認所有線程都是非守護線程,這里有兩個例子說明區(qū)別。這段代碼執(zhí)行的時候就沒指定my_thread的daemon屬性,所以默認為非守護,所以進程等待他結束。最后就可以看到 100 個 hello,world
#!/usr/bin/env python # coding=utf-8 import threading def hello_world(): for i in range(100): print 'hello,world' if __name__ == '__main__': my_thread = threading.Thread(target=hello_world) my_thread.start()
這里設置了my_thread為守護線程,所以進程直接就退出了,并沒有等待他的結束,所以我們看不到 100 個 hello,world 只有幾個而已。甚至還會拋出一個異常告訴我們有線程沒結束。
#!/usr/bin/env python # coding=utf-8 import threading def hello_world(): for i in range(100): print 'hello,world' if __name__ == '__main__': my_thread = threading.Thread(target=hello_world) my_thread.daemon = True # 設置了標志位True my_thread.start()
傳個參數(shù)
之前的代碼都是直接執(zhí)行一段代碼,沒有過參數(shù)的傳遞,那么怎么傳遞參數(shù)呢?其實還是很簡單的。threading.Thread(target=hello_world, args=('hello,', 'world'))
就可以了。args 后面跟的是一個元組,如果沒有參數(shù)可以不寫,如果有參數(shù)就直接在元組里按順序添加就行了。
#!/usr/bin/env python # coding=utf-8 import threading def hello_world(str_1, str_2): for i in range(10): print str_1 + str_2 if __name__ == '__main__': my_thread = threading.Thread(target=hello_world, args=('hello,', 'world')) # 這里傳遞參數(shù) my_thread.start()
再來個多線程
threading 有三種創(chuàng)建 Thread 對象的方式,但是一般只會用到兩種,一種是上面0X02說的傳個函數(shù)進去,另一種就是這里說的繼承threading.Thread
。在這兒我們自己定義了兩個類,類里重寫了 run()
方法,也就是調用 start()
之后執(zhí)行的代碼,開啟線程就和之前開啟是一樣的。之前的方式更面向過程,這個更面向對象。
#!/usr/bin/env python # coding=utf-8 import threading class MyThreadHello(threading.Thread): def run(self): for i in range(100): print 'hello' class MyThreadWorld(threading.Thread): def run(self): for i in range(100): print 'world' if __name__ == '__main__': thread_hello = MyThreadHello() thread_world = MyThreadWorld() thread_hello.start() thread_world.start()
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流。
- Python中多線程thread與threading的實現(xiàn)方法
- python基于queue和threading實現(xiàn)多線程下載實例
- python使用threading獲取線程函數(shù)返回值的實現(xiàn)方法
- Python 使用threading+Queue實現(xiàn)線程池示例
- Python線程協(xié)作threading.Condition實現(xiàn)過程解析
- Python3 socket即時通訊腳本實現(xiàn)代碼實例(threading多線程)
- python中threading和queue庫實現(xiàn)多線程編程
- Python中threading庫實現(xiàn)線程鎖與釋放鎖
- Python?threading和Thread模塊及線程的實現(xiàn)
相關文章
python實現(xiàn)學生信息管理系統(tǒng)(面向對象)
這篇文章主要介紹了python實現(xiàn)面向對象版學生信息管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-06-06如何使用python讀取Excel指定范圍并轉為數(shù)組
python處理數(shù)據(jù)文件的途徑有很多種,下面這篇文章主要給大家介紹了關于如何使用python讀取Excel指定范圍并轉為數(shù)組的相關資料,文中通過圖文以及實例代碼介紹的非常詳細,需要的朋友可以參考下2022-11-11Python Pandas實現(xiàn)數(shù)據(jù)分組求平均值并填充nan的示例
今天小編就為大家分享一篇Python Pandas實現(xiàn)數(shù)據(jù)分組求平均值并填充nan的示例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-07-07利用Python自制網(wǎng)頁并實現(xiàn)一鍵自動生成探索性數(shù)據(jù)分析報告
這篇文章主要介紹了利用Python自制了網(wǎng)頁并實現(xiàn)一鍵自動生成探索性數(shù)據(jù)分析報告,文章內容具有一定的參考價值,需要的小伙伴可以參考一下2022-05-05Numpy中的shape、reshape函數(shù)的區(qū)別
本文主要介紹了Numpy中的shape、reshape函數(shù)的區(qū)別,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-07-07