Python 四種創建多線程的方法

Python多線程

Python默認的解釋器,由於全局解釋器鎖的存在,確實在任意時刻都只有一個線程在執行代碼,致使多線程不能充分利用機器多核的特性。如果程序是CPU密集型的,使用Python多線程確實無法提升程序的效率,如果程序是IO密集型的,則可以使用Python的多線程提高程序的整體效率。

方法一:threading模塊創建線程

python的標準庫提供了兩個與線程相關的模塊,分別是thread和threading。其中,thread是低級模塊,threading是高級模塊,threading模塊對thread進行了封裝。只需要使用threading這個高級模塊即可。

import threading

def say_hi():
	time.sleep(1)
    print("hello world")

def main():
    for i in range(5):
        thread = threading.Thread(target=say_hi())
        thread.start()

if __name__ == "__main__":
    main()

在這個例子中,首先導入了與多線程相關的threading模塊。在main函數中通過for循環創建了5個線程。通過target取值爲say_hi的方式,告訴線程去執行say_hi函數。線程創建完後,調用線程的start方法運行線程。5個線程是併發運行的,整個python程序大約花費1秒多的時間。

給線程傳遞參數

示例:傳遞參數的例子。在Python中給線程傳遞參數非常簡單,通過Threading.Thread類調用args參數傳遞。無論需要多少個參數,都是通過元組的形式進行傳遞。

import threading

def say_hi(count,name):
    while count>0:
        print("hello",name)
        count -=1

def main():
    usernames = ['劉備','張飛','關羽','趙雲']
    for i in range(4):
        thread = threading.Thread(target=say_hi,args=(2,usernames[i]))
        thread.start()

if __name__ == "__main__":
    main()

線程的常用方法

threading.Thread類比較常用的方法有:
join:該方法會阻塞調用,直到線程中止
setDaemon:設置線程爲守護線程
isDaemon:判斷線程是否爲守護線程

方法二:通過繼承創建線程

除了直接構造threading.Thread對象以外,可以通過繼承threading.Thread的方式編寫多線程程序。通過繼承threading.Thread類進行多線程編程,只需要在子類中實現run方法,並在run方法中實現該線程的業務邏輯即可。

import threading

class MyThread(threading.Thread):
    def __init__(self,count,name):
        super(MyThread,self).__init__()
        self.count = count
        self.name = name

    def run(self):
        while self.count > 0:
            print("hello",self.name)
            self.count -= 1

def main():
    usernames = ['劉備','張飛','關羽','趙雲']
    for i in range(4):
        thread = MyThread(2,usernames[i])
        thread.start()

if __name__ == "__main__":
    main()

在這裏插入圖片描述

方法三:queue創建多線程

隊列是線程間最常用的交換數據的形式,Queue模塊實現了線程安全的隊列,尤其適合多線程編程。Queue模塊實現了三種類型隊列:
Queue Queue:一個先進先出的隊列,最先加入隊列的元素最先取出。
LifoQueue LifoQueue:一個後進先出的隊列,最後加入隊列的元素最先取出
PriorityQueue PriorityQueue:優先級隊列,隊列中的元素根據優先級排序。

示例:使用put方法向隊列中添加元素時,會將元素添加到隊列的尾部。使用get方法從Queue中獲取元素時,會從隊列的頭部取出元素。單線程情況下,Queue最大的優勢在於線程是安全的。

import queue

q = queue.Queue()

for i in range(3):
    q.put(i)

while not q.empty():
    print(q.get())
Queue常用方法:

empty:判斷隊列是否爲空
full:判斷隊列是否已滿
put:向隊列中添加元素,可以通過可選的block參數和timeout參數控制put是否爲阻塞等待。如果是阻塞等待,並且timeout是一個正數,put方法將會在超時以後引發Queue.Full異常。
put_nowait:非阻塞地向隊列添加元素
get:從隊列中取出元素,可以通過block參數控制是否阻塞等待,通過timeout控制阻塞等待的時間。如果超時也沒有得到元素,拋出Queue.Empty異常
get_nowait:非阻塞地從隊列中取出元素
task_done:與join一起工作,指示先前取出的元素以及完成處理
join:阻塞等待,直到所有消費者對每一個元素都調用了task_done

