第十五章、Python多线程同步锁,死锁和递归锁

1. 引子:

1.创建线程对象
t1 = threading.Thread(target=say,args=('tony',))
2.启动线程
t1.start()
后面又说了两个点就是join和守护线程的概念

以上就是python多线程的基本使用

​ 说明:前面说的两个功能是相互独立的,相互不干涉的,不会用到同享的资源或者数据,如果我们多个线程要用到相同的数据,那么就会存在资源争用和锁的问题,不管在什么语言中,这个都是不能避免的。 那么接下来讲讲同步锁,死锁和递归锁的使用

2.同步锁

​ 锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁。

​ 适用同步锁的例子如下:

import threading
import time num = 100 def fun_sub():
global num
# num -= 1
num2 = num
time.sleep(0.001)
num = num2-1 if __name__ == '__main__':
print('开始测试同步锁 at %s' % time.ctime()) thread_list = []
for thread in range(100):
t = threading.Thread(target=fun_sub)
t.start()
thread_list.append(t) for t in thread_list:
t.join()
print('num is %d' % num)
print('结束测试同步锁 at %s' % time.ctime())
-----------------------------------------------------
开始测试同步锁 at Sun Apr 28 09:56:45 2019
num is 91
结束测试同步锁 at Sun Apr 28 09:56:45 2019

这样的例子描述:创建100的线程,然后每个线程去从公共资源num变量去执行减1操作,按照正常情况下面,等到代码执行结束,打印num变量,应该得到的是0,因为100个线程都去执行了一次减1的操作。

这样的问题是:发现结果不是0而是91

让我们整理一下代码思路

1.因为GIL,只有一个线程(假设线程1)拿到了num这个资源,然后把变量赋值给num2,sleep 0.001秒,这时候num=100

2.当第一个线程sleep 0.001秒这个期间,这个线程会做yield操作,就是把cpu切换给别的线程执行(假设线程2拿到个GIL,获得cpu使用权),线程2也和线程1一样也拿到num,返回赋值给num2,然后sleep,这时候,其实num还是=100.

3.线程2 sleep时候,又要yield操作,假设线程3拿到num,执行上面的操作,其实num有可能还是100

4.等到后面cpu重新切换给线程1,线程2,线程3上执行的时候,他们执行减1操作后,其实等到的num其实都是99,而不是顺序递减的。

5.其他剩余的线程操作如上

解决方案:这里就要借助于python的同步锁了,也就是同一时间只能放一个线程来操作num变量,减1之后,后面的线程操作来操作num变量。看看下面我们怎么实现。

import threading
import time num = 100 def fun_sub():
global num
lock.acquire()
print('----加锁----')
print('现在操作共享资源的线程名字是:',t.name)
num2 = num
time.sleep(0.001)
num = num2-1
lock.release()
print('----释放锁----') if __name__ == '__main__':
print('开始测试同步锁 at %s' % time.ctime()) lock = threading.Lock() #创建一把同步锁 thread_list = []
for thread in range(100):
t = threading.Thread(target=fun_sub)
t.start()
thread_list.append(t) for t in thread_list:
t.join()
print('num is %d' % num)
print('结束测试同步锁 at %s' % time.ctime())
------------------------------------------------
.......
----加锁----
现在操作共享资源的线程名字是: Thread-98
----释放锁----
----加锁----
现在操作共享资源的线程名字是: Thread-100
----释放锁----
num is 0
结束测试同步锁 at Sun Apr 28 12:08:27 2019

思路:看到上面我们给中间的减1代码块,加个一把同步锁,这样,我们就可以得到我们想要的结果了,这就是同步锁的作用,一次只有一个线程操作同享资源。

3.死锁

引子:

​ 死锁的这个概念在很多地方都存在,比较在数据中,大概介绍下死锁是怎么产生的

# 线程1拿到了(锁头2)想要往下执行需要(锁头1),
# 线程2拿到了(锁头1)想要往下执行需要(锁头2)
# 互相都拿到了彼此想要往下执行的必需条件,互相都不放手里的锁头.
# 产生了死锁问题

产生原因:python中在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去。

