美文网首页程序员
互斥锁解决 Python 中多线程共享全局变量的问题

互斥锁解决 Python 中多线程共享全局变量的问题

作者: 蒸熟的土豆 | 来源:发表于2020-09-28 14:35 被阅读0次

    一、同步概念

    同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。

    "同"字从字面上容易理解为一起动作。

    其实不是,在这里,"同"字应是指协同、协助、互相配合。

    线程同步,可理解为线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B执行,再将结果给A;A再继续操作。

    之前我们遇到过,如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。

    解决线程同时修改全局变量的方式

    我们先把上次那个问题再看下。

    importthreadingimporttimeg_num =0defwork1(num):globalg_numforiinrange(num):        g_num +=1print("----in work1, g_num is %d---"% g_num)defwork2(num):globalg_numforiinrange(num):        g_num +=1print("----in work2, g_num is %d---"% g_num)print("---线程创建之前g_num is %d---"% g_num)t1 = threading.Thread(target=work1, args=(1000000,))t1.start()t2 = threading.Thread(target=work2, args=(1000000,))t2.start()# 确保子线程都运行结束whilelen(threading.enumerate()) !=1:    time.sleep(1)print("2个线程对同一个全局变量操作之后的最终结果是:%s"% g_num)

    运行结果:

    ---线程创建之前g_numis0-------inwork2,g_numis1048576-------inwork1,g_numis1155200---2个线程对同一个全局变量操作之后的最终结果是:1155200

    对于这个计算错误的问题,可以通过线程同步来进行解决。

    思路,如下:

    系统调用 t1,然后获取到 g_num 的值为0,此时上一把锁,即不允许其他线程操作 g_num。

    t1 对 g_num 的值进行+1。

    t1 解锁,此时 g_num 的值为1,其他的线程就可以使用 g_num 了,而且 g_num 的值不是0而是1。

    同理其他线程在对 g_num 进行修改时,都要先上锁,处理完后再解锁,在上锁的整个过程中不允许其他线程访问,就保证了数据的正确性。

    思路基本是这个样子,那代码怎么来实现呢?

    二、互斥锁解决资源竞争的问题

    当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制。

    线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制就是引入互斥锁。

    互斥锁为资源引入一个状态:锁定/非锁定。

    某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。

    互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

    threading 模块中定义了 Lock 类,可以方便的处理锁定:

    # 创建锁mutex = threading.Lock()# 锁定mutex.acquire()# 释放mutex.release()

    注意:

    如果这个锁之前是没有上锁的,那么 acquire 不会堵塞。

    如果在调用 acquire 对这个锁上锁之前,它已经被其他线程上了锁,那么此时 acquire 会堵塞,直到这个锁被解锁为止。

    示例:

    使用互斥锁完成2个线程对同一个全局变量各加100万次的操作。

    importthreadingimporttimeg_num =0deftest1(num):globalg_numforiinrange(num):        mutex.acquire()# 上锁g_num +=1mutex.release()# 解锁print("---test1---g_num=%d"% g_num)deftest2(num):globalg_numforiinrange(num):        mutex.acquire()# 上锁g_num +=1mutex.release()# 解锁print("---test2---g_num=%d"% g_num)# 创建一个互斥锁# 默认是未上锁的状态mutex = threading.Lock()# 创建2个线程,让他们各自对g_num加1000000次p1 = threading.Thread(target=test1, args=(1000000,))p1.start()p2 = threading.Thread(target=test2, args=(1000000,))p2.start()# 等待计算完成whilelen(threading.enumerate()) !=1:    time.sleep(1)print("2个线程对同一个全局变量操作之后的最终结果是:%s"% g_num)

    运行结果:

    ---test1---g_num=1989108

    ---test2---g_num=2000000

    2个线程对同一个全局变量操作之后的最终结果是:2000000

    可以看到最后的结果,加入互斥锁后,其结果与预期相符。

    记住,上锁的代码范围要越小越好。在业务逻辑正确的前提下,能锁一行代码,就不要锁两行。

    上锁解锁过程

    当一个线程调用锁的 acquire() 方法获得锁时,锁就进入“locked”状态。

    每次只有一个线程可以获得锁。

    如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调用锁的 release() 方法释放锁之后,锁进入“unlocked”状态。

    线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。

    总结

    锁的好处:

    确保了某段关键代码只能由一个线程从头到尾完整地执行。

    锁的坏处:

    阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。

    由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁。

    最后,小编想说:我是一名python开发工程师, 整理了一套最新的python系统学习教程, 想要这些资料的可以关注私信小编“01”即可(免费分享哦)希望能对你有所帮助。

    相关文章

      网友评论

        本文标题:互斥锁解决 Python 中多线程共享全局变量的问题

        本文链接:https://www.haomeiwen.com/subject/yjvduktx.html