今天我們要一起探索的是Python編程中的一個超炫酷領域——多線程!想象一下,你的程序能像超人一樣同時處理多個任務,是不是很激動人心?讓我們以輕松愉快的方式,一步步揭開它的神秘面紗。
想象你是個廚房大廚,一邊炒菜一邊洗菜,這就是多線程的日常。在Python里,threading模塊就是我們的廚房神器。
import threadingdef cook(): # 炒菜線程 print("炒菜中...") def wash(): # 洗菜線程 print("洗菜中...")# 創建線程對象thread1 = threading.Thread(target=cook)thread2 = threading.Thread(target=wash)# 啟動線程thread1.start()thread2.start()# 等待所有線程完成thread1.join()thread2.join()print("飯做好啦!")
這段代碼中,Thread類用來創建線程,target參數指定線程要執行的函數。start()讓線程開始執行,而join()確保主線程等待這些小線程們完成它們的任務。
在多線程世界,如果兩個線程同時操作同一資源(比如共享食材),就可能出亂子。這時就需要“鎖”來幫忙了,Python里的鎖叫Lock。
import threadingshared_resource = 0lock = threading.Lock()def increase(): global shared_resource lock.acquire() # 上鎖,防止同時訪問 shared_resource += 1 lock.release() # 解鎖,釋放控制權threads = [threading.Thread(target=increase) for _ in range(100)]for t in threads: t.start()for t in threads: t.join()print("共享資源的最終值:", shared_resource)
每次訪問共享資源前,先acquire()上鎖,操作完后release()解鎖,這樣就避免了數據混亂。
但鎖用不好也會出問題,就像兩個廚師互相等待對方手中的鍋,形成了死鎖。要小心設計,避免循環等待。
想象一下,如果你每次炒菜都要新雇一個廚師,那得多浪費?線程池(ThreadPoolExecutor)就是解決這個問題的神器,它預先創建好一些線程,重復利用。
from concurrent.futures import ThreadPoolExecutordef task(n): print(f"執行任務{n}")with ThreadPoolExecutor(max_workers=5) as executor: executor.map(task, range(1, 6))
這里,ThreadPoolExecutor創建了一個最多有5個線程的池,map()函數并行執行任務列表中的每個任務。
守護線程就像廚房的清潔工,在所有其他線程完成后默默清理。通過setDaemon(True)設置線程為守護線程。
def cleaner(): while True: # 假設這是一個無限循環,清理任務 print("打掃廚房...") if not other_threads_running(): # 假定函數檢查其他線程是否還在運行 breakclean_thread = threading.Thread(target=cleaner)clean_thread.setDaemon(True)clean_thread.start()# 其他線程的代碼...print("廚房關閉,清潔完成。")
雖然Python標準庫沒有直接提供線程優先級的功能,但可以通過隊列等間接實現。不過,大多數情況下,Python的線程調度是公平的,不需要擔心。
Python的GIL是一個讓人又愛又恨的東西,它保證了任何時刻只有一個線程在執行Python字節碼,這對多核CPU來說不是個好消息。但在I/O密集型任務中,GIL的影響沒那么大。
不同線程需要不同的“調料”怎么辦?threading.local()來幫忙,它提供了線程本地的存儲空間。
import threadinglocal_data = threading.local()def set_data(): local_data.value = "這是我的調料"def get_data(): print(local_data.value)t1 = threading.Thread(target=set_data)t2 = threading.Thread(target=get_data)t1.start()t2.start()t1.join()t2.join()
在這里,每個線程都有自己的local_data,互不影響。
線程中的異常不會自動傳遞到主線程,需要用try-except捕獲處理。
def risky_task(): raise ValueError("出錯了!")try: t = threading.Thread(target=risky_task) t.start() t.join()except ValueError as e: print(f"捕獲到異常: {e}")
確保即使線程出錯,程序也不會突然崩潰。
最后,來點實戰吧,比如多線程下載圖片,體驗速度的提升。
import requestsimport threadingfrom queue import Queuedef download_image(url): response = requests.get(url) with open(f"image_{url[-4:]}", 'wb') as f: f.write(response.content) print(f"下載完成:{url}")image_urls = ["http://example.com/image1.jpg", "http://example.com/image2.jpg"] # 假設的URLqueue = Queue()threads = []for url in image_urls: queue.put(url)def worker(): while not queue.empty(): url = queue.get() download_image(url) queue.task_done()for _ in range(3): # 啟動3個下載線程 t = threading.Thread(target=worker) t.start() threads.append(t)# 等待所有下載任務完成for t in threads: t.join()print("所有圖片下載完成!")
通過隊列分配任務給多個線程,實現了并行下載,大大提高了效率。
好啦,今天的探險就到這里!希望你已經對Python多線程有了更深入的理解。
本文鏈接:http://www.tebozhan.com/showinfo-26-89711-0.htmlPython 多線程編程的十個關鍵概念
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
下一篇: C# 線程池的使用方法