python 深度探究線程和線程鎖的概念

注:如果您能仔細的讀完本文後,您可能對多線程有更深刻的理解


相信大多數人對於多線程總是感到頭痛

於是今天我冒着頭痛的危險,詳細的分析了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)

此時打印的結果爲:
在這裏插入圖片描述
裝上鎖後,進程之間的運行就是正常的了

這也就相當於,甲工作的時候開始上鎖,乙不能動,等到甲運行結束後,乙就開始工作了,從而程序就正常的運行了。

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