from threading import Thread,Lock
mutex1 = Lock()
mutex2 = Lock()
import time
class MyThreada(Thread):
def run(self):
self.task1()
self.task2()
def task1(self):
mutex1.acquire()
print(f'{self.name} 抢到了 锁1 ')
mutex2.acquire()
print(f'{self.name} 抢到了 锁2 ')
mutex2.release()
print(f'{self.name} 释放了 锁2 ')
mutex1.release()
print(f'{self.name} 释放了 锁1 ') def task2(self):
mutex2.acquire()
print(f'{self.name} 抢到了 锁2 ')
time.sleep(1)
mutex1.acquire()
print(f'{self.name} 抢到了 锁1 ')
mutex1.release()
print(f'{self.name} 释放了 锁1 ')
mutex2.release()
print(f'{self.name} 释放了 锁2 ') for i in range(3):
t = MyThreada()
t.start()

那么,为了解决这个死锁问题,就引入了递归锁方案

4.递归锁RLock

原理:

​ 为了支持在同一线程中多次请求同一资源,python提供了"递归锁":threading.RLock。RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源

不多说,放代码

from threading import Thread,Lock,RLock
# mutex1 = Lock()
# mutex2 = Lock()
mutex1 = RLock()
mutex2 = mutex1 import time
class MyThreada(Thread):
def run(self):
self.task1()
self.task2()
def task1(self):
mutex1.acquire()
print(f'{self.name} 抢到了 锁1 ')
mutex2.acquire()
print(f'{self.name} 抢到了 锁2 ')
mutex2.release()
print(f'{self.name} 释放了 锁2 ')
mutex1.release()
print(f'{self.name} 释放了 锁1 ') def task2(self):
mutex2.acquire()
print(f'{self.name} 抢到了 锁2 ')
time.sleep(1)
mutex1.acquire()
print(f'{self.name} 抢到了 锁1 ')
mutex1.release()
print(f'{self.name} 释放了 锁1 ')
mutex2.release()
print(f'{self.name} 释放了 锁2 ') for i in range(3):
t = MyThreada()
t.start()

总结:

​ 上面我们用一把递归锁,就解决了多个同步锁导致的死锁问题。大家可以把RLock理解为大锁中还有小锁,只有等到内部所有的小锁,都没有了,其他的线程才能进入这个公共资源。

5. 大总结

​ 还有一点,并不是所有的多线程都存在数据不同步、死锁的问题,但在访问共享资源的时候,锁是一定要存在了, 所以我们在代码里面加锁的时候,要注意在什么地方加,对性能的影响最小,这个就靠对逻辑的理解了。

第十五章、Python多线程同步锁,死锁和递归锁的更多相关文章

  1. 8.14 day32 TCP服务端并发 GIL解释器锁 python多线程是否有用 死锁与递归锁 信号量event事件线程q

    TCP服务端支持并发 解决方式:开多线程 服务端 基础版 import socket """ 服务端 1.要有固定的IP和PORT 2.24小时不间断提供服务 3.能够支 ...

  2. Thread类的其他方法,同步锁,死锁与递归锁,信号量,事件,条件,定时器,队列,Python标准模块--concurrent.futures

    参考博客: https://www.cnblogs.com/xiao987334176/p/9046028.html 线程简述 什么是线程?线程是cpu调度的最小单位进程是资源分配的最小单位 进程和线 ...

  3. python 全栈开发,Day42(Thread类的其他方法,同步锁,死锁与递归锁,信号量,事件,条件,定时器,队列,Python标准模块--concurrent.futures)

    昨日内容回顾 线程什么是线程?线程是cpu调度的最小单位进程是资源分配的最小单位 进程和线程是什么关系? 线程是在进程中的 一个执行单位 多进程 本质上开启的这个进程里就有一个线程 多线程 单纯的在当 ...

  4. 同步锁 死锁与递归锁 信号量 线程queue event事件

    二个需要注意的点: 1 线程抢的是GIL锁,GIL锁相当于执行权限,拿到执行权限后才能拿到互斥锁Lock,其他线程也可以抢到GIL,但如果发现Lock任然没有被释放则阻塞,即便是拿到执行权限GIL也要 ...

  5. GIL全局解释器锁-死锁与递归锁-信号量-event事件

    一.全局解释器锁GIL: 官方的解释:掌握概念为主 """ In CPython, the global interpreter lock, or GIL, is a m ...

  6. 10 并发编程-(线程)-GIL全局解释器锁&死锁与递归锁

    一.GIL全局解释器锁 1.引子 在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势 首先需要明确的一点是GIL并不是Python的特性,它是在实现Pyt ...

  7. python并发编程之线程(创建线程,锁(死锁现象,递归锁),GIL锁)

    什么是线程 进程:资源分配单位 线程:cpu执行单位(实体),每一个py文件中就是一个进程,一个进程中至少有一个线程 线程的两种创建方式: 一 from threading import Thread ...

  8. day 33 什么是线程? 两种创建方式. 守护线程. 锁. 死锁现象. 递归锁. GIL锁

    一.线程     1.进程:资源的分配单位    线程:cpu执行单位(实体) 2.线程的创建和销毁开销特别小 3.线程之间资源共享,共享的是同一个进程中的资源 4.线程之间不是隔离的 5.线程可不需 ...

  9. 第十五章、Python多线程之信号量和GIL

    目录 第十五章.Python多线程之信号量和GIL 1. 信号量(Semaphore) 2. GIL 说明: 第十五章.Python多线程之信号量和GIL 1. 信号量(Semaphore) 信号量用 ...

