0x00 前言

本片文章讲述了小明同学在编写python多线程过程中遇到一些奇怪现象,小明根据这些奇怪现象挖掘背后的原因...通过遇到的问题,引申出全局解释器锁,同步锁,递归锁,信号量...

0x01 全局解释器锁

小明同学在实验过程中,需要计算一个加法和一个乘法,觉得单线程运行时间较长,所以改为多线程,不料发现线程比单线程运行时间还长...

单线程代码如下,运行时间为8.41097640991211

import timestart_time=time.time()def add():   q=0   for q_i in range(100000000):       q=q+q_idef mcl():   w=0   for w_i in range(100000000):       w=w*w_iadd()mcl()end_time=time.time()print(end_time-start_time)

多线程代码如下,运行时间为8.47524094581604

import timeimport threadingstart_time=time.time()def add():   q=0   for q_i in range(100000000):       q=q+q_idef mcl():   w=0   for w_i in range(100000000):       w=w*w_it1=threading.Thread(target=add,args=())t2=threading.Thread(target=mcl,args=())t1.start()t2.start()t1.join()t2.join()end_time=time.time()print(end_time-start_time)

多线程为什么没有起作用,猜测程序执行过程是串行的,这就是全局解释器锁导致的,全局解释器锁的概念如下:

在同一个进程中只要有一个线程获取了全局解释器(cpu)的使用权限,那么其他的线程就必须等待该线程的全局解释器(cpu)使用权消失后才能使用全局解释器(cpu),即时多个线程直接不会相互影响在同一个进程下也只有一个线程使用cpu,这样的机制称为全局解释器锁(GIL)。

最后通过修改为多进程可以节省运行时间,5.125233888626099

import timefrom multiprocessing import Processstart_time=time.time()def add():   q=0   for q_i in range(100000000):       q=q+q_idef mcl():   w=0   for w_i in range(100000000):       w=w*w_iif __name__ == '__main__':   p1 = Process(target=add, args=())   p2 = Process(target=mcl, args=())   p1.start()   p2.start()   p1.join()   p2.join()   end_time=time.time()   print(end_time-start_time)

0x02 同步锁

小明同学在实验过程中,需要用多线程计算一个减法,将数字100减到0,小明同学开了100个线程,每个线程减1,最后应该减为0...最后却没有达到期望结果。

小明写的代码如下,计算结果却是94。

import timeimport threadingnumber=100def sub():   global number   tmp_num=number   time.sleep(0.0005)   number=tmp_num-1thread_list=[]for i in range(100):   t=threading.Thread(target=sub,args=())   t.start()   thread_list.append(t)for thread_i in thread_list:   thread_i.join()print(number)

小明请教老师帮忙需寻找到的原因为,cpu第一个线程执行到sub函数的tmp_num=number步骤,未完成执行情况下,因为存在IO阻塞(time.sleep) ,CPU会切换到第二个线程依旧执行到sub函数的tmp_num=number步骤,此时number也还是100,假设线程二执行完毕此时number为99,CPU再切回线程一继续执行之前上下文计算出来number也是99,赋值给number为99,可以看出执行完2个线程了实际number只减一。经过指导小明给线程加上线程锁。运算结果为0

import timeimport threadingnumber=100syn_lock=threading.Lock()def sub():   global number   syn_lock.acquire()   tmp_num=number   time.sleep(0.0005)   number=tmp_num-1   syn_lock.release()thread_list=[]for i in range(100):   t=threading.Thread(target=sub,args=())   t.start()   thread_list.append(t)for thread_i in thread_list:   thread_i.join()print(number)

综上总结:

1、什么是同步锁?  

同一时刻的一个进程下的一个线程只能使用一个cpu,要确保这个线程下的程序在一段时间内被cpu执,那么就要用到同步锁。

2、为什么用同步锁?  

因为有可能当一个线程在使用cpu时,该线程下的程序可能会遇到io操作,那么cpu就会切到别的线程上去,这样就有可能会影响到该程序结果的完整性。

3、怎么使用同步锁?  

只需要在对公共数据的操作前后加上上锁和释放锁的操作即可。

0x03 死锁

因为疫情严重,学校假期有个规定,每次只准一个人进入学校,学校有两把锁,进入学校后需要先用A锁上学校门,进入教室在C锁上教室门。小明先用锁A,然后用锁C,成功进入教室,此时两把锁都是用完了处于暂时释放状态。然后拿完东西想出学校,小明此时使用C锁走出教室。于此同时,刚才小白看两把锁都是处于暂时释放状态,也想进入学校,便使用了锁A;

然后问题出现了,小明此时占用锁C想使用锁A,小白此时占用锁A想使用锁C。这种情况就造成了死锁。

