概述
記得前些日子傘哥發(fā)過一個微博調(diào)侃過Python由于GIL鎖的存在,所以現(xiàn)在死活想把自己和機器學(xué)習(xí)扯上關(guān)系。確實,由于這個全局解釋鎖的存在,任何時刻只有一個核在執(zhí)行Python代碼实蔽,這樣就導(dǎo)致不能充分利用多核處理器的特性。但是谨读,我們的程序也不總是在計算的局装,程序有IO密集型和CPU計算密集型。如果我們的程序需要等待用戶輸入漆腌,等待文件讀寫以及網(wǎng)絡(luò)收發(fā)數(shù)據(jù)贼邓,那計算機就會把這些等待操作放到后臺去處理,把CPU留出來用于計算闷尿。所以塑径,雖然CPU密集型的程序用Python多線程確實無法提高效率,但是如果是IO密集型的程序填具,是可以使用多線程提高效率的统舀。
接下來,讓我們通過例子一步一步了解多線程:
利用threading模塊使用多線程
Python標準庫自帶了兩個多線程模塊劳景,分別是threading
和thread
誉简,其中,thread
是低級模塊盟广,threading
是對thread
的封裝闷串,一般,我們直接使用threading
即可筋量。下面來看一個簡單的多線程例子:
import threading
def say_hello():
print("Hello world!")
def main():
for i in range(10):
thread = threading.Thread(target=say_hello)
thread.start()
main()
在這個例子中烹吵,我們首先定義了要多線程執(zhí)行的函數(shù)say_hello
,然后我們在主函數(shù)里創(chuàng)建了10個線程,target
取值是say_hi
桨武,告訴線程要執(zhí)行的函數(shù)肋拔,然后我們調(diào)用start()
方法吩咐線程去執(zhí)行這些線程。
這個程序最終會輸出10個Hello world!
呀酸,與["Hello world!" for i in range(10)]
效果一致凉蜂,那么為什么還要使用多線程呢,我們通過下面這個例子理解下多線程的意義:
import threading
import time
def say_hello():
time.sleep(1)
print("Hello world!")
def main():
for i in range(10):
thread = threading.Thread(target=say_hello)
thread.start()
main()
在這個例子里,我們加了time.sleep(1)
來模擬等待事件×裕現(xiàn)在如果用普通的循環(huán)來迭代茎杂,代碼執(zhí)行完需要至少20秒,而多線程運行只需要1秒多爆存,減少了程序整體運行的時間蛉顽。
給線程傳參和線程常用方法
在上面的代碼中蝗砾,我們并沒有給say_hello
傳參數(shù)先较,在多線程里傳參很簡單,只需要這樣做就好了:
import threading
def say_hello(count, name):
print("Hello world!", name)
count -= 1
def main():
name_list = ['Bob', 'Jack', 'Jone', 'Mike', 'David']
for i in range(5):
thread = threading.Thread(target=say_hello, args=(10, name_list[i]))
thread.start()
main()
在threading.Thread
類中悼粮,常用的方法有:
- isAlive: 檢查線程是否在運行中
- getName: 獲取線程名稱
- setName: 設(shè)置線程名稱
- join:阻塞線程調(diào)用闲勺,直到線程中止
- setDaemon:設(shè)置線程為守護線程
- isDaemon: 判斷線程是否是守護線程
通過繼承創(chuàng)建線程
除了直接實例化threading.Thread
對象,我們還可以通過繼承threading.Thread
來編寫多線程的類扣猫。然后把多線程調(diào)用的函數(shù)攜程一個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 > 10:
print("hello", self.name)
self.count -= 1
線程與互斥鎖
多個線程之間 內(nèi)存是共享的,所以線程比進程輕量申尤。多個線程是可以同時訪問內(nèi)存中的數(shù)據(jù)的癌幕,如果多個線程同時修改一個對象,那這份數(shù)據(jù)可能會被破壞昧穿,Python的threading
類中提供了Lock
方法勺远,它會返回一個鎖對象,一般通過lock.acquire()
來獲取鎖时鸵,通過lock.release()
來釋放鎖胶逢,對于那種只允許一個線程操作 的數(shù)據(jù),一般把對其的操作放在lock.acquire()
和lock.release()
中間饰潜。
無論在什么情況下初坠,我們都要保證代碼要釋放鎖,所以其他語言中一般把加鎖和釋放鎖放在try/finally
語句中彭雾。在Python中碟刺,其實我們可以用上下文管理器來簡化代碼,關(guān)于上下文管理器的介紹可以參考我前面的文章:上下文管理器,這里我們可以這樣使用鎖:
with lock:
#lock processing
下面來看一個使用互斥鎖的例子薯酝,在這個例子中半沽,我們使用了全局變量,然后創(chuàng)建10個線程蜜托,每個線程做同樣的事情抄囚,由于num
是全局變量,而且每個線程都需要使用這個變量橄务,因此存在著數(shù)據(jù)爭用的問題幔托,所以,我們就需要使用互斥鎖保護這個全局變量:所有修改這個變量的線程在修改前都需要加鎖,在increment
函數(shù)中重挑,我們通過with
語句進行加鎖嗓化。如下所示:
import threading
lock = threading.Lock()
num = 0
def increment(count):
global num
while count > 0:
with lock:
num += 1
count -= 1
def main():
threads = []
for i in range(10):
thread = threading.Thread(target=increment,args=(100,))
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
print("except value is 1000, real value is{}".format(num))
main()
有興趣的讀者可以試試把鎖去掉是什么結(jié)果,實際上谬哀,我們永遠得不到正確的結(jié)果刺覆。
對于這段代碼,我們可以這樣理解:
threads = []
for i in range(10):
thread = thread.Threading(target=increment, args=(100,))
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
第一個for循環(huán)史煎,意思是吩咐十個線程去做target里面的事谦屑,執(zhí)行完第一個for循環(huán)后就吩咐完了,但僅僅是只是吩咐完了,target里面的任務(wù)沒有執(zhí)行完篇梭,因為不知道是否執(zhí)行完氢橙,所以還需要創(chuàng)建一個列表把他們都保存起來。對于第二個for循環(huán)恬偷,如果在第一個循環(huán)里 他們的target已經(jīng)執(zhí)行完了悍手,那后面直接就join,不用等待袍患,遇到有的線程沒有執(zhí)行完的坦康,join 就阻塞調(diào)用,直到這些線程執(zhí)行完诡延。
線程安全隊列queue
隊列是線程間最常用的交換數(shù)據(jù)的形式滞欠,queue模塊實現(xiàn)了線程安全的隊列,有三種類型的隊列:
- queue Queue:FIFO(先進先出) 的隊列孕暇。最常用的隊列仑撞!
- queue LifoQueue: LIFO(后進先出)的隊列,最后加入隊列的元素最先取出
- queue PriorityQueue: 優(yōu)先級隊列妖滔,隊列中的元素根據(jù)優(yōu)先級排序隧哮。
下面是Queue類常用的方法:
- empty: 判斷隊列是否為空
- full: 判斷隊列是否已滿
- put: 向隊列中添加元素
- get: 從隊列中取出元素
- put_nowait: 非阻塞 向隊列中添加元素
- get_nowait: 非阻塞 從隊列中取出元素
- join:阻塞等待,直到所有任務(wù)完成
來看一個官方給的多線程模型:
def worker():
while True:
item = q.get()
do_work()
q.task_done()
q = Queue()
for i in range(thread_number):
t = Thread(target=worker)
t.daemon = True
t.start()
for item in source():
q.put(item)
q.join()
之后會有一個線程池的例子運用Queue隊列座舍。寫完后放鏈接沮翔!待續(xù)!