首页 > python教程

Python多线程操作之互斥锁、递归锁、信号量、事件实例详解

时间:2020-07-30 python教程 查看: 969

本文实例讲述了Python多线程操作之互斥锁、递归锁、信号量、事件。分享给大家供大家参考,具体如下:

互斥锁:

  • 为什么要有互斥锁:由于多线程是并行的,如果某一线程取出了某一个数据将要进行操作,但它还没有那么快执行完操作,这时候如果另外一个线程也要操作这个数据,那么这个数据可能会因为两次操作而发生错误
import time,threading

x=6
def run1():
  print("run1我拿到了数据:",x)
  print("我现在还不想操作,先睡一下")
  time.sleep(3)
  print("再看一下数据,稳一稳",x)

def run2():
  global x
  print("run2我拿到了数据:", x)

  x=5
  print(x)

t1=threading.Thread(target=run1)
t2=threading.Thread(target=run2)

t1.start()
t2.start()
t1.join()
t2.join()

  • 而多线程的互斥锁机制本质上是:申请一个锁,A线程拿了钥匙【acquire】之后,如果B也想拿到钥匙是不行的,只有等A把钥匙还回来【release】才行
  • 如何使用互斥锁:
    1. 定义一个锁对象:锁对象=threading.Lock()
    2. 请求锁:锁对象.acquire()
    3. 释放锁:锁对象.release()

使用互斥锁来更改上段代码

import time,threading

x=6
def run1():
  lock.acquire()
  global x
  print("run1我拿到了数据,x=",x)
  print("我现在还不想操作,先睡一下")
  time.sleep(3)
  print("再看一下数据,稳一稳,x=",x)
  x+=1
  print("run1操作完毕:x=",x)
  lock.release()
def run2():
  lock.acquire()
  global x
  print("run2我拿到了数据:", x)
  x+=1
  print("run2操作完毕:x=",x)
  lock.release()

lock=threading.Lock()#生成一个锁对象
t1=threading.Thread(target=run1)
t2=threading.Thread(target=run2)

t1.start()
t2.start()
start_time=time.time()
t1.join()
t2.join()
print("最终的x=",x)
print(time.time()-start_time)#3.0多说明,由于受到锁的影响,run2要等待run1释放lock,所以变成了串行

这种互斥锁在操作系统中可以称作“临界区”,如果想了解更多:

https://baike.baidu.com/item/%E4%B8%B4%E7%95%8C%E5%8C%BA/8942134?fr=aladdin


递归锁:

  • 为什么要有递归锁:互斥锁本质上是阻止其他线程进入,如果有两个需要阻止其他线程进入的操作【像两个人过独木桥】,那么需要两个锁,而想要锁上第二个如果直接用第一个锁的acquire会失败,因为第一个锁还没release,我们可以选择再定义一个互斥锁对象来acquire,但这仅仅是两层的情况下,如果多层的吧,那么就需要定义好几个互斥锁对象了【而且由于对象变多,有时候会因为互相调用锁而发生死锁】。递归锁就是为了处理这种情况,递归锁对象允许多次acquire和多次release
    • 发生死锁的情况[A拿到A锁,想要拿B锁,B拿着B锁,想要A锁]

【以过独木桥为例】:桥只能容一个人通过,A只能看得到北边桥上有没有人,看不到南边桥有没有人,当他看到北边桥没人就会过桥,等到他到桥中间才能看到南边桥有没有人,B情况相反:【于是当两个人一起过桥的时候就会发生死锁】

import threading,time

"""
A只能看得到北边桥上有没有人,看不到南边桥有没有人,
当他看到北边桥没人就会过桥,等到他到桥中间才能看到南边桥有没有人
"""
def A():
  lockNorth.acquire()#拿到北边桥的锁
  print("A过桥北")
  time.sleep(3)#过桥中
  lockSorth.acquire()#企图过到南边桥,
  print("A过桥南")
  time.sleep(3) # 过桥中
  lockSorth.release()
  lockNorth.release()
  print("A过桥成功")

"""
B只能看得到南边桥上有没有人,看不到北边桥有没有人,
当他看到南边桥没人就会过桥,等到他到桥中间才能看到北边桥有没有人
"""
def B():
  lockSorth.acquire() # 企图过到南边桥,
  print("B过桥南")
  time.sleep(3) # 过桥中
  lockNorth.acquire() # 拿到北边桥的锁
  print("B过桥北")
  time.sleep(3) # 过桥中
  lockNorth.release()
  lockSorth.release()
  print("B过桥成功")


lockNorth=threading.Lock()
lockSorth=threading.Lock()

tA=threading.Thread(target=A)
tB=threading.Thread(target=B)
tA.start()
tB.start()

tA.join()
tB.join()

  • 递归锁的本质是:本质上还是一个锁,但如果在一个线程里面可以多次acquire。【因为只有一个锁,所以不会发生互相调用的死锁,而因为可以多次调用,所以可以锁多次】
  • 如何使用递归锁:
    1. 定义一个锁对象:递归锁对象=threading.RLock()
    2. 请求锁:锁对象.acquire()
    3. 释放锁:锁对象.release()

使用递归锁来解决上面的死锁问题:

```python
import threading,time

""" A只能看得到北边桥上有没有人,看不到南边桥有没有人, 当他看到北边桥没人就会过桥,等到他到桥中间才能看到南边桥有没有人 """ def A(): lock.acquire()#拿到北边桥的锁 print("A过桥北") time.sleep(3)#过桥中 lock.acquire()#企图过到南边桥, print("A过桥南") time.sleep(3) # 过桥中 lock.release() lock.release() print("A过桥成功")

""" B只能看得到南边桥上有没有人,看不到北边桥有没有人, 当他看到南边桥没人就会过桥,等到他到桥中间才能看到北边桥有没有人 """ def B(): lock.acquire() # 拿南桥锁, print("B过桥南") time.sleep(3) # 过桥中 lock.acquire() # 企图拿北桥的锁 print("B过桥北") time.sleep(3) # 过桥中 lock.release() lock.release() print("B过桥成功")

lock=threading.RLock()

tA=threading.Thread(target=A) tB=threading.Thread(target=B) tA.start() tB.start()

tA.join() tB.join()

  <p><img title="image" border="0" alt="image" width="147" height="158" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; margin: 0px; display: inline; padding-right: 0px" src="https://img.94e.cn/media/uploads/full/20200730/2020032410034111.png"></p>
  <p>【由于本质是一把锁,A拿到锁后,B要等待】</p>
  <p></p><hr>

  <h1>信号量:</h1>
  <ul>
    <li>什么是信号量:</li>
  </ul>
  <p><img title="image" border="0" alt="image" width="834" height="142" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; margin: 0px; display: inline; padding-right: 0px" src="https://img.94e.cn/media/uploads/full/20200730/2020032410034112.png"></p>
  <p>信号量可以限制进入的线程的数量。</p>
  <ul>
    <li>如何使用信号量:</li>
  </ul>
  <ol>
    <ol>
      <li>创建信号量对象:信号量对象=threading.BoundedSemaphore(x),x是限制进程的数量</li>
      <li>当有进程需要进入的时候,调用acquire()来减少信号量:信号量对象.acquire()</li>
      <li>当有进程离开的时候,调用release()来增加信号量:信号量对象.release()</li>
    </ol>
  </ol>

  ```python  
import threading,time


def run():
  s.acquire()
  print("hello")
  time.sleep(1.5)
  s.release()

s=threading.BoundedSemaphore(3)#限制3个

threading_list=[]
for i in range(12):#创建12个线程
  obj=threading.Thread(target=run)
  obj.setDaemon(True) # 设置守护线程,避免干扰主线程运行,并行等待
  obj.start()

for i in range(4):
  print("")#为了把结果分割,可以清楚看出分为了三组
  time.sleep(1.5)
#结果分为三组是因为运行的太快了,三个线程装入的时间差太小


事件:

  • 什么是事件:当发生线程发生一件事的时候如果要提醒另外一个线程,使用事件。双方共享该事件对象【等待的一方会阻塞而进行等待】,当一方更改事件对象的时候,另外一方也能知道【以读者-写者为例:读者要等写者告诉他去读才会去读,写者写完后要设置一个事件,当该事件设置时,读者就会来读】
  • 如何使用事件:
    1. 创建事件对象:事件对象=threading.Event()
    2. 设置事件:事件对象.set()    判断事件是否set:事件对象.is_set(),等待事件set:事件对象.wait()
    3. 清除事件:事件对象.clear() 

```python
import threading,time

def read(): while True: if event.is_set(): print("事件已设置,我要读了!!!!") time.sleep(1) else:#事件未设置 print("还没写好,我要等咯") event.wait()#那么就等着咯 #如果等到了 print("终于等到了!那么我又可以读了") time.sleep(1)

def write(): event.clear()#初始设空 while True: time.sleep(3)#写 event.set()#设置事件,一旦set,那么读者wait就有返回了,读者可以继续运行了 print("write:写好了") time.sleep(2)#等人读 event.clear()#清除事件

event=threading.Event() #创建事件对象

t1=threading.Thread(target=write) t2=threading.Thread(target=read)

t1.start() t2.start() t1.join() t2.join()

"""结果显示:读者确实一直在等待写者写好""" ```

希望本文所述对大家Python程序设计有所帮助。

展开全文
上一篇:Python动态强类型解释型语言原理解析
下一篇:pyqt5 QlistView列表显示的实现示例
输入字:
相关知识
Python 实现图片色彩转换案例

我们在看动漫、影视作品中,当人物在回忆过程中,体现出来的画面一般都是黑白或者褐色的。本文将提供将图片色彩转为黑白或者褐色风格的案例详解,感兴趣的小伙伴可以了解一下。

python初学定义函数

这篇文章主要为大家介绍了python的定义函数,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助,希望能够给你带来帮助

图文详解Python如何导入自己编写的py文件

有时候自己写了一个py文件,想要把它导入到另一个py文件里面,所以下面这篇文章主要给大家介绍了关于Python如何导入自己编写的py文件的相关资料,需要的朋友可以参考下

python二分法查找实例代码

二分算法是一种效率比较高的查找算法,其输入的是一个有序的元素列表,如果查找元素包含在列表中,二分查找返回其位置,否则返回NONE,下面这篇文章主要给大家介绍了关于python二分法查找的相关资料,需要的朋友可以参考下