温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

python 线程互斥锁Lock(29)

发布时间:2020-06-29 10:55:38 来源:网络 阅读:184 作者:qq5d6f345f0205e 栏目:编程语言

在前一篇文章 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函数,每次输出的结果还不同!!

 

python 线程互斥锁Lock(29)

 

一.线程共享全局变量

分析下上面的代码:两个线程共享全局变量并执行for循环1000000,每次自动加1,我们都知道两个线程都是同时在运行,也就是说两个线程同时在执行 g_num = g_num + 1 操作, 经过我们冷静分析一波,貌似结果还是应该等于2000000,对不对?

 

python 线程互斥锁Lock(29)

 

首先,我们将上面全局变量自动加 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,误差就由此产生,往往循环次数越多,产生的误差就越大。

 

python 线程互斥锁Lock(29)

 

二.线程互斥锁

为了避免上述问题,我们可以利用线程互斥锁解决这个问题。那么互斥锁到底是个什么原理呢?互斥锁就好比排队上厕所,一个坑位只能蹲一个人,只有占用坑位的人完事了,另外一个人才能上!

python 线程互斥锁Lock(29)

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 


向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI