Python快速而美麗[v1.0.0][線程池]

線程池

  • 當程序中需要創建大量生存期很短的線程時,應該考慮使用線程池,因爲線程的創建成本較高,每次創建都要與系統交互,線程池在系統啓動時就創建大量空閒的線程,程序只要將一個函數提交給線程池,線程池就會啓動一個空閒的線程來執行它,當該函數執行結束後,該線程並不會死亡,而是再次返回到線程池中變成空閒狀態,等待下一個函數
  • 使用線程池可以有效的控制系統中併發線程的數量,線程池的最大線程數就限制了併發的上限
  • 線程池的基類是concurrent.futures模塊中的Executor,它提供了兩個子類ThreadPoolExecutorProcessPoolExecutor前者用於創建線程池後者用於創建進程池

Executor

它提供瞭如下常用方法:

  • submit(fn, *args, **kwargs):將fn函數提交給線程池,*args代表傳給fn函數的參數,*kwargs代表以關鍵字參數的形式爲fn函數傳入參數
  • map(func, *iterables, timeout=None, chunksize=1):該函數類似於全局函數map(func, *iterables),只是該函數將會爲iterables的每一個元素啓動一個線程,以併發的形式來執行func函數,以異步方式立即對iterables執行map處理,相當於啓動len(iterables)個線程,並返回每個線程的執行結果
  • shutdown(wait=True):關閉線程池,在用完一個線程池後,應該調用其shutdown()方法,該方法將啓動線程池的關閉序列,調用了該方法後線程池不再接收新任務,但會將以前所有的已提交任務執行完成,當線程池中所有的任務都執行完成後,該線程池中的所有線程都會死亡

程序將task函數提交給線程池後,submit方法會返回一個Future對象,Future類主要用於獲取線程任務函數的返回值,因爲線程任務會在新線程中以異步方式執行,因此線程執行的函數相當於一個將來的任務

Future

它提供瞭如下常用方法:

  • cancel():取消該Future代表的線程任務,如果該任務正在執行,不可取消,則該方法返回False,否則,程序會取消該任務,並返回True
  • cancelled():返回Future代表的線程任務是否被成功取消
  • running():如果該Future代表的線程任務正在執行、不可取消,該方法返回Ture
  • done():如果該Future代表的線程任務被成功取消或者執行完成,則該方法返回Ture
  • result(timeout=None):獲取該Future代表的線程任務最後返回的結果,如果Future代表的線程任務還未完成,該方法將會阻塞當前線程,其中timeout參數指定最多阻塞多少秒
  • exception(timeout=None):獲取該Future代表的線程任務所引發的異常,如果該任務成功完成,沒有異常,則該方法返回None
  • add_done_callback(fn):爲該Future代表的線程任務註冊一個“回調函數”,當該任務成功完成時,程序會自動觸發該fn函數

代碼示例

使用線程池執行任務的步驟如下:

  • 調用ThreadPoolExecutor類的構造器創建一個線程池
  • 定義一個普通作爲線程任務
  • 調用ThreadPoolExecutor對象的submit()方法來提交線程任務
  • 當不想提交任何任務時,調用ThreadPoolExecutor對象的shutdown()方法來關閉線程池
from concurrent.futures import ThreadPoolExecutor
import threading
import time

# 定義一個準備作爲線程任務的函數
def action(max):
    my_sum = 0
    for i in range(max):
        print(threading.current_thread().name + '  ' + str(i))
        my_sum += i
    return my_sum
# 創建一個包含2條線程的線程池
pool = ThreadPoolExecutor(max_workers=2)
# 向線程池提交一個task, 50會作爲action()函數的參數, 並且返回future對象
future1 = pool.submit(action, 50)
# 向線程池再提交一個task, 100會作爲action()函數的參數, 並且返回future對象
future2 = pool.submit(action, 100)
# 判斷future1代表的任務是否結束
print(future1.done())
time.sleep(3)
# 判斷future2代表的任務是否結束
print(future2.done())
# 查看future1代表的任務返回的結果
print(future1.result())
# 查看future2代表的任務返回的結果
print(future2.result())
# 關閉線程池
pool.shutdown()

需要注意的是,當程序使用Future的result()方法來獲取結果時,該方法將會阻塞當前線程,如果沒有指定timeout,則當前線程將一直處於阻塞狀態,直到Future得到返回結果
如果程序不希望直接調用result()方法阻塞線程,則可通過調用add_done_callback()方法來添加回調函數,該回調函數形如fn(future), 當線程任務完成後,程序會自動觸發該回調函數,並將對應的Future對象 作爲參數傳給該回調函數

from concurrent.futures import ThreadPoolExecutor
import threading
import time

# 定義一個準備作爲線程任務的函數
def action(max):
    my_sum = 0
    for i in range(max):
        print(threading.current_thread().name + '  ' + str(i))
        my_sum += i
    return my_sum
# 創建一個包含2條線程的線程池
with ThreadPoolExecutor(max_workers=2) as pool:
    # 向線程池提交一個task, 50會作爲action()函數的參數
    future1 = pool.submit(action, 50)
    # 向線程池再提交一個task, 100會作爲action()函數的參數
    future2 = pool.submit(action, 100)
    def get_result(future):
        print(future.result())
    # 爲future1添加線程完成的回調函數
    future1.add_done_callback(get_result)
    # 爲future2添加線程完成的回調函數
    future2.add_done_callback(get_result)
    print('--------------')

由於線程池實現了上下文管理協議(Context Manage Protocol),因此,程序可以使用with語句來管理線程池,這樣可以避免手動關閉線程池

map

from concurrent.futures import ThreadPoolExecutor
import threading
import time

# 定義一個準備作爲線程任務的函數
def action(max):
    my_sum = 0
    for i in range(max):
        print(threading.current_thread().name + '  ' + str(i))
        my_sum += i
    return my_sum
# 創建一個包含4條線程的線程池
with ThreadPoolExecutor(max_workers=4) as pool:
    # 使用線程執行map計算
    # 後面元組有3個元素,因此程序啓動3條線程來執行action函數
    results = pool.map(action, (50, 100, 150))
    print('--------------')
    for r in results:
        print(r)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章