死锁:两个或两个以上的线程或进程在执行程序的过程中,因争夺资源而相互等待的一个现象。

import timeimport threadingthread_lock_1=threading.Lock()thread_lock_2=threading.Lock()def move_school():   thread_lock_1.acquire()   print("%s进入学校门使用了thread_lock_1"%threading.current_thread())   time.sleep(3)   thread_lock_2.acquire()   print("%s进入教室门thread_lock_2"%threading.current_thread())   time.sleep(2)   thread_lock_2.release()   thread_lock_1.release()def move_home():   thread_lock_2.acquire()   print("%s出去教室门thread_lock_2"%threading.current_thread())   time.sleep(2)   thread_lock_1.acquire()   print("%s出去学校门thread_lock_1"%threading.current_thread())   time.sleep(3)   thread_lock_1.release()   thread_lock_2.release()def game():   move_school()   move_home()thread_list=[]for i in range(0,2):   t=threading.Thread(target=game)   t.start()   thread_list.append(t)for t in thread_list:   t.join()print("-----------end----------")

打印结果如下:

进入学校门使用了thread_lock_1进入教室门thread_lock_2出去教室门thread_lock_2进入学校门使用了thread_lock_1........卡住

0x04 递归锁

死锁的原因是两个或两个以上的线程或进程在执行程序的过程中,因争夺资源而相互等待的一个现象。

那么我们在多线程中同时让一个线程只能使用一把锁,问题得以解决。但是这个锁特殊之处在于锁的内部还可以生成锁。所以这里称为递归锁。

递归锁:

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

二者的区别是:

递归锁可以连续acquire多次,每acquire一次计数器加1,只要计数不为0,就不能被其他线程抢到。只有计数为0时,才能被其他线程抢到acquire。释放一次计数器-1,而互斥锁只能加锁acquire一次,想要再加锁acquire,就需要release解之前的锁。

import timeimport threadingthread_lock_1=threading.RLock()def move_school():   thread_lock_1.acquire()   print("%s进入学校门使用了thread_lock_1"%threading.current_thread())   time.sleep(3)   thread_lock_1.acquire()   print("%s进入教室门thread_lock_2"%threading.current_thread())   time.sleep(2)   thread_lock_1.release()   thread_lock_1.release()def move_home():   thread_lock_1.acquire()   print("%s出去教室门thread_lock_2"%threading.current_thread())   time.sleep(2)   thread_lock_1.acquire()   print("%s出去学校门thread_lock_1"%threading.current_thread())   time.sleep(3)   thread_lock_1.release()   thread_lock_1.release()def game():   move_school()   move_home()thread_list=[]for i in range(0,2):   t=threading.Thread(target=game)   t.start()   thread_list.append(t)for t in thread_list:   t.join()print("-----------end----------")

执行结果如下

D:\py_project\venv\Scripts\python.exe D:/py_project/递归锁.py<Thread(Thread-1, started 16908)>进入学校门使用了thread_lock_1<Thread(Thread-1, started 16908)>进入教室门thread_lock_2<Thread(Thread-2, started 6292)>进入学校门使用了thread_lock_1<Thread(Thread-2, started 6292)>进入教室门thread_lock_2<Thread(Thread-1, started 16908)>出去教室门thread_lock_2<Thread(Thread-1, started 16908)>出去学校门thread_lock_1<Thread(Thread-2, started 6292)>出去教室门thread_lock_2<Thread(Thread-2, started 6292)>出去学校门thread_lock_1-----------end----------Process finished with exit code 0

0x05 信号量

假设停车场一次可以停五辆车,我们可以将并发调整为五threading.Semaphore(5),如果不适用信号量的话,1秒种会将50辆车全部停进去了。

Semaphore管理一个内置的计数器,每当调用acquire()时内置计数器-1;调用release() 时内置计数器+1;计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。

实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5):

import timeimport threadingsemaphore=threading.Semaphore(5)def stop_car(n):   semaphore.acquire()   time.sleep(1)   print("这是第%s车子"%n)   semaphore.release()thread_list=[]for x in range(0,50):   t=threading.Thread(target=stop_car,args=(x,))   t.start()   thread_list.append(t)for t in thread_list:   t.join()print("----------------end-------------")

打印结果会是5辆车一起打印出来。

