python互斥锁和死锁

同步的概念

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

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

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

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

解决线程同时修改全局变量的方式
对于上一小节提出的那个计算错误的问题,可以通过线程同步来进行解决

思路,如下:
系统调用t1,然后获取到g_num的值为0,此时上一把锁,即不允许其他线程操作g_num
t1对g_num的值进行+1
t1解锁,此时g_num的值为1,其他的线程就可以使用g_num了,而且是g_num的值不是0而是1
同理其他线程在对g_num进行修改时,都要先上锁,处理完后再解锁,在上锁的整个过程中不允许其他线程访问,就保证了数据的正确性

互斥锁

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

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

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

某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
threading模块中定义了Lock类,可以方便的处理锁定:

lock = threading.Lock() #创建锁

lock.acquire() #上锁

lock.release() #解锁

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

使用互斥锁自加:

from threading import Thread
from threading import Lock
import time
g_num = 0
# 创建一个全局锁对象
glock = Lock()
def work1(num):
    global g_num
    glock.acquire()# 给该线程加锁
    for i in range(num):
        g_num+=1
    glock.release()#解锁
    print("in work1-->",g_num)

def work2(num):
    global g_num
    glock.acquire()
    for i in range(num):
        g_num+=1
    glock.release()
    print("in work2-->",g_num)

def main():
    t1 = Thread(target=work1,args=(1000000,))
    t2 = Thread(target=work2,args=(1000000,))
    t1.start()
    t2.start()
    t2.join()


if __name__ == '__main__':
    main()
    print("main in-->",g_num)

运行结果:

in work1--> 1000000
in work2--> 2000000
main in--> 2000000

死锁

在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。
尽管死锁很少发生,但一旦发生就会造成应用的停止响应。下面看一个死锁的例子
简单来说就是:两个线程占用一个资源,互不相让,导致程序堵塞

from threading import Thread
from threading import Lock
import time
lock1 = Lock()# 创建第一个锁
lock2 = Lock()# 创建第二个锁
def work1(num):
   lock1.acquire() #lock1上锁
   time.sleep(1) # 等待一秒钟
   print("in work1")
   lock2.acquire()# lock2上锁
   print("work1----->")
   lock2.release()# 解锁lock2
   lock1.release() # 解锁lock1

def work2(num):
    lock2.acquire() # 给lock2上锁
    print("in work2")
    lock1.acquire()  # 给lock1上锁
    print("work2---->")
    lock1.release() # 进行解锁
    lock2.release()

if __name__ == '__main__':
    t1 = Thread(target=work1,args=(100000,)) # 创建线程t1
    t2 = Thread(target=work2,args=(100000,))# 创建线程t2
    t1.start() # 启动线程
    t2.start()

运行结果:
在这里插入图片描述

避免死锁
程序设计时要尽量避免(银行家算法)
添加超时时间等
附录-银行家算法
[背景知识]
一个银行家如何将一定数目的资金安全地借给若干个客户,使这些客户既能借到钱完成要干的事,同时银行家又能收回全部资金而不至于破产,这就是银行家问题。这个问题同操作系统中资源分配问题十分相似:银行家就像一个操作系统,客户就像运行的进程,银行家的资金就是系统的资源。

[问题的描述]

一个银行家拥有一定数量的资金,有若干个客户要贷款。每个客户须在一开始就声明他所需贷款的总额。若该客户贷款总额不超过银行家的资金总数,银行家可以接收客户的要求。客户贷款是以每次一个资金单位(如1万RMB等)的方式进行的,客户在借满所需的全部单位款额之前可能会等待,但银行家须保证这种等待是有限的,可完成的。

例如:有三个客户C1,C2,C3,向银行家借款,该银行家的资金总额为10个资金单位,其中C1客户要借9各资金单位,C2客户要借3个资金单位,C3客户要借8个资金单位,总计20个资金单位。某一时刻的状态如图所示。

