python线程同步原语--源码阅读
前面两篇文章,写了python线程同步原语的基本应用。下面这篇文章主要是通过阅读源码来了解这几个类的内部原理和是怎么协同一起工作来实现python多线程的。
相关文章链接:python同步原语--线程锁
一、关于Condition类
Condition的用法:
用来记录线程的状态变量
查看Condition的源码,会看到作者给开发者提供的文档说明。‘Class that implemets a condition variable’写得很明白,这是一个用来记录线程状态的类。
1. Condition对象初始化
从这段代码可以看出,Condition使用了threading模块的Rlock类,关于Rlock的用法可以看我之前写的一篇文章python同步原语--线程锁。在对象初始化的同时,将Rlock的请求锁和释放锁方法赋给了内部的self.acquire和self.release对象方法。当初始化对象同时初始化这两个方法,也就是说,每个对象在实例化的时候都会实例一个新的可重入锁(RLock)。这样可以避免不同对象(condition实例对象)间对类中共享方法的争夺,避免出现死锁的问题。
这段代码非常的重要。如果熟悉python的上下文管理的朋友应该一看就明白,这是上下文管理中的进入和退出操作。当在调用with时,程序会自动调用_ _enter_ _方法,在程序执行完毕,退出此上下文环境时,自动调用_ _exit_ _方法。那么在这里的_ _enter_ _和_ _exit_ _方法分别有什么用呢?通过阅读源码发现,前者是调用了Rlock的acquier方法(获取锁),而后者调用了Rlock的release方法(释放锁)。在下面我会继续讲这两个方法在类中的作用。
2. wait()方法
源码中对wait()方法的定义是‘Wait untified or until a timeout occurs’。意思是阻塞等待知道有提示(notify)或者超时时间(timeout)的到达。
再看看wait()函数的内部逻辑
_is_owned()方法是判断此Condition对象是否有获取到锁,如果没有获取到锁(可能是可重入锁的获取次数已经达到预定值,不过这种情况很少发生),就会报出错误。接下来是对需要等待的程序进行一些列的处理。先是给这个程序分配锁,对它的程序空间和内部变量进行封锁。同时把这个加锁后的程序放进双端队列(deque)‘等待者们’中。
好像wait()方法的功能到此就结束了。但是注意到下面还有try函数块,旁边一行注释写着‘restore state no matter what’然后又举了一个KeyboardInterrupt的异常情况。意思是当出现了例如键盘输入ctrl+C这类操作的时候,程序如何退出阻塞。如果在调用wait方法的时候没有传入timeout参数,那么,等待者程序就会重新获取锁。如果有timeout参数,就会根据参数来确定退出阻塞的时间。这就是为什么我们有时在输入ctrl+C强行退出阻塞的时候,程序会等待一会儿才给出退出程序的提示的原因。
3. notify()方法
接下来这个notify()方法在Condition类中也是非常的重要(queue模块内部也调用了这个函数)
notify()方法内部实现:
notify直接翻译过来就是‘提示’的意思。那么为什么Condition对象需要‘提示’呢?阅读源码下来,其真正的功能不是提示,而是锁的释放,并且在释放了指定数量的waiters之后,顺便将他们从‘等待者们’队列中删除。如果直接理解为提示,就会很难理解了。但这是老外在定义函数时的写法,本人的理解是,有点像给阻塞的程序发出信号(提示),停止阻塞(释放锁),这么理解应该也算勉强解释得过去吧。
Condition内部另外还有一个notify_all()方法,这个方法对‘等待者们’队列中的所有的程序都发出‘提示’,释放锁,而没有像notify中那样有数量n的限制。
源码:
那么总结上面的Condititon内部的方法实现,可以看出,Condition类是为了实现一种状态的‘保存’,即在多线程编程的情况下,由于线程间共享空间而容易引发错误,往往需要让一些线程先执行,而后面的线程等待(阻塞)。那么如果这些程序需要阻塞等待,就会调用Condition类实例对象的wait方法,当结束等待的信号发出时,就会调用Condition的notify方法对队列中的程序进行释放锁操作。
二、关于Segmaphore和BoundedSegmaphore
如果在主机执行IO密集型任务的时候再执行这种短时间内完成大量任务(多线程)的程序时,计算机就有很大可能会宕机。
这时候就可以为这段程序添加一个计数器(counter)功能,来限制一个时间点内的线程数量。当每次进行IO操作时,都需要向segmaphore请求资源(锁),如果没有请求到,就阻塞等待,请求成功才就像执行任务。
那么segmaphore的内部实现是怎样的呢?实质上segmaphore也是锁,其内部也是通过Lock和Condition实现的。Lock是单锁,而segmaphore是可以自己定义的多锁。在初始化segmaphore时,需要传入参数counter。当线程向segmaphore请求资源(锁)时,内部的counter会自动减1。当释放资源(锁)的时,counter就会自动加1。
segmaphore主要有两个方法,acquire()和release()方法。
1. acquire()方法
官方的定义:
def acquire(self, blocking=True, timeout=None):
当内部的counter(源码实际上是用value变量保存)等于0的时候,其他线程acquire会阻塞。这个时候,之前向segmaphore发出请求并获得锁的线程,它们如果同时执行完任务并希望释放锁时,那么锁的释放是随机的。任何一个完成任务的线程都会释放锁,这个顺序跟线程向请求的时间和任务完成的时间是没有任何关系的。
参数的解析:
1)blocking:默认为True,当线程请求不到资源的时候,会阻塞等待。如果设置为False,则线程请求不到资源时不会阻塞。
2)timeout:如果设置blocking = True,即默认值时,经过timeout时间会退出阻塞。
2. release()方法
这个方法与Lock的release方法很像,具体可以看看我之前写的关于锁的一篇文章。
源码:
解析:
当一个实例请求释放锁的时候,segmaphore内部的_value会自动加1,同时调用notify方法,将被锁住的线程‘唤醒’。
三、关于Event类
阅读源码知道,Event是也基于Condition和Lock实现的
1. set()方法
在 python--线程同步原语 这篇文章我曾经写过一个案例,在进程中调用一次event.set()函数就可以一次性通知(释放)所有阻塞的等待的锁。其内部实现的原理在这里,最关键的一个方法是notify_all()。
在调用set()方法的时候,方法内部会将_flags设置为True,即等待的事件会退出阻塞。
2. clear()方法
clear()方法不同作太多解析了,就是内部的_flags重新设置为False
3. wait()方法
wait()方法的定义:
def wait(self, timeout=None):
内部实现:
原理还是很简单的,实际上Event的wait()方法正是调用了Condition中的实例方法wait()。调用wait()方法的时候可以传入参数timeout(超时时间),作为Event事件自动退出阻塞的时间界限。
python线程同步原语--源码阅读的更多相关文章
- 一个python线程池的源码解析
python为了方便人们编程高度封装了很多东西,比如进程里的进程池,大大方便了人们编程的效率,但是默认却没有线程池,本人前段时间整理出一个线程池,并进行了简单的解析和注释,本人水平有限,如有错误希望高 ...
- C# Synchronized 和 SyncRoot 实现线程同步的源码分析及泛型集合的线程安全访问
转载:http://blog.csdn.net/zztfj/article/details/5640889 Synchronized vs SyncRoot 我们知道,在.net的一些集合类型中,譬如 ...
- Python线程池ThreadPoolExecutor源码分析
在学习concurrent库时遇到了一些问题,后来搞清楚了,这里记录一下 先看个例子: import time from concurrent.futures import ThreadPoolExe ...
- python线程threading.Timer源码解读
threading.Timer的作用 官方给的定义是: """Call a function after a specified number of seconds: t ...
- Netty源码阅读之如何将TCP的读写操作和指定线程绑定
原文链接:http://xueliang.org/article/detail/20200712234015993 前言 在Netty的线程模型中,对于一个TCP连接的读写操作,都是由一个单线程完成的 ...
- 《java.util.concurrent 包源码阅读》13 线程池系列之ThreadPoolExecutor 第三部分
这一部分来说说线程池如何进行状态控制,即线程池的开启和关闭. 先来说说线程池的开启,这部分来看ThreadPoolExecutor构造方法: public ThreadPoolExecutor(int ...
- 线程池:ThreadPoolExcutor源码阅读
ThreadPoolExcutor源码流程图:(图片较大,下载再看比较方便) 线程池里的二进制奥秘 前言: 线程池的五种状态state(RUNNING.SHUTDOWN.STOP.TIDYING.TE ...
- python3 源码阅读-虚拟机运行原理
阅读源码版本python 3.8.3 参考书籍<<Python源码剖析>> 参考书籍<<Python学习手册 第4版>> 官网文档目录介绍 Doc目录主 ...
- 【原】AFNetworking源码阅读(一)
[原]AFNetworking源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 AFNetworking版本:3.0.4 由于我平常并没有经常使用AFNetw ...
随机推荐
- Python文件操作与函数目录
文件操作 python文件操作 函数 Python函数学习——初步认识 Python函数学习——作用域与嵌套函数 Python函数学习——匿名函数 python内置函数 Python函数学习——递归 ...
- nRF52832 SDK15.3.0 基于ble_app_uart demo FreeRTOS移植
参考资料:https://blog.csdn.net/u010860832/article/details/86235993 这里把移植经验记录下来,供有需要的同学参考,有不对的地方也请大家批评指正. ...
- 2.MySQL(二)
数据之表操作 1.创建表 语法:CREATE TABLE table_name (column_name column_type); create table student( -> id IN ...
- parseInt和map方法使用案例分析
["1","2","3"].map(parseInt) //[1,NaN,NaN] 先看map()方法 定义和用法 map() 方法返回一个 ...
- 全网最全的Windows下Anaconda2 / Anaconda3里正确下载安装OpenCV(离线方式和在线方式)(图文详解)
不多说,直接上干货! 说明: Anaconda2-5.0.0-Windows-x86_64.exe安装下来,默认的Python2.7 Anaconda3-4.2.0-Windows-x86_64.ex ...
- leetcode — two-sum
package org.lep.leetcode.twosum; import java.util.Arrays; import java.util.HashMap; import java.util ...
- 基于redis的分布式锁实现
1.分布式锁介绍 在计算机系统中,锁作为一种控制并发的机制无处不在. 单机环境下,操作系统能够在进程或线程之间通过本地的锁来控制并发程序的行为.而在如今的大型复杂系统中,通常采用的是分布式架构提供服务 ...
- MongoDB副本集(一主两从)读写分离、故障转移功能环境部署记录
Mongodb是一种非关系数据库(NoSQL),非关系型数据库的产生就是为了解决大数据量.高扩展性.高性能.灵活数据模型.高可用性.MongoDB官方已经不建议使用主从模式了,替代方案是采用副本集的模 ...
- Deploying Keras model on Tensorflow Serving--
keras训练了个二分类的模型.需求是把keras模型跑到 tensorflow serving上 (TensorFlow Serving 系统用于在生产环境中运行模型) keras模型转 tenso ...
- 学习 spring-boot (一)
学习文章来自:http://www.ityouknow.com/spring-boot.html Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初 ...