python多线程_【python多线程02】各种线程锁相关推荐

  1. java 秒杀多线程_秒杀多线程系列 - 随笔分类 - Joyfulmath - 博客园

    随笔分类 - 秒杀多线程系列 秒杀多线程系列,该系列转载至CSDN MoreWindows: http://blog.csdn.net/morewindows/article/details/7392 ...

  2. python队列长度_[python模块]队列queue

    一.队列queue 队列queue 多应用在多线程场景,多线程访问共享变量. 对于多线程而言,访问共享变量时,队列queue的线程安全的. 因为queue使用了一个线程锁(pthread.Lock() ...

  3. 初识python 视频_#python day02 初识python 学习视频来源于 太白金星

    #python day02 初识python 学习视频来源于 太白金星 ''' 知识点:安装PyCharm''' # 设置鼠标条件字体大小:file ->settings # 搜索mouse E ...

  4. 27_多线程_第27天(线程安全、线程同步、等待唤醒机制、单例设计模式)_讲义...

    今日内容介绍 1.多线程安全问题 2.等待唤醒机制 01线程操作共享数据的安全问题 *A:线程操作共享数据的安全问题如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程 ...

  5. 27_多线程_第27天(线程安全、线程同步、等待唤醒机制、单例设计模式)

    今日内容介绍 1.多线程安全问题 2.等待唤醒机制 01线程操作共享数据的安全问题 *A:线程操作共享数据的安全问题如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程 ...

  6. wxpython 多线程_在wxPython中使用线程连续更新GUI的好方法?

    我正在开发一个使用pythonv2.7和wxpythonv3.0的GUI应用程序. 我必须不断更新我的图形用户界面,其中包含许多面板.每个面板包含一个wx.StaticText.我必须不断更新这些wx ...

  7. .net mysql 多线程_.Net多线程问题总结

    转自:http://www.cnblogs.com/yizhu2000/archive/2008/01/03/1011958.html 目录 基础篇 怎样创建一个线程 我只简单列举几种常用的方法,详细 ...

  8. amd同步多线程_多核多线程,Intel 和 AMD 具体差在哪里?

    CPU的多核多线程,如果不考虑至强的话,那么目前是AMD更强,MSDT强于Intel,HEDT一样也还是强于Intel.其实EPYC也一样可以强于至强,只是至强可以四路甚至八路,而EPYC应该只能两路 ...

  9. 汉诺塔问题递归算法python代码_[python]汉诺塔问题递归实现

    一.问题描述及算法步骤 汉诺塔问题的大意是有三根柱子a, b, c,现在a柱有N个盘子从下往上尺寸递减排列,要求: 1. 将a上的盘子移动到c柱上; 2. 每次移动一个盘子; 3. 柱子上的盘子始终必 ...

  10. python数据模型_#PYTHON#数据模型 | 学步园

    今天,谈谈python中的数据模型,当然你可以不了解这些东西,照样可以写出漂亮的python代码,但是"知其然知其所以然"是我的作风,总是不明白python的一些机制,心里很不爽. ...

最新文章

  1. 【Laravel】只保留Auth::routes()的登录,关闭Auth::routes()的注册、重置密码、验证路由
  2. android ui自动化测试框架有哪些,自动化测试框架对比(UIAutomator、Appium、Robotium)...
  3. (15)Verilog表达式与运算符-基本语法(三)(第3天)
  4. 学会CycleGAN进行风格迁移,实现自定义滤镜
  5. MySQL查询优化:查询慢原因和解决技巧
  6. 如何看待B站疑似源码泄漏的问题?
  7. GPIO的8种工作模式
  8. c 语言ifelse语句例子,C if else 语句
  9. HCIPHCIE【2019-4月-更新增加新题】221 65道新题
  10. Java导出excel合并单元格边框消失问题
  11. 7-5 华氏度转摄氏度(四舍五入) (5分) java pta
  12. 3Dmax导出插件制作
  13. 从苏宁电器到卡巴斯基(第二部)第06篇:我在卡巴的日子 VI
  14. 人生不该有如此压力,来吃下这口缓解焦虑的良药[50P]
  15. android 事例源码 搜集
  16. 活法 - 第四章 以利他心,度人生
  17. 比尔·盖茨的分布式爱情
  18. 计算机网络安全教育培训教程,网络安全培训教材(PPT 67页)
  19. Linux删除history历史命令记录
  20. 二、5.输入身份证号,输出此人的出生年月日。注意限制输入的身份证号只能为18位,第7位开始即为出生年月日

热门文章

  1. ssh登陆分布式服务器进行编程
  2. 程序猿接私活经验总结,来自csdn论坛语录
  3. 关于前端使用JavaScript无法实现canvas打印问题的解决
  4. Linux下undefined reference to ‘pthread_create’问题解决 zz
  5. java异常处理机制详解
  6. HttpRequestException encountered解决方法
  7. 如果我的接口必须返回Task,那么实现无操作的最佳方法是什么?
  8. 一个或多个实体的验证失败。 有关更多详细信息,请参见“ EntityValidationErrors”属性
  9. 为什么“ cd”在shell脚本中不起作用?
  10. Python中可以使用静态类变量吗?