python同步、互斥鎖、死鎖


同步

同步的概念

同步就是協同步調,按預定的先后次序進行運行。如:你說完,我再說。"同"字從字面上容易理解為一起動作,其實不是,"同"字應是指協同、協助、互相配合。如進程、線程同步,可理解為進程或線程A和B一塊配合,A執行到一定程度時要依靠B的某個結果,於是停下來,示意B運行;B執行,再將結果給A;A再繼續操作。

解決線程同時修改全局變量的方式

對於上一篇中(多線程共享全局變量)的那個計算錯誤的問題,可以通過線程同步來進行解決。

思路,如下:

  1. 系統調用t1,然后獲取到g_num的值為0,此時上一把鎖,即不允許其他線程操作g_num
  2. t1對g_num的值進行+1
  3. t1解鎖,此時g_num的值為1,其他的線程就可以使用g_num了,而且是g_num的值不是0而是1
  4. 同理其他線程在對g_num進行修改時,都要先上鎖,處理完后再解鎖,在上鎖的整個過程中不允許其他線程訪問,就保證了數據的正確性

互斥鎖

當多個線程幾乎同時修改某一個共享數據的時候,需要進行同步控制。線程同步能夠保證多個線程安全訪問競爭資源,最簡單的同步機制是引入互斥鎖。

互斥鎖為資源引入一個狀態:鎖定/非鎖定

某個線程要更改共享數據時,先將其鎖定,此時資源的狀態為“鎖定”,其他線程不能更改;直到該線程釋放資源,將資源的狀態變成“非鎖定”,其他的線程才能再次鎖定該資源。互斥鎖保證了每次只有一個線程進行寫入操作,從而保證了多線程情況下數據的正確性。

threading模塊中定義了Lock類,可以方便的處理鎖定:

# 創建鎖
mutex = threading.Lock()

# 鎖定
mutex.acquire()

# 釋放
mutex.release()

注意:

  • 如果這個鎖之前是沒有上鎖的,那么acquire不會堵塞
  • 如果在調用acquire對這個鎖上鎖之前 它已經被 其他線程上了鎖,那么此時acquire會堵塞,直到這個鎖被解鎖為止

使用互斥鎖完成2個線程對同一個全局變量各加9999999 次的操作

import threading
import time


g_num = 0 
# 創建一個互斥鎖,默認為未上鎖zhuangtai
mutex = threading.Lock()

def test1(num):
        global g_num
    
        for i in range(num):
    
                mutex.acquire()
                g_num += 1
                mutex.release()
        print("--test1, g_num = %d--" % g_num)


def test2(num):
        global g_num
    
        for i in range(num):
    
                mutex.acquire()
                g_num += 1
                mutex.release()
    
        print("--test2, g_num = %d--" % g_num)


if __name__ == "__main__":
    
        print("--創建線程之前, g_num = %d--" % g_num)

        t1 = threading.Thread(target=test1, args=(9999999,))
        t1.start()

        t2 = threading.Thread(target=test2, args=(9999999,))
        t2.start()
    
        while len(threading.enumerate()) != 1:
                time.sleep(1)
    
        print("最終結果為:g_num=%d" % g_num)

運行結果:

--創建線程之前, g_num = 0--
--test1, g_num = 19580251--
--test2, g_num = 19999998--
最終結果為:g_num=19999998

可以看到最后的結果,加入互斥鎖后,其結果與預期相符。

上鎖解鎖過程

當一個線程調用鎖的acquire()方法獲得鎖時,鎖就進入“locked”狀態。

每次只有一個線程可以獲得鎖。如果此時另一個線程試圖獲得這個鎖,該線程就會變為“blocked”狀態,稱為“阻塞”,直到擁有鎖的線程調用鎖的release()方法釋放鎖之后,鎖進入“unlocked”狀態。

線程調度程序從處於同步阻塞狀態的線程中選擇一個來獲得鎖,並使得該線程進入運行(running)狀態。

總結

鎖的好處:

  • 確保了某段關鍵代碼只能由一個線程從頭到尾完整地執行

鎖的壞處:

  • 阻止了多線程並發執行,包含鎖的某段代碼實際上只能以單線程模式執行,效率就大大地下降了
  • 由於可以存在多個鎖,不同的線程持有不同的鎖,並試圖獲取對方持有的鎖時,可能會造成死鎖

死鎖

在線程間共享多個資源的時候,如果兩個線程分別占有一部分資源並且同時等待對方的資源,就會造成死鎖。

盡管死鎖很少發生,但一旦發生就會造成應用的停止響應。下面看一個死鎖的例子

import threading
import time


class MyThread1(threading.Thread):

	def run(self):
		
		# 對mutex1上鎖
		mutex1.acquire()
		
		# mutex1上鎖后,延遲1秒,等待另外一個線程,把mutex2上鎖
		print(self.name + " up")
		time.sleep(1)

		# 此時這里會堵塞,因為mutex2已經被另外的線程搶先上鎖了
		mutex2.acquire()
		print(self.name + " down")
		mutex2.release()
		
		# 對mutex1解鎖
		mutex1.release()
		

class MyThread2(threading.Thread):

	def run(self):
		
		# 對mutex2上鎖
		mutex2.acquire()
		
		# mutex2上鎖后,延遲1秒,等待另外一個線程,把mutex1上鎖
		print(self.name + " up")
		time.sleep(1)

		# 此時這里會堵塞,因為mutex1已經被另外的線程搶先上鎖了
		mutex1.acquire()
		print(self.name + " down")
		mutex1.release()
		
		# 對mutex2解鎖
		mutex2.release()


mutex1 = threading.Lock()
mutex2 = threading.Lock()

if __name__ == "__main__":
	
	t1 = MyThread1()
	t1.start()

	t2 = MyThread2()
	t2.start()

運行結果:

Thread-1 up
Thread-2 up

此時已經進入到了死鎖狀態,可以使用ctrl-c退出

避免死鎖

  • 程序設計時要盡量避免(銀行家算法)
  • 添加超時時間等


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM