1 import threading 2 3 4 # 全局变量 5 g_num = 0 6 7 8 # 对g_num进行加操作 9 def sum_num1(): 10 # 上锁 11 print("sun_num1...") 12 print(f"sum_num1 id(mutex) {id(mutex)}") 13 mutex.acquire() 14 15 for i in range(1000000): 16 global g_num 17 g_num += 1 18 19 print("g_num1:", g_num) 20 21 22 # 对g_num进行加操作 23 def sum_num2(): 24 # 上锁 25 print("sun_num2...") 26 print(f"sum_num2 id(mutex) {id(mutex)}") 27 mutex.acquire() # 没有释放 28 29 for i in range(1000000): 30 global g_num 31 g_num += 1 32 33 print("g_num2:", g_num) 34 35 36 if __name__ == '__main__': 37 # 创建锁 38 mutex = threading.Lock() # 全局变量 39 print(id(mutex)) 40 # 创建子线程 41 sum1_thread = threading.Thread(target=sum_num1) 42 sum2_thread = threading.Thread(target=sum_num2) 43 44 # 启动线程 45 sum1_thread.start() 46 sum2_thread.start()
输出:
2590825403712
sun_num1...
sum_num1 id(mutex) 2590825403712
sun_num2...
sum_num2 id(mutex) 2590825403712
g_num1: 1000000
从输出结果上看是同一把锁。锁主了。
2个线程都争取上锁(只能1个成功),成功后没有释放,导致另外一个线程一直等待(拿到锁的线程已经执行完了还是在等待)
总结
死锁发生的条件通常包括以下四个必要条件:
- 互斥条件(Mutual Exclusion):一个资源同时只能被一个线程或进程占用。
- 请求和保持条件(Hold and Wait):一个线程或进程在持有某个资源的同时,还请求其他资源。
- 不可剥夺条件(No Preemption):一个资源不能被强制从一个线程或进程中释放,只能由持有该资源的线程或进程主动释放。
- 循环等待条件(Circular Wait):多个线程或进程形成一个循环等待其他线程或进程所持有的资源。
避免死锁的最佳实践包括:
- 避免使用过多的锁。尽量简化并发代码,减少需要加锁的地方。
- 使用适当的锁粒度。将锁的范围控制在最小必要范围内,以降低死锁的概率。
- 避免嵌套锁。如果一个线程已经持有了一个锁,在持有的锁范围内尽量不要再去请求其他锁。
- 使用定时锁(Timeout Locks)。在加锁时设置超时时间,避免线程永久等待锁的释放。
- 使用死锁检测和恢复机制。可以周期性地检测死锁的发生,并采取相应的措施进行恢复,例如强制释放某些资源或回滚操作。