博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
第十五章、Python多线程同步锁,死锁和递归锁
阅读量:5291 次
发布时间:2019-06-14

本文共 4616 字,大约阅读时间需要 15 分钟。

目录

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

1. 引子:

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

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

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

2.同步锁

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

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

import threadingimport timenum = 100def fun_sub():    global num    # num -= 1    num2 = num    time.sleep(0.001)    num = num2-1if __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 2019num 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 threadingimport timenum = 100def 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,Lockmutex1 = Lock()mutex2 = Lock()import timeclass 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 = mutex1import timeclass 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. 大总结

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

转载于:https://www.cnblogs.com/demiao/p/11543888.html

你可能感兴趣的文章
使用pager进行分页
查看>>
吐医疗器械研发可配置性需求的槽点
查看>>
UVA - 1592 Database
查看>>
机器翻译评价指标 — BLEU算法
查看>>
机器学习基石(9)--Linear Regression
查看>>
Min Stack
查看>>
从LazyPhp说起
查看>>
Fine Uploader文件上传组件
查看>>
Spring Boot与Spring的区别
查看>>
查看linux 之mysql 是否安装的几种方法
查看>>
javascript中的传递参数
查看>>
objective-c overview(二)
查看>>
python查询mangodb
查看>>
软件测试(基础理论一)摘
查看>>
consonant combination
查看>>
PHP与Linux进程间的通信
查看>>
【长期更新】坑点合集
查看>>
wnmp windows 2012 r2+php7.0+nginx1.14安装
查看>>
weblogic与axis2 jar包冲突
查看>>
Hello Spring Framework——面向切面编程(AOP)
查看>>