一、互斥锁
一个进程中的某些资源是可以由多个线程共享的,因为多个线程是并发进行的,且程序执行时会先被转化为原子操作,多个线程可能在同一时间段内都需要使用共享资源,会形成竞态,如果没有预先对应的同步机制,最终导致运行结果错误。
二、示例说明
-
from threading import Thread total = 0 def add(): global total for i in range(1000000): total += 1 def desc(): global total for i in range(1000000): total -= 1 if __name__ == '__main__': thread_add = Thread(target=add) thread_desc = Thread(target=desc) thread_add.start() thread_desc.start() thread_add.join() thread_desc.join() print(total) """ 第一次运行结果: -513053 Process finished with exit code 0 """ """ 第二次运行结果: 57426 Process finished with exit code 0 """ """ 对比两次运行结果,两次结果不同。 原因分析:首先 += 和 -=并不是原子操作,其次python在同一时刻只能有一个线程运行,线程的切换规则中有一 条,当某个线程执行了一定行数的字节码时解释器就会对线程进行切换。 (1)a += 1 原子操作过程: i. load a ii. load 1 iii. + iv. 赋值给a a -= 1 原子操作过程: i. load a ii. load 1 iii. - iv. 赋值给a (2)假设刚开始a=0,且假设执行一行字节码就进行线程切换则两个线程的执行过程如下: 线程 原子操作 结果 += i. load a 0 (a) -= i. load a 0 (a) += ii. load 1 1 (1) -= ii. load 1 1 (1) += iii. + -= iii. - += iv. 赋值给a 1 (a) -= iv. 赋值给a -1 (a) 切片的过程导致a+=1或a-=1的过程丢失,使得最终的结果是a=1或-1,而不是0。 """
-
使用锁机制来解决以上问题
from threading import Thread, Lock total = 0 lock_Lock = Lock() def add(): global total global lock_Lock for i in range(1000000): lock_Lock.acquire() total += 1 lock_Lock.release() def desc(): global total global lock_Lock for i in range(1000000): lock_Lock.acquire() total -= 1 lock_Lock.release() if __name__ == '__main__': thread_add = Thread(target=add) thread_desc = Thread(target=desc) thread_add.start() thread_desc.start() thread_add.join() thread_desc.join() print(total) """ 第一次运行结果: 0 Process finished with exit code 0 """ """ 第二次运行结果: 0 Process finished with exit code 0 """ """ 两次运行结果相同;使用锁机制的缺点,运行速度降低,原因,加锁与释放锁都需要耗费时间。 """
三、死锁
死锁几种情况示例:
1. 相互引用死锁
两个线程对a、b两个共享变量竞争造成死锁
import threading
import time
a = 0
b = 0
a_lock = threading.Lock()
b_lock = threading.Lock()
def A():
print("A已启动")
global a, b
if a_lock.acquire():
try:
print("A_a")
time.sleep(1)
if b_lock.acquire():
try:
print("A_b")
finally:
b_lock.release()
finally:
a_lock.release()
def B():
print("B已启动")
global a, b
if b_lock.acquire():
try:
print("B_b")
if a_lock.acquire():
try:
print("B_a")
finally:
a_lock.release()
finally:
b_lock.release()
threads = [
threading.Thread(target=A ),
threading.Thread(target=B )
]
[t.start() for t in threads]
[t.join() for t in threads]
print(a, b)
"""
执行结果:
A已启动
B已启动
A_a
B_b
"""
2. 某个线程在加锁后运行出错,但在错误处理时没有释放锁,导致另一个线程无法进行加锁操作,造成死锁
import threading
import time
a = 0
a_lock = threading.Lock()
def A():
print("A已启动")
global a
if a_lock.acquire():
try:
print("A_a")
a = 2 / 0
a_lock.release()
except Exception as e:
pass
finally:
pass
def B():
print("B已启动")
global a
if a_lock.acquire():
try:
print("B_a")
a = 2 / 1
finally:
a_lock.release()
threads = [
threading.Thread(target=A ),
threading.Thread(target=B )
]
[t.start() for t in threads]
[t.join() for t in threads]
print(a)
"""
执行结果:
A已启动
B已启动
A_a
"""
3. 递归死锁
from threading import Thread, Lock
total = 0
lock_Lock = Lock()
def Fibonacci(num):
if num == 2 or num == 1:
return 1
else:
global total,lock_Lock
lock_Lock.acquire()
print("lock_Lock已为total上锁")
total = Fibonacci(num - 1) + Fibonacci(num - 2)
lock_Lock.release()
print("lock_Lock已为total释放锁")
return total
if __name__ == '__main__':
thread_Fibonacci = Thread(target=Fibonacci, args=(6,))
thread_Fibonacci.start()
thread_Fibonacci.join()
print(total)
"""
运行结果:
lock_Lock已为total上锁
"""
"""
程序被卡住,停止往下运行。
原因:(1)上锁过程,在第一层Fibonacci()中,首先lock_Lock为total上锁,之后又进入第二层Fibonacci(),
在第二层Fibonacci()中在没有使用lock_Lock为total释放锁的情况下又使用lock_Lock为total上锁;
(2)threading.Lock() 是加载线程的锁对象,是一个基本的锁对象,一次只能一个锁定,其余锁请求,需
等待锁释放后才能获取。
"""
三、使用RLock解决递归锁
1. Lock锁与RLock锁的区别
Lock()作为一个基本的锁对象,一次只能一个锁定,其余锁请求,需等待锁释放后才能获取,否则会发生死锁。
为解决同一线程中不能多次请求同一资源的问题,python提供了“可重入锁”:threading.RLock,RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源 。
2. 使用RLock锁解决递归锁死锁问题,示例如下:
from threading import Thread, RLock
total = 1
lock_RLock = RLock()
def Fibonacci(num):
if num == 2 or num == 1:
return 1
else:
global total
global lock_RLock
lock_RLock.acquire()
print("lock_Lock为total上锁=====num:%d" % num)
total = Fibonacci(num - 1) + Fibonacci(num - 2)
lock_RLock.release()
print("lock_Lock为total释放锁===num:%d" % num)
return total
if __name__ == '__main__':
thread_Fibonacci = Thread(target=Fibonacci, args=(6,))
thread_Fibonacci.start()
thread_Fibonacci.join()
print(total)
"""
运行结果:
lock_Lock为total上锁=====num:6
lock_Lock为total上锁=====num:5
lock_Lock为total上锁=====num:4
lock_Lock为total上锁=====num:3
lock_Lock为total上锁=====num:2
lock_Lock为total释放锁===num:2
lock_Lock为total释放锁===num:3
lock_Lock为total上锁=====num:2
lock_Lock为total释放锁===num:2
lock_Lock为total释放锁===num:4
lock_Lock为total上锁=====num:3
lock_Lock为total上锁=====num:2
lock_Lock为total释放锁===num:2
lock_Lock为total释放锁===num:3
lock_Lock为total释放锁===num:5
lock_Lock为total上锁=====num:4
lock_Lock为total上锁=====num:3
lock_Lock为total上锁=====num:2
lock_Lock为total释放锁===num:2
lock_Lock为total释放锁===num:3
lock_Lock为total上锁=====num:2
lock_Lock为total释放锁===num:2
lock_Lock为total释放锁===num:4
lock_Lock为total释放锁===num:6
8
Process finished with exit code 0
"""