python 線程互斥鎖Lock(29)

在前一篇文章 python線程創建和傳參 中我們介紹了關於python線程的一些簡單函數使用和線程的參數傳遞,使用多線程可以同時執行多個任務,提高開發效率,但是在實際開發中往往我們會碰到線程同步問題,假如有這樣一個場景:對全局變量累加1000000次,爲了提高效率,我們可以使用多線程完成,示例代碼如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

# !usr/bin/env python

# -*- coding:utf-8 _*-

"""

@Author:何以解憂

@Blog(個人博客地址): shuopython.com

@WeChat Official Account(微信公衆號):猿說python

@Github:www.github.com

@File:python_thread_lock.py

@Time:2019/10/17 21:22

 

@Motto:不積跬步無以至千里,不積小流無以成江海,程序人生的精彩需要堅持不懈地積累!

"""

# 導入線程threading模塊

import threading

 

# 聲明全局變量

g_num = 0

 

def my_thread1():

 

    # 聲明全局變量

    global g_num

    # 循環 1000000 次,每次累計加 1

    for i in range(0,1000000):

        g_num = g_num + 1

 

def my_thread2():

 

    # 聲明全局變量

    global g_num

    # 循環 1000000 次,每次累計加 1

    for i in range(0,1000000):

        g_num = g_num + 1

 

def main(i):

 

    # 聲明全局變量

    global g_num

    # 初始化全局變量,初始值爲 0

    g_num = 0

    # 創建兩個線程,對全局變量進行累計加 1

    t1 = threading.Thread(target=my_thread1)

    t2 = threading.Thread(target=my_thread2)

 

    # 啓動線程

    t1.start()

    t2.start()

    # 阻塞函數,等待線程結束

    t1.join()

    t2.join()

    # 獲取全局變量的值

    print("第%d次計算結果:%d "% (i,g_num))

 

if __name__ == "__main__":

 

    # 循環4次,調用main函數,計算全局變量的值

    for i in range(1,5):

        main(i)

輸出結果:

1

2

3

4

1次計算結果:1262996

2次計算結果:1661455

3次計算結果:1300211

4次計算結果:1563699

what ? 這是什麼操作??看着代碼好像也沒問題,兩個線程,各自累加1000000次,不應該輸出是2000000次嗎?而且調用了4次main函數,每次輸出的結果還不同!!

 

蹲坑

 

一.線程共享全局變量

分析下上面的代碼:兩個線程共享全局變量並執行for循環1000000,每次自動加1,我們都知道兩個線程都是同時在運行,也就是說兩個線程同時在執行 g_num = g_num + 1 操作, 經過我們冷靜分析一波,貌似結果還是應該等於2000000,對不對?

 

扎心

 

首先,我們將上面全局變量自動加 1 的代碼分爲兩步:

1

2

第一步:g_num + 1

第二步:將 g_num + 1 的結果賦值給 g_num

由此可見,執行一個完整的自動加1過程需要兩步,然而線程卻是在同時運行,誰也不能保證線程1的第一步和第二步執行完成之後才執行線程2的第一步和第二步,執行的過程充滿隨機性,這就是導致每次計算結果不同的原因所在!

舉個簡單的例子:

假如當前 g_num 值是100,當線程1執行第一步時,cpu通過計算獲得結果101,並準備把計算的結果101賦值給g_num,然後再傳值的過程中,線程2突然開始執行了並且執行了第一步,此時g_num的值仍未100,101還在傳遞的過程中,還沒成功賦值,線程2獲得計算結果101,並準備傳遞給g_num,經過一來一去這麼一折騰,分明做了兩次加 1 操作,g_num結果卻是101,誤差就由此產生,往往循環次數越多,產生的誤差就越大。

 

窒息

 

二.線程互斥鎖

爲了避免上述問題,我們可以利用線程互斥鎖解決這個問題。那麼互斥鎖到底是個什麼原理呢?互斥鎖就好比排隊上廁所,一個坑位只能蹲一個人,只有佔用坑位的人完事了,另外一個人才能上!

上廁所

1.創建互斥鎖

導入線程模塊,通過 threading.Lock() 創建互斥鎖.

1

2

3

4

5

# 導入線程threading模塊

import threading

 

# 創建互斥鎖

mutex = threading.Lock()

 

2.鎖定資源/解鎖資源

acquire() — 鎖定資源,此時資源是鎖定狀態,其他線程無法修改鎖定的資源,直到等待鎖定的資源釋放之後才能操作;

release() — 釋放資源,也稱爲解鎖操作,對鎖定的資源解鎖,解鎖之後其他線程可以對資源正常操作;

 

以上面的代碼爲列子:想得到正確的結果,可以直接利用互斥鎖在全局變量 加1 之前 鎖定資源,然後在計算完成之後釋放資源,這樣就是一個完整的計算過程,至於應該是哪個線程先執行,無所謂,先到先得,憑本事說話….演示代碼如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

# !usr/bin/env python

# -*- coding:utf-8 _*-