在这里插入图片描述
对于a图的状态,按照安全序列的要求,我们选的第一个客户应满足该客户所需的贷款小于等于银行家当前所剩余的钱款,可以看出只有C2客户能被满足:C2客户需1个资金单位,小银行家手中的2个资金单位,于是银行家把1个资金单位借给C2客户,使之完成工作并归还所借的3个资金单位的钱,进入b图。同理,银行家把4个资金单位借给C3客户,使其完成工作,在c图中,只剩一个客户C1,它需7个资金单位,这时银行家有8个资金单位,所以C1也能顺利借到钱并完成工作。最后(见图d)银行家收回全部10个资金单位,保证不赔本。那麽客户序列{C1,C2,C3}就是个安全序列,按照这个序列贷款,银行家才是安全的。否则的话,若在图b状态时,银行家把手中的4个资金单位借给了C1,则出现不安全状态:这时C1,C3均不能完成工作,而银行家手中又没有钱了,系统陷入僵持局面,银行家也不能收回投资。

综上所述,银行家算法是从当前状态出发,逐个按安全序列检查各客户谁能完成其工作,然后假定其完成工作且归还全部贷款,再进而检查下一个能完成工作的客户,…。如果所有客户都能完成工作,则找到一个安全序列,银行家才是安全的。

<think>好的,用户想了解Python中的互斥锁用法实现。首先,我需要回忆一下Python中的线程同步机制。互斥锁,也就是Lock,是用来防止多个线程同时访问共享资源,避免竞态条件的。 首先,我应该从threading模块入手,因为Python的标准库中处理线程的就是这个模块。Lock对象的基本用法是acquire()release(),或者使用with语句来自动管理锁的获取释放。这部分需要详细说明,最好给出代码示例,比如一个简单的计数器例子,展示没有锁的情况下会出现问题,加上锁后问题解决。 然后,关于实现部分,可能需要解释Lock的内部机制。虽然Python的GIL(全局解释器锁)已经确保了同一时间只有一个线程执行字节码,但在涉及IO操作或者如numpy这样的扩展库时,线程仍然可能被切换,因此需要显式的锁。不过用户可能更关心的是如何正确使用Lock,而不是底层实现细节,但简要说明实现原理也是有必要的,比如基于操作系统提供的原语,如pthread的mutex。 接下来,用户可能有更深层次的需求,比如如何避免死锁,或者锁的高级用法。这时候需要提到with语句的好处,因为它能确保锁被正确释放,即使在异常情况下。另外,可能还需要比较RLock(可重入锁)普通Lock的区别,但用户的问题集中在mutex lock,所以可能先不展开,除非相关问题中提到。 用户提供的引用中提到了mutex的所有权问题,只有所有者能释放锁。在Python中,Lock确实遵循这一点,所以在线程中必须由同一个线程来获取释放。这部分需要强调,避免用户误用。 最后,生成相关问题时要考虑用户可能的后续疑问,比如死锁如何避免,LockRLock的区别,线程间通信的其他机制如Condition、Semaphore等,或者性能方面的考虑。确保问题覆盖常见的使用场景潜在问题。</think>### Python互斥锁(Mutex Lock)的实现与用法 #### 一、基础用法 Python通过`threading`模块的`Lock`类实现互斥锁,主要用于保护共享资源。以下是典型使用场景: ```python import threading counter = 0 lock = threading.Lock() def increment(): global counter for _ in range(100000): with lock: # 自动获取/释放锁 counter += 1 t1 = threading.Thread(target=increment) t2 = threading.Thread(target=increment) t1.start(); t2.start() t1.join(); t2.join() print(counter) # 保证输出200000 ``` #### 二、底层实现原理 1. **操作系统级同步**:Python的线程锁最终基于操作系统原语实现(如Linux的pthread_mutex_t),确保原子性操作[^2] 2. **GIL关系**:在CPython中,全局解释器锁(GIL)的存在使单个Python进程无法并行执行字节码,但I/O操作C扩展模块仍可能引发竞态条件 3. **锁状态管理**:内部通过`_is_owned()`方法跟踪锁状态,保证同一线程不能重复获取未释放的锁 #### 三、关键特性对比 | 特性 | 普通Lock | RLock | |-----------|---------|---------| | 可重入性 | ❌ | ✅ | | 释放锁要求 | 任意线程 | 获取线程 | | 性能消耗 | 低 | 稍高 | | 适用场景 | 简单同步 | 嵌套调用 | #### 四、最佳实践 1. 优先使用`with`语句自动管理锁 2. 避免在持锁期间执行阻塞操作 3. 设置获取锁的超时时间(`lock.acquire(timeout=2)`) 4. 使用调试工具检测死锁(如`threading.enumerate()`) ```python # 带超时的安全获取方式 if lock.acquire(timeout=1): try: # 临界区操作 finally: lock.release() else: print("获取锁超时") ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值