python多线程机制
Python中的线程从一开始就是操作系统的原生线程。而Python虚拟机也同样使用一个全局解释器锁(Global Interpreter Lock,GIL)来互斥线程多Python虚拟机的使用。
- GIL与线程调度
为了理解Pyhon为什么需要GIL,考虑这样的情形:假设有两个线程A B,在两个线程中,都同时保存着对内存中同一对象obj的引用,也就是说,这事obj->ob_refcnt的值为2.如果A销毁对obj的引用,显然,A将通过Py_DECREF调整obj的引用计数值。外面知道,py_DECREF的整个动作可以分为两个部分:
--obj ->ob_refcnt;
if(obj->ob_refcnt == 0) destory object and free memory.
如果A执行完第一个动作后,obj->ob_refcnt的值变为1,不幸的是,在这里时候线程调度机制将A挂起,唤醒了B。更为不幸的是,B同样也开始销毁对obj的引用。B完成第一个动作后,obj ->ob_refcnt为0,B是一个幸运儿,它没有被线程调度打断,而是顺利完成了接下来的第二个动作,将对象销毁,内存释放。好了吗,现在A又被重新唤醒,可现在已是物是人非,obj ->ob_refcnt已经被B减少到0,而不是当时的1.按照约定,A开始在一次地对已经销毁的对象进行对象销毁的内存释放动作。结局是什么?只有天知道………………
为了支持多线程机制,一个基本的要求就是需要实现不同线程对共享资源访问的互斥。Python也不例外,这正是引入GIL的根源所在。Python中的GIL是一个非常霸道的互斥实现,正如它的名字所暗示的,GIL是一个解释器(Interpreter)。也就是说,在一个线程拥有了解释器的访问权之后,其他的所有线程都必须等待它释放解释器的访问权,即使这些线程的下一条指令并不会互相影响。初看上去,这样的保护机制粒度太大了,我们似乎只需要将可能被多个线程共享的资源保护起来即可,对于不会被多个线程共享的资源,完全可以不用保护。实际上,在Python发展的历史中,的确出现过这样的解决方案,但令人惊奇的,这样的方案在单处理器上的多线程实现效率上却没有GIL的方案好,所以现在python中的多线程机制是在GIL的基础上实现的。
当然,这样的方案也就意味着,无论如何,在同一时间,只能有一个线程能访问python所提供的API。注意这里的同一时间对于单处理器是毫无意义的,因为单处理器的本质是不可能并行的,但是多处理器就完全不同了,同一时间,的确可以有多个线程独立运行,然而python的GIL限制了这样的情形,是的多处理器最终退化为单处理器,性能大打折扣。
2.
对于python而言,字节码解释器是python的核心所在,所以Python通过GIL来互斥不用线程对解释器的使用。
如图所示,A B C都需要使用解释器来执行字节码,以完成某种计算,但是在这之前,他们必须获得GIL,因为GIL把守这通往字节码解释器的大门。当A获得GIL之后,其他两个线程B C只能等待A释放GIL后,才能进入解释器,执行一些计算。
实际上,Python的GIL背后所保护的不仅仅是Python的解释器,同样还有Python的C API,在C/C++和Python的混合开发中,在涉及到原生线程和Python线程的相互协作时,也需要GIL进行互斥。
那么A在何时释放GIL呢?如果等到A使用完解释器之后,才释放GIL,这也就意味着,并行计算退化了为了串行的计算,毫无疑问,Python拥有一探线程的调度机制。
对于线程的调度机制而言,同操作系统的进程调度一样,最关键要解决两个问题:
- 在何时挂起当前的线程,选择处于等待状态的下一个线程?
- 在众多的处于等待状态的线程中,选择激活哪一个线程?
在python多线程的机制中,这两个问题是分别由不同的层次解决的。对于何时进行线程调度的问题,是由python自身决定的。考虑一下操作系统是如何进行进程的切换的。当一个进程执行了一段时间后,发生了时钟中断,操作系统响应时钟中断,并在这时开始进行进程的调度。同样,python中也是通过软件模拟了这样的时钟中断,来激活线程的调度。我们知道,python的字节码解释器的工作原理是按照指令的顺序一条一条的顺序执行,Python内部维护着一个数值,这个数值就是Python内部的时钟,如果这个数值为N,则意味着Python在执行了N条指令以后应该立即启动线程调度机制。
import sys
print sys.getcheckinterval()
上面代码的执行结果,Python默认是在执行了100条指令后启动线程调度机制。实际上,这个值不仅仅用来进行线程调度,在内部,Python也使用它来检查是否有异步的时间(envent)发生,需要处理。我们可以通过 sys.setcheckinterval() 来调节这个值。
那么究竟python会在众多等待线程中选择哪一个幸运儿呢?答案是,不知道。对于这个问题,Python完全没有插手,而是交给了底层的操作系统来解决。也就是说python借用了底层操作系统所提供的线程调度机制来决定下一个进入Python解释器的线程究竟是谁。
这一点至关重要,这就意味着Python中的线程实际上就是操作系统所支持的原生线程,并不是模拟出来的。Python中的多线程机制也是建立在操作系统的原生线程的基础之上,对应不同的操作系统,有不同的实现,然而最终,在不同的原生线程基础上,Python提供了一套统一的抽象机制,给Python的使用者一个非常简单而方便的多线程工具箱,这就是python中的两个Module:thread以及在其之上的threading。
python多线程机制的更多相关文章
- day-3 python多线程编程知识点汇总
python语言以容易入门,适合应用开发,编程简洁,第三方库多等等诸多优点,并吸引广大编程爱好者.但是也存在一个被熟知的性能瓶颈:python解释器引入GIL锁以后,多CPU场景下,也不再是并行方式运 ...
- Python GIL 多线程机制 (C source code)
最近阅读<Python源码剖析>对进程线程的封装解释: GIL,Global Interpreter Lock,对于python的多线程机制非常重要,其如何实现的?代码中实现如下: 指向一 ...
- 【python,threading】python多线程
使用多线程的方式 1. 函数式:使用threading模块threading.Thread(e.g target name parameters) import time,threading def ...
- <转>Python 多线程的单cpu与cpu上的多线程的区别
你对Python 多线程有所了解的话.那么你对python 多线程在单cpu意义上的多线程与多cpu上的多线程有着本质的区别,如果你对Python 多线程的相关知识想有更多的了解,你就可以浏览我们的文 ...
- Python多线程和Python的锁
Python多线程 Python中实现多线程有两种方式,一种基于_thread模块(在Python2.x版本中为thread模块,没有下划线)的start_new_thread()函数,另一种基于th ...
- 【跟我一起学Python吧】Python 多线程
其实自我感觉Python的多线程很类似于Java的多线程机制,但是比JAVA的多线程更灵活.在早期的Python多线程实现中,采用了thread模块.例如: from time import ctim ...
- 搞定python多线程和多进程
1 概念梳理: 1.1 线程 1.1.1 什么是线程 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发 ...
- Python多线程练习(threading)
这几天学习python多线程的时候,试了几次thread模块和threading模块,发现thread模块非常的不好用.强烈不建议大家使用thread,建议使用threading模块,此模块对thre ...
- 进程,线程,GIL,Python多线程,生产者消费者模型都是什么鬼
1. 操作系统基本知识,进程,线程 CPU是计算机的核心,承担了所有的计算任务: 操作系统是计算机的管理者,它负责任务的调度.资源的分配和管理,统领整个计算机硬件:那么操作系统是如何进行任务调度的呢? ...
随机推荐
- ContextSwitchDeadlock was detected Message(读取注册表时出现).
google的时候,在StackOverflow中得到个暂时解决的方法: http://stackoverflow.com/questions/2797677/contextswitchdeadloc ...
- 关于webservice不支持方法重载的解决办法
今天在写WebService时,出现了这样的错误: Count(Int32, Int32) 和 Count(Int32) 同时使用消息名称“Count”.使用 WebMethod 自定义特性的 Mes ...
- php100视频原始地址列表整理:
php100视频原始地址列表整理: 教程名称 . 1:环境配置与代码调试 2:PHP的数据类型与源码调试 3:常用PHP运算类型介绍与应用 4: PHP条件语句介绍与应用 5:PHP循环语句的介绍与应 ...
- HTTP请求返回的NSData无法转换为NSString
最近在做的一个项目中有一个功能是有一个网页,模拟http请求获取到这个网页返回的相应的数据. 在请求完成后获取到的数据为NSData类型,按照我们通常的转换为NSString的方法: NSString ...
- 12XML(可扩展标记语言)
XML:eXtensible Markup Language 什么是标记语言?什么是标记? 标记(Markup):文档中任何不想被打印输出的部分(不是真正的文档内容,联想读书时做的“读书笔记”,在旁边 ...
- (JAVA)从零开始之--打印流PrintStream记录日志文件
这里的记录日志是利用打印流来实现的. 文本信息中的内容为String类型.而像文件中写入数据,我们经常用到的还有文件输出流对象FileOutputStream. File file = new Fil ...
- sicily-2499 平方数
题目分析: 一个数可以表示成四种状态,所以可以用一个状态数组来存放该数由几个数的平方和表示.1.表示该数本身是完全平方.2.表示该数是由两个平方和3.表示三个.4.表示4个.一次遍历找出本身是完全平方 ...
- Java学习----HashMap原理
1.HashMap的数据结构 数组的特点是:寻址容易,插入和删除困难:而链表的特点是:寻址困难,插入和删除容易.那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的, ...
- 【转载】详细解读C#中的 .NET 弱事件模式
你可能知道,事件处理是内存泄漏的一个常见来源,它由不再使用的对象存留产生,你也许认为它们应该已经被回收了,但不是,并有充分的理由. 在这个短文中(期望如此),我会在 .Net 框架的上下文事件处理中展 ...
- C++实现base64编码
将昨天的php代码改造成C++ /*base_64.h文件*/ #ifndef BASE_64_H #define BASE_64_H /** * Base64 编码/解码 * @author lir ...