注:如果您能仔細的讀完本文後,您可能對多線程有更深刻的理解
相信大多數人對於多線程總是感到頭痛
於是今天我冒着頭痛的危險,詳細的分析了python中多線程在運行時候的流程
我們先上一個簡單的代碼(後面我們會用多線程來搞一個buffer緩存器)
我們首先定義兩個函數:m、n
def m():
print("m 開始工作")
time.sleep(10)
print("m 工作完畢")
def n():
print("n 開始工作")
time.sleep(5)
print("n 工作完畢")
如果直接調用這兩個方法
m()
n()
結果肯定是下面這個結果
m 開始工作
m 工作完畢
n 開始工作
n 工作完畢
當然,還有時間等待的部分。(這裏沒法顯示了)
如果我們將這個過程變成多線程的形式,打印的結果又是什麼呢?
這裏我們需要了解,如何定義多線程
首先我們需要導入線程的這個模塊import threading
其次,我們定義線程的方法,將方法的名字放入到線程的方法中
thread = threading.Thread(target=m)
thread1 = threading.Thread(target=n)
開啓線程
thread.start()
thread1.start()
使用join方法,讓主線程等待子線程結束後再執行主程序
thread.join()
thread1.join()
最後我們在主線程中添加一句話
print('after start()')
這樣多線程的代碼就調用完畢了
這時,我們在運行一下:
m 開始工作
n 開始工作
n 工作完畢
m 工作完畢
after start()
這個結果,顯而易見:
mn兩個進程同時開始工作,因爲n需要的時間比較少,所以先返回(可以理解先報告工作完畢了)
只有當m完成後,主程序才執行結束。
當然,如果不加thread.join()
的方法,主線程會直接完成,不會顧及子線程是否完成
可以舉個例子:
m和n是員工,主線程是老闆。
如果加上join方法,老闆是在監工,並且m和n兩個員工都做完,纔去喫飯
如果不加上join方法,老闆則是下發任務後,直接就去喫飯了,不會管m、n兩個員工是否完成任務
通過以上的步驟,我們大概的瞭解了什麼是多線程了
當然多線程還有一個很重要的概念就是鎖的概念
我們舉一個例子來說明一下
# -*-coding:utf-8-*-
import threading
import time
n = 0
lock = threading.Lock()
def add():
global n
for _ in range(400000):
n += 1
def sub():
global n
for _ in range(400000):
n -= 1
if __name__ == "__main__":
thread_add = threading.Thread(target=add)
thread_sub = threading.Thread(target=sub)
thread_add.start()
thread_sub.start()
thread_sub.join()
thread_add.join()
print("n = ", n, flush=True)
上面的代碼主要是這樣的:
使用多線程的方式進行兩個函數方法的同時使用,一個是不斷的給n加上1,一個是不斷的給n減去1
當然兩個循環的次數是相同的
大家不難猜測:既然給n加上四十萬個1,然後在對n減去四十萬個1,最終的結果一定是0呀。
好的,我們運行一下這個代碼:
然而現實總是殘酷的,打印的結果如下:
是什麼問題導致的這種原因的呢??
實際上是這樣的
我麼可以這樣理解,甲乙兩個人在一共工廠工作,甲的任務是對n進行加一,乙的任務是對n進行減一。
當然,在工作的位置中,甲看不到乙,乙也看不到甲
當甲取到n的值,並將n的值進行加一的操作後,將結果返回,如果在此之間乙已將n的值進行減一操作的話
那麼,數值將會被覆蓋掉。換成了甲加一之後的值,這樣,在這種不斷覆蓋的過程中,最終n的值當然不是0了
當然以上這是一個問題,但是該如何解決呢?我們引出了鎖的概念
首先鎖是如何定義的呢?
lock = threading.Lock()
同樣對於鎖,我們有兩種操作,上鎖和解鎖
lock.acquire() # 上鎖
lock.release() # 解鎖
如果學過操作系統的小夥伴,你一定直到PV操作,和這個簡直就是異曲同工
這時我們將上述程序進行鎖的操作(注意上鎖和解鎖的位置)
# -*-coding:utf-8-*-
import threading
import time
n = 0
lock = threading.Lock()
def add():
global n
for _ in range(400000):
lock.acquire()
n += 1
lock.release()
def sub():
global n
for _ in range(400000):
lock.acquire()
n -= 1
lock.release()
if __name__ == "__main__":
thread_add = threading.Thread(target=add)
thread_sub = threading.Thread(target=sub)
thread_add.start()
thread_sub.start()
thread_sub.join()
thread_add.join()
print("n = ", n, flush=True)
此時打印的結果爲:
裝上鎖後,進程之間的運行就是正常的了
這也就相當於,甲工作的時候開始上鎖,乙不能動,等到甲運行結束後,乙就開始工作了,從而程序就正常的運行了。