1. 程式人生 > >day-3 聊聊python多線程編程那些事

day-3 聊聊python多線程編程那些事

獲取鎖 垃圾清理 sum() gif 機制 isp 時間 .com 技術分享

  python一開始給我的印象是容易入門,適合應用開發,編程簡潔,第三方庫多等等諸多優點,並吸引我去深入學習。直到學習完多線程編程,在自己環境上驗證完這句話:python解釋器引入GIL鎖以後,多CPU場景下,也不再是並行方式運行,甚至比串行性能更差。不免有些落差,一開始就註定了這門語言遲早是有天花板的,對於一些並行要求高的系統,python可能不再成為首選,甚至是完全不考慮。但是事情也並不是絕對悲觀的,我們已經看到有一大批人正在致力優化這個特性,新版本較老版本也有了一定改進,一些核心模塊我們也可以選用其它模塊開發等等措施。

1、python多線程編程

threading是python實現多線程編程的常用庫,有兩種方式可以實現多線程:1、調用庫接口傳入功能函數和參數執行;2、自定義線程類繼承threading.Thread,然後重寫__init__和run方法。

1、調用庫接口傳入功能函數和參數執行

技術分享圖片
import threading
import queue
import time

‘‘‘
實現功能:定義一個FIFO的queue,10個元素,3個線程同時來獲取
‘‘‘

# 初始化FIFO隊列
q = queue.Queue()
for i in range(10):
    q.put(i)
print("%s : Init queue,size:%d"%(time.ctime(),q.qsize()))

# 線程功能函數,獲取隊列數據
def run(q,threadid):
    is_empty = False
    
while not is_empty: if not q.empty(): data = q.get() print("Thread %d get:%d"%(threadid,data)) time.sleep(1) else: is_empty = True # 定義線程列表 thread_handler_lists = [] # 初始化線程 for i in range(3): thread = threading.Thread(target=run,args = (q,i)) thread.start() thread_handler_lists.append(thread)
# 等待線程執行完畢 for thread_handler in thread_handler_lists: thread_handler.join() print("%s : End of progress"%(time.ctime()))
View Code

2、自定義線程類繼承threading.Thread,然後重寫__init__和run方法

  和其它語言一樣,為了保證多線程間數據一致性,threading庫自帶鎖功能,涉及3個接口:

thread_lock = threading.Lock() 創建一個鎖對象

thread_lock.acquire() 獲取鎖

thread_lock.release() 釋放鎖

  註意:由於python模塊queue已經實現多線程安全,實際編碼中,不再需要進行鎖的操作,此處只是進行編程演示。

技術分享圖片
import threading
import queue
import time

‘‘‘
實現功能:定義一個FIFO的queue,10個元素,3個線程同時來獲取
queue線程安全的隊列,因此不需要加
thread_lock.acquire()
thread_lock.release()

‘‘‘

# 自定義一個線程類,繼承threading.Thread,重寫__init__和run方法即可
class MyThread(threading.Thread):
    def __init__(self,threadid,name,q):
        threading.Thread.__init__(self)
        self.threadid = threadid
        self.name = name
        self.q =q
        print("%s : Init %s success."%(time.ctime(),self.name))

    def run(self):
        is_empty = False
        while not is_empty:
            thread_lock.acquire()
            if not q.empty():
                data  = self.q.get()
                print("Thread %d get:%d"%(self.threadid,data))
                time.sleep(1)
                thread_lock.release()
            else:
                is_empty = True
                thread_lock.release()

# 定義一個鎖
thread_lock = threading.Lock()
# 定義一個FIFO隊列
q = queue.Queue()
# 定義線程列表
thread_name_list = ["Thread-1","Thread-2","Thread-3"]
thread_handler_lists = []

# 初始化隊列
thread_lock.acquire()
for i in range(10):
    q.put(i)
thread_lock.release()
print("%s : Init queue,size:%d"%(time.ctime(),q.qsize()))

# 初始化線程
thread_id = 1
for thread_name in thread_name_list:
    thread = MyThread(thread_id,thread_name,q)
    thread.start()
    thread_handler_lists.append(thread)
    thread_id += 1

# 等待線程執行完畢
for thread_handler in thread_handler_lists:
    thread_handler.join()

print("%s : End of progress"%(time.ctime()))
View Code

  

2、python多線程機制分析

  討論前,我們先梳理幾個概念:

  並行並發

  並發的關鍵是你有處理多個任務的能力,不一定要同時。而並行的關鍵是你有同時處理多個任務的能力。我認為它們最關鍵的點就是:是否是『同時』,或者說並行是並發的子集。

  GIL

  GIL:全局解釋鎖,python解釋器級別的鎖,為了保證程序本身運行正常,例如python的自動垃圾回收機制,在我們程序運行的同時,也在進行垃圾清理工作。

  下圖試圖模擬A進程中3個線程的執行情況:

    技術分享圖片

    1、 t1、t2、t3線程處於就緒狀態,同時向python解釋器獲取GIL鎖

    2、 假設t1獲取到GIL鎖,被python分配給任意CPU執行,處於運行狀態

    3、 Python基於某種調度方式(例如pcode),會讓t1釋放GIL鎖,重新處於就緒狀態

    4、 重復1步驟,假設這時t2獲取到GIL鎖,運行過程同上,被python分配給任意CPU執行,處於運行狀態,Python基於某種調度方式(例如pcode),會讓t2釋放GIL鎖,重新處於就緒狀態

    5、 最後可以推得t1、t2、t3按如下1、2、3、4方式串行運行

因此,盡管t1、t2、t3為三個線程,理論上可以並行運行,但實際上python解釋器引入GIL鎖以後,多CPU場景下,也不再是並行方式運行,甚至比串行性能更差,下面我們做個測試:

我們寫兩個計算函數,測試單線程和多線程的時間開銷,代碼如下:

技術分享圖片
import threading
import time

# 定義兩個計算量大的函數
def sum():
    sum = 0
    for i in range(100000000):
        sum += i

def mul():
    sum = 0
    for i in range(10000000):
        sum *= i

# 單線程時間測試
starttime = time.time()
sum()
mul()
endtime = time.time()
period = endtime - starttime
print("The single thread cost:%d"%(period))

# 多線程時間測試
starttime = time.time()
l = []
t1 = threading.Thread(target = sum)
t2 = threading.Thread(target = sum)
l.append(t1)
l.append(t2)
for i in l:
    i.start()
for i in l:
    i.join()
endtime = time.time()
period = endtime - starttime
print("The mutiple thread cost:%d"%(period))


print("End of program.")
View Code

測試發現,多線程的時間開銷居然比單線程還要大

技術分享圖片

這個結果有點讓人不可接受,那有沒有辦法優化?答案是有的,比如把多線程變成多進程,但是考慮到進程開銷問題,實際編程中,不能開過多進程,另外進程+協程也可以提高一定性能,這裏暫時不再深入分析。

有興趣可以繼續閱讀下鏈接博客:http://cenalulu.github.io/python/gil-in-python/

day-3 聊聊python多線程編程那些事