Python学习 :多线程 --- 锁
多线程
什么是锁?
- 锁通常被用来实现对共享资源的同步访问。
- 为每一个共享资源创建一个lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁:
gil(global interpreter lock) 全局的解释器锁
增加锁的目的:
1.虽然效率十分低,但保证了数据的安全性
2.不同的锁对应保护不同的数据
3.谁拿到gil锁就让谁得到cpython解释器的执行权限
4.gil锁保护的是cpython解释器数据的安全,而不会保护你自己程序的数据的安全
5.gil锁当遇到阻塞的时候,就*把锁给释放了,那么其他的就开始抢锁了,抢到后把值进行修改,但是第一个拿到锁的还依旧保持着原本的数据,当再次拿到锁的时候,数据已经修改了,而第一位拿的还是原来的数值,这样就造成了混乱,也就保证不了数据的安全了。
同步锁lock
- gil与lock是两把锁,保护的数据不一样,前者是解释器级别的(保护的是解释器级别的数据,比如垃圾回收的数据),后者是保护用户自己开发的应用程序的数据
实例:没有加上锁的情况
- 在执行这个操作的多条bytecodes期间的时候可能中途就换别的线程了,这样就出现了data races的情况
mport time
import threading
def addnum():
global num # 在每个线程中都获取这个全局变量
temp = num
print('--get num:',num )
time.sleep(0.01)
num = temp - 1 # 对此公共变量进行-1操作
start = time.time()
num = 100 # 设定一个共享变量
thread_list = []
for i in range(100):
t = threading.thread(target=addnum)
t.start()
thread_list.append(t)
for t in thread_list: # 等待所有线程执行完毕
t.join()
print('final num:', num )
end = time.time()
print(end - start)
# 此时并不能获取到正确的答案0
# 100个线程每一个一定都没有执行完就进行了切换,我们说过sleep就等效于io阻塞,1s之内不会再切换回来,所以最后的结果一定是99.
# 多个线程都在同时操作同一个共享资源,所以造成了资源破坏
实例:加上锁的情况
- 同步锁保证了在同一时刻只有一个线程被执行
import time
import threading
def addnum():
global num # 在每个线程中都获取这个全局变量
lock.acquire() # 获取锁
temp = num
print('--get num:',num )
num = temp - 1 # 对此公共变量进行-1操作
lock.release() # 只有在执行完上述内容才会释放锁
start = time.time()
num = 100 # 设定一个共享变量
thread_list = []
lock = threading.lock()
for i in range(100):
t = threading.thread(target=addnum)
t.start()
thread_list.append(t)
for t in thread_list: #等待所有线程执行完毕
t.join()
print('final num:', num )
end = time.time()
print(end - start)
死锁lock
线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁的情况
因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去
import threading,time
class mythread(threading.thread):
def loa(self):
locka.acquire()
print(self.name,"got locka",time.ctime())
time.sleep(3)
lockb.acquire()
print(self.name,"got lockb",time.ctime())
lockb.release()
locka.release()
def lob(self):
lockb.acquire()
print(self.name,"got lockb",time.ctime())
time.sleep(2)
locka.acquire()
print(self.name,"got locka",time.ctime())
locka.release()
lockb.release()
def run(self):
self.loa()
self.lob()
if __name__=="__main__":
locka=threading.lock()
lockb=threading.lock()
# 解决方案:添加 lock = threading.rlock()
# lock = threading.rlock()
threads=[]
for i in range(3):
threads.append(mythread())
for t in threads:
t.start()
for t in threads:
t.join()
# 此时线程会卡死,一直等待下去,此时添加递归锁即可解决死锁的问题
递归锁 rlock
在添加递归锁后,rlock内部维护着一个lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源
信号量
信号量用来控制线程并发数的,它也是一把锁,boundedsemaphore或semaphore管理一个内置的计数 器,每当调用acquire()时-1,调用release()时+1
计数器不能小于0,当计数器为 0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。(类似于停车位的概念)
boundedsemaphore与semaphore的唯一区别在于前者将在调用release()时检查计数 器的值是否超过了计数器的初始值,如果超过了将抛出一个异常
import threading,time
class mythread(threading.thread):
def run(self):
if semaphore.acquire(): # 如果上信号量锁就往下进行
print(self.name)
time.sleep(5)
semaphore.release()
if __name__=="__main__":
semaphore = threading.semaphore(5) # 一次允许5个线程进行
# semaphore = threading.boundedsemaphore(5) # 与上述效果一致
thrs = []
for i in range(20): # 开启20个线程
thrs.append(mythread())
for t in thrs:
t.start()
条件变量同步
有一类线程需要满足条件之后才能够继续执行,python提供了threading.condition对象用于条件变量线程的支持,它除了能提供rlock()或lock()的方法外,还提供了wait()、notify()、notifyall()方法
lock_con = threading.condition([lock / rlock]): 默认创建一个rlock锁,用于线程间的通信
wait():条件不满足时调用,线程会释放锁并进入等待阻塞
notify():条件创造后调用,通知等待池激活一个线程
notifyall():条件创造后调用,通知等待池激活所有线程
import threading,time
from random import randint
class producer(threading.thread):
def run(self):
global l
while true:
val=randint(0,100)
print('生产者',self.name,":append"+str(val),l)
if lock_con.acquire():
l.append(val)
lock_con.notify() # 激活一个线程
lock_con.release()
time.sleep(3)
class consumer(threading.thread):
def run(self):
global l
while true:
lock_con.acquire() # wait()过后,从此处开始进行
if len(l)==0:
lock_con.wait() # 进入等待阻塞
print('继续进行') # 我们可以看到并没有打印这个话,这说明线程不是从wait()下面继续进行
print('消费者',self.name,":delete"+str(l[0]),l)
del l[0]
lock_con.release()
time.sleep(0.25)
if __name__=="__main__":
l = []
lock_con = threading.condition()
threads = []
for i in range(5):
threads.append(producer())
threads.append(consumer())
for t in threads:
t.start()
for t in threads:
t.join()
同步条件(event)
条件同步和条件变量同步差不多意思,只是少了锁功能,同步条件不是锁
因为条件同步设计于不访问共享资源的条件环境。event = threading.event():条件环境对象,初始值为false;
event.isset():返回event的状态值
event.wait():如果 event.isset()==false将阻塞线程
event.set(): 设置event的状态值为true,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度
event.clear():恢复event的状态值为false
import threading,time
import random
def light():
if not event.isset():
event.set() #wait就不阻塞 #绿灯状态
count = 0
while true:
if count < 10:
print('\033[42;1m--green light on---\033[0m')
elif count <13:
print('\033[43;1m--yellow light on---\033[0m')
elif count <20:
if event.isset():
event.clear()
print('\033[41;1m--red light on---\033[0m')
else:
count = 0
event.set() # 打开绿灯
time.sleep(1)
count += 1
def car(n):
while 1:
time.sleep(random.randrange(10))
if event.isset(): # 绿灯
print("car [%s] is running.." % n)
else:
print("car [%s] is waiting for the red light.." %n)
if __name__ == '__main__':
event = threading.event()
light = threading.thread(target=light)
light.start()
for i in range(3):
t = threading.thread(target=car,args=(i,))
t.start()
上一篇: 看的都是成人