Queue多線程模型

開啓10個線程分別判斷數據是否是偶數,然後將結果放入queue中。線程結束後,在主線程中從queue中獲取計算結果。

# 開啓10個線程分別判斷數據是否是偶數,然後將結果放入queue中。線程結束後,在主線程中從queue中獲取計算結果。
import threading
from queue import Queue
from queue import LifoQueue
from queue import PriorityQueue

def is_even(value,q):
    if value % 2 == 0:
        q.put(True)
    else:
        q.put(False)


def main():
    q = Queue()

    for i in range(10):
        t = threading.Thread(target=is_even,args=(i,q))
        t.start()
        t.join()

    results = []
    for i in range(10):
        results.append(q.get())
    print(results)

if __name__ == "__main__":
    main()

方法四:線程池threadpool創建多線程

通過創建線程池threadpool的方法,向方法中傳入多個參數,同時獲取方法的返回值。threadpool是第三方模塊,需要先進行安裝,
pip instll threadpool

1)引入threadpool模塊import threadpool
2)創建線程池threadpool.ThreadPool()
3)創建需要線程池處理的任務即threadpool.makeRequests()
爲任務函數創建多線程,傳入函數參數和回調函數,回調函數用於處理結果數據
4)將創建的多個任務put到線程池中,threadpool.putRequest
5)等到所有任務處理完畢pool.wait()

import threadpool
#多線程執行的方法
def retry_read_ApsStatus(pairDate,pairShift,site,flag):
#flag返回的值爲1或0
#方法省略	
        return flag
#回調函數,接收的參數(請求本身,任務函數執行結果)        
def get_result(request,result):
    global results
    results.append(result)

results = []
# 聲明可容納3個線程的池
pool = threadpool.ThreadPool(3)
# 創建線程運行內容請求列表(線程工作函數,線程工作參數列表,回調函數)
list_var1 = [parse_date, parse_shift, 2080, ne_flag]
list_var3 = [parse_date, parse_shift, 2090, sz_flag]
list_var2 = [parse_date, parse_shift, 5060, xb_flag]
par_list = [(list_var1, None), (list_var2, None), (list_var3, None)]
re = threadpool.makeRequests(retry_read_ApsStatus, par_list, get_result)

#將每一個線程放到線程池
res = [pool.putRequest(req) for req in re]
pool.wait()
print("多線程返回結果")
print(results)
#多線程返回結果
#[1, 1, 1]

線程同步與互斥鎖

線程之所以比進程輕量,是因爲多個線程之間是共享內存的。多個線程之間可以平等訪問內存中的數據。因爲多個線程可以共享數據,爲了保證數據的正確性需要對多個線程進行同步。

如果多個線程之間存在資源共享,在不加鎖的前提下允許多個線程修改同一個對象,程序的數據結構可能會遭到破壞。在python標準庫的threading模塊中有一個名爲Lock的工廠函數,會返回一個thread.LockType對象。該對象的acquire方法用來獲取鎖,release方法用來釋放鎖。對於那些每次只允許一個線程操作的數據,可以將其操作放到acquire和release方法之間。

lock = threading.Lock()
lock.acquire()
lock.release()

案列:定義了一個全局變量,然後再main函數中創建了10個線程,每個線程都是將全局變量num累計1000次。由於num是一個全局變量,並且各個線程都需要更新這個變量,因此存在數據爭用的問題。爲了解決這個問題,使用了threading.Lock保護這個全局變量。所以有要修改num這個全局變量的線程,在修改之前都需要加鎖。使用上下管理器with語句來加鎖

import threading

#創建鎖
lock = threading.Lock()
num = 0

def incre(count):
    global num
    while count>0:
    #使用with加鎖
        with lock:
            num +=1
        count -= 1

def main():
    threads = []
    for i in range(10):
        thread = threading.Thread(target=incre,args=(1000,))
        thread.start()
        threads.append(thread)


    for thread in threads:
        thread.join()

    print("expected value is ", 10 * 1000,"real value is",num)

if __name__ == '__main__':
    main()

# 輸出結果如下所示:
#expected value is  10000 real value is 10000

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章