随机推荐

  1. 表单中使用<button>的注意点

    本文主要记录了我调查问题的思路想法,想看结论的同学直接拖到最后吧 上周在做项目的时候,有一个需求是在页面中加一个按钮,点一下查询数据库将内容填充在表格中.这不是很简单嘛,页面加个按钮,发送ajax请求 ...

  2. 一百一十六:CMS系统之使用阿里大于sdk发送短信验证码

    阿里大于短信平台:https://dysms.console.aliyun.com/dysms.htm#/overview 使用教程:https://blog.csdn.net/qq103189393 ...

  3. Linux命令集锦:crontab命令

    Linux crontab是用来定期执行程序的命令.当安装完成操作系统之后,默认便会启动此任务调度命令.crontab命令每分钟会定期检查是否有要执行的工作,如果有要执行的工作便会自动执行该工作. 而 ...

  4. 刀塔OMG塔防1.23单机版使用方法

    使用方法1.确保魔兽的版本为1.26(低版本会报错),如果版本不对用附件中的版本转换器转一下2.把 omg.w3x 复制到 魔兽的MAPS文件夹 WarcraftIII 游戏根目录\Maps3.双击运 ...

  5. 【原创】asp.net webdiyer AspNetPager控件分页码改变时,序号列从新从1开始问题的解决

      长话短说,首先看repeater 配合 webdiyer AspNetPager使用时webdiyer AspNetPager属性设置代码. <webdiyer:AspNetPager ID ...

  6. element UI实现动态生成多级表头

    一.效果图 二.封装两个组件,分别为DynamicTable.vue和TableColumn.vue,TableColumn.vue主要是使用递归来对表头进行循环生成 DynamicTable.vue ...

  7. python界面编程

    这是一个简单的加法计算器 首先,要先对这个简易计算器进行布局设计,需要两个输入框,还有一个输入框用于存放就算的结果,还需要两个table,一个是"+"一个是"=" ...

  8. A New 3-bit Programming Algorithm using SLC-to-TLC Migration for 8MBs High Performance TLC NAND Flash Memory

    背景 1.2012年左右的数据SLC.MLC.TLC闪存芯片的区别:SLC = Single-Level Cell ,即1bit/cell,速度快寿命长,价格超贵(约MLC 3倍以上的价格),约10万 ...

  9. 性能排查--CPU占用高

    排查思路: 1.先找到占用CPU高的进程PID    top命令 2.top -H -p <PID>  查看哪个占用CPU高的线程TID 3.jstack <PID>  /ho ...

  10. struts框架之环境搭建(一)

    一.首先,我们需要做一些准备工作: 1.1.  下载struts:https://struts.apache.org/download.cgi#struts2520 1.2.  下载tomcat:ht ...