热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Python学习:多线程锁

 多线程 什么是锁? -锁通常被用来实现对共享资源的同步访问。 -为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获

 多线程


  什么是锁?

  - 锁通常被用来实现对共享资源的同步访问

  - 为每一个共享资源创建一个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()

推荐阅读
  • 关键词:Golang, Cookie, 跟踪位置, net/http/cookiejar, package main, golang.org/x/net/publicsuffix, io/ioutil, log, net/http, net/http/cookiejar ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • OO第一单元自白:简单多项式导函数的设计与bug分析
    本文介绍了作者在学习OO的第一次作业中所遇到的问题及其解决方案。作者通过建立Multinomial和Monomial两个类来实现多项式和单项式,并通过append方法将单项式组合为多项式,并在此过程中合并同类项。作者还介绍了单项式和多项式的求导方法,并解释了如何利用正则表达式提取各个单项式并进行求导。同时,作者还对自己在输入合法性判断上的不足进行了bug分析,指出了自己在处理指数情况时出现的问题,并总结了被hack的原因。 ... [详细]
  • java drools5_Java Drools5.1 规则流基础【示例】(中)
    五、规则文件及规则流EduInfoRule.drl:packagemyrules;importsample.Employ;ruleBachelorruleflow-group ... [详细]
  • 深刻理解 python中函数的参数 引用的传递方式
    函数的参数作为引用Python唯一支持的参数传递模式是共享传参(callbysharing)。共享传参指函数的各个形参获得实参中各个引用的副本。也就是 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文介绍了C++中省略号类型和参数个数不确定函数参数的使用方法,并提供了一个范例。通过宏定义的方式,可以方便地处理不定参数的情况。文章中给出了具体的代码实现,并对代码进行了解释和说明。这对于需要处理不定参数的情况的程序员来说,是一个很有用的参考资料。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • Linux如何安装Mongodb的详细步骤和注意事项
    本文介绍了Linux如何安装Mongodb的详细步骤和注意事项,同时介绍了Mongodb的特点和优势。Mongodb是一个开源的数据库,适用于各种规模的企业和各类应用程序。它具有灵活的数据模式和高性能的数据读写操作,能够提高企业的敏捷性和可扩展性。文章还提供了Mongodb的下载安装包地址。 ... [详细]
author-avatar
飞飞飞070801
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有