同步锁
首先,我们先了解在使用多线程的时候,什么情况下需要加锁?
那是因为在不同的线程,我们可能会需要对同一个变量进行修改,这个时候就会出现资源抢占的问题,比如在线程A中,对变量X的修改还未执行完毕时,这个时候线程B也要对X进行修改,此时线程B就会把X抢占过来,那么线程A中对X的修改就会被中断,导致修改不成功。
比如,我们下面这个例子,初设化n=0,我们启动10个线程,每个线程都对n进行1000000次+1的操作,那么预期的结果n应该等于10000000
from threading import Thread
def add_n():
global n
for i in range(1000000):
n += 1
# 开始添加线程
threads = []
# target:函数,args:函数的参数,以元组形式
for i in range(10):
threads.append(Thread(target=add_n))
n = 0
for t in threads:
# 设置为守护线程:主线程运行完毕之后,子线程仍然继续运行
t.setDaemon(True)
t.start() # 所有子线程同时启动
# 等待所有子线程运行完毕之后,再运行主程序
for t in threads:
t.join()
print("n = ", n)
但是,可以看到,结果并不是1000000,这就是多线程资源抢占造成的后果
n = 3434881
那么问题来了,如何解决呢?这个时候就需要用到同步锁了,在对n进行+1时,加上一把同步锁,等操作完成后再释放锁,这样就保证在加锁期间,只有一个线程在执行n+=1操作,其他线程只能等待锁的释放之后才能执行该操作。
from threading import Thread, Lock
lock = Lock()
def add_n():
global n
for i in range(1000000):
lock.acquire() #
n += 1
lock.release()
# 开始添加线程
threads = []
# target:函数,args:函数的参数,以元组形式
for i in range(10):
threads.append(Thread(target=add_n))
n = 0
for t in threads:
# 设置为守护线程:主线程运行完毕之后,子线程仍然继续运行
t.setDaemon(True)
t.start() # 所有子线程同时启动
# 等待所有子线程运行完毕之后,再运行主程序
for t in threads:
t.join()
print("n = ", n)
这个时候,加上同步锁之后,得到的n就如我们预期一样了。
n = 10000000
死锁
在给多线程加锁的时候,需要特别注意一点就是防止出现死锁。
在下面的例子中,就出现了死锁的情况,我们来分析一下为什么出现了死锁?
- A拿了一个苹果,并把苹果锁住了;
- B拿了一个香蕉,并把香蕉锁住了;
- A现在想再拿个香蕉,但必须得等待B释放这个香蕉;
- B同时想要再拿个苹果,这时候它也得等待A释放苹果;
- 这样就是陷入了僵局,出现了死锁。
from threading import Thread, Lock
import time
lock_apple = Lock()
lock_banana = Lock()
def fun1():
lock_apple.acquire() # 如果锁被占用,则阻塞在这里,等待锁的释放
print ("线程 %s , 想拿: %s--%s" %(t.name, "苹果",time.ctime()))
lock_banana.acquire()
print ("线程 %s , 想拿: %s--%s" %(t.name, "香蕉",time.ctime()))
lock_banana.release()
lock_apple.release()
def fun2():
lock_banana.acquire()
print ("线程 %s , 想拿: %s--%s" %(t.name, "香蕉",time.ctime()))
time.sleep(0.1)
lock_apple.acquire()
print ("线程 %s , 想拿: %s--%s" %(t.name, "苹果",time.ctime()))
lock_apple.release()
lock_banana.release()
def run():
fun1()
fun2()
# 开始添加线程
threads = []
# target:函数,args:函数的参数,以元组形式
for i in range(10):
threads.append(Thread(target=run))
for t in threads:
# 设置为守护线程:主线程运行完毕之后,子线程仍然继续运行
t.setDaemon(True)
t.start() # 所有子线程同时启动
for t in threads:
t.join()
程序会一直卡在这里不动了
线程 Thread-1 , 想拿: 苹果--Wed Aug 28 12:19:40 2019
线程 Thread-1 , 想拿: 香蕉--Wed Aug 28 12:19:40 2019
线程 Thread-2 , 想拿: 香蕉--Wed Aug 28 12:19:40 2019
线程 Thread-2 , 想拿: 苹果--Wed Aug 28 12:19:40 2019
递归锁
解决死锁,就需要用到递归锁了。RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源。
from threading import Thread, RLock
import time
lock = RLock()
def fun1():
lock.acquire() # 如果锁被占用,则阻塞在这里,等待锁的释放
print ("线程 %s , 想拿: %s--%s" %(t.name, "苹果",time.ctime()))
lock.acquire()
print ("线程 %s , 想拿: %s--%s" %(t.name, "香蕉",time.ctime()))
lock.release()
lock.release()
def fun2():
lock.acquire()
print ("线程 %s , 想拿: %s--%s" %(t.name, "香蕉",time.ctime()))
time.sleep(0.1)
lock.acquire()
print ("线程 %s , 想拿: %s--%s" %(t.name, "苹果",time.ctime()))
lock.release()
lock.release()
def run():
fun1()
fun2()
# 开始添加线程
threads = []
# target:函数,args:函数的参数,以元组形式
for i in range(10):
threads.append(Thread(target=run))
for t in threads:
# 设置为守护线程:主线程运行完毕之后,子线程仍然继续运行
t.setDaemon(True)
t.start() # 所有子线程同时启动
for t in threads:
t.join()
欢迎关注同名公众号:“我就算饿死也不做程序员”。
交个朋友,一起交流,一起学习,一起进步。