python3多线程是否需要加锁_Python中的多线程编程,线程安全与锁(一)

本文介绍了Python中的多线程编程和线程安全概念,探讨了GIL如何影响线程安全。通过示例展示了在多线程环境下,不加锁的线程不安全问题,并通过Threading.Lock实现了互斥锁,确保线程安全。还讨论了两种死锁情况——迭代死锁与互相等待死锁,以及如何通过递归锁(RLock)和锁的升序使用避免死锁。
摘要由CSDN通过智能技术生成

1. 多线程编程与线程安全相关重要概念

在我的上篇博文 聊聊Python中的GIL 中,我们熟悉了几个特别重要的概念:GIL,线程,进程, 线程安全,原子操作。

以下是简单回顾,详细介绍请直接看聊聊Python中的GIL

GIL:  Global Interpreter Lock,全局解释器锁。为了解决多线程之间数据完整性和状态同步的问题,设计为在任意时刻只有一个线程在解释器中运行。

线程:程序执行的最小单位。

进程:系统资源分配的最小单位。

线程安全:多线程环境中,共享数据同一时间只能有一个线程来操作。

原子操作:原子操作就是不会因为进程并发或者线程并发而导致被中断的操作。

还有一个重要的结论:当对全局资源存在写操作时,如果不能保证写入过程的原子性,会出现脏读脏写的情况,即线程不安全。Python的GIL只能保证原子操作的线程安全,因此在多线程编程时我们需要通过加锁来保证线程安全。

最简单的锁是互斥锁(同步锁),互斥锁是用来解决io密集型场景产生的计算错误,即目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据。

下面我们会来介绍如何使用互斥锁。

2. Threading.Lock实现互斥锁的简单示例

我们通过Threading.Lock()来实现锁。

以下是线程不安全的例子:

>>> importthreading>>> importtime>>> defsub1():globalcount

tmp=count

time.sleep(0.001)

count= tmp + 1time.sleep(2)>>> count =0>>> defverify(sub):globalcount

thread_list=[]for i in range(100):

t= threading.Thread(target=sub,args=())

t.start()

thread_list.append(t)for j inthread_list:

j.join()print(count)>>>verify(sub1)14

在这个例子中,我们把

count+=1

代替为

tmp =count

time.sleep(0.001)

count= tmp + 1

是因为,尽管count+=1是非原子操作,但是因为CPU执行的太快了,比较难以复现出多进程的非原子操作导致的进程不安全。经过代替之后,尽管只sleep了0.001秒,但是对于CPU的时间来说是非常长的,会导致这个代码块执行到一半,GIL锁就释放了。即tmp已经获取到count的值了,但是还没有将tmp + 1赋值给count。而此时其他线程如果执行完了count = tmp + 1, 当返回到原来的线程执行时,尽管count的值已经更新了,但是count = tmp + 1是个赋值操作,赋值的结果跟count的更新的值是一样的。最终导致了我们累加的值有很多丢失。

下面是线程安全的例子,我们可以用threading.Lock()获得锁

>>> count =0>>> defsub2():globalcountif lock.acquire(1):

#acquire()是获取锁,acquire(1)返回获取锁的结果,成功获取到互斥锁为True,如果没有获取到互斥锁则返回False

tmp=count

time.sleep(0.001)

count= tmp + 1time.sleep(2)

lock.release() 一系列操作结束之后需要释放锁>>> defver

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值