"""

@Author:何以解憂

@Blog(個人博客地址): shuopython.com

@WeChat Official Account(微信公衆號):猿說python

@Github:www.github.com

@File:python_thread_lock.py

@Time:2019/10/18 21:22

 

@Motto:不積跬步無以至千里,不積小流無以成江海,程序人生的精彩需要堅持不懈地積累!

"""

# 導入線程threading模塊

import threading

 

# 聲明全局變量

g_num = 0

# 創建互斥鎖

mutex = threading.Lock()

 

def my_thread1():

 

    # 聲明全局變量

    global g_num

    # 循環 1000000 次,每次累計加 1

    for i in range(0,1000000):

        # 鎖定資源

        mutex.acquire()

        g_num = g_num + 1

        # 解鎖資源

        mutex.release()

 

def my_thread2():

 

    # 聲明全局變量

    global g_num

    # 循環 1000000 次,每次累計加 1

    for i in range(0,1000000):

        # 鎖定資源

        mutex.acquire()

        g_num = g_num + 1

        # 解鎖資源

        mutex.release()

 

def main(i):

 

    # 聲明全局變量

    global g_num

    # 初始化全局變量,初始值爲 0

    g_num = 0

    # 創建兩個線程,對全局變量進行累計加 1

    t1 = threading.Thread(target=my_thread1)

    t2 = threading.Thread(target=my_thread2)

 

    # 啓動線程

    t1.start()

    t2.start()

    # 阻塞函數,等待線程結束

    t1.join()

    t2.join()

    # 獲取全局變量的值

    print("第%d次計算結果:%d "% (i,g_num))

 

if __name__ == "__main__":

 

    # 循環4次,調用main函數,計算全局變量的值

    for i in range(1,5):

        main(i)

輸出結果:

1

2

3

4

1次計算結果:2000000

2次計算結果:2000000

3次計算結果:2000000

4次計算結果:2000000

由此可見,全局變量計算加上互斥鎖之後,不論執行多少次,計算結果都相同。注意:互斥鎖一旦鎖定之後要記得解鎖,否則資源會一直處於鎖定狀態

 

三.線程死鎖

1.單個互斥鎖的死鎖:acquire()/release() 是成對出現的,互斥鎖對資源鎖定之後就一定要解鎖,否則資源會一直處於鎖定狀態,其他線程無法修改;就好比上面的代碼,任何一個線程沒有釋放資源release(),程序就會一直處於阻塞狀態(在等待資源被釋放),不信你可以試一試~

2.多個互斥鎖的死鎖:在同時操作多個互斥鎖的時候一定要格外小心,因爲一不小心就容易進入死循環,假如有這樣一個場景:boss讓程序員一實現功能一的開發,讓程序員二實現功能二的開發,功能開發完成之後一起整合代碼!

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

# 導入線程threading模塊

import threading

# 導入線程time模塊

import time

 

 

# 創建互斥鎖

mutex_one = threading.Lock()

mutex_two = threading.Lock()

 

def programmer_thread1():

 

    mutex_one.acquire()

    print("我是程序員1,module1開發正式開始,誰也別動我的代碼")

    time.sleep(2)

 

    # 此時會堵塞,因爲這個mutex_two已經被線程programmer_thread2搶先上鎖了,等待解鎖

    mutex_two.acquire()

    print("等待程序員2通知我合併代碼")

    mutex_two.release()

 

    mutex_one.release()

 

def programmer_thread2():

    mutex_two.acquire()

    print("我是程序員2,module2開發正式開始,誰也別動我的代碼")

    time.sleep(2)

 

    # 此時會堵塞,因爲這個mutex_one已經被線程programmer_thread1搶先上鎖了,等待解鎖

    mutex_one.acquire()

    print("等待程序員1通知我合併代碼")

    mutex_one.release()

 

    mutex_two.release()

 

def main():

 

    t1 = threading.Thread(target=programmer_thread1)

    t2 = threading.Thread(target=programmer_thread2)

 

    # 啓動線程

    t1.start()

    t2.start()

    # 阻塞函數,等待線程結束

    t1.join()

    t2.join()

    # 整合代碼結束

    print("整合代碼結束 ")

 

if __name__ == "__main__":

 

    main()

輸出結果:

1

2

我是程序員1module1開發正式開始,誰也別動我的代碼

我是程序員2module2開發正式開始,誰也別動我的代碼

分析下上面代碼:程序員1在等程序員2通知,程序員2在等程序員1通知,兩個線程都陷入阻塞中,因爲兩個線程都在等待對方解鎖,這就是死鎖!所以在開發中對於死鎖的問題還是需要多多注意!

 

四.重點總結

1.線程與線程之間共享全局變量需要設置互斥鎖;

2.注意在互斥鎖操作中 acquire()/release() 成對出現,避免造成死鎖;

 

 

猜你喜歡:

1.python線程創建和傳參

2.python函數-缺省參數

3.python局部變量和全局變量

 

轉載請註明猿說Python » Python線程互斥鎖Lock 


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