频 道 直 达 - 新闻 - 培训 - 软件 - 教程 - 前沿 - 组网 - 系统应用 - 安全 - 编程 - 存储 - 操作系统 - 数据库 - 服务器 - 专题 - 产品 - 案例库 - 读书 - 博客 - BBS
51CTO.COM_中国最大的网络技术网站
找资料:

15.3.1 建立多线程环境

作者: 陈儒著 出处:电子工业出版社  2008-07-23 16:46    砖    好    评论   进入论坛
阅读提示:《Python源码剖析--深度探索动态语言核心技术》第15章主要讲述的是Python 多线程机制,本小节为大家介绍的是 建立多线程环境。

15.3.1 建立多线程环境

多线程环境的建立,说得直白一点,主要就是创建GIL。我们已经知道GIL 对于Python的多线程机制的重要意义,然而这个GIL 到底是如何实现的呢,呃,这是一个很有趣的问题。

[pythread.h]
typedef void *PyThread_type_lock;
[ceval.c]
static PyThread_type_lock interpreter_
lock = 0; /* This is the GIL */
static long main_thread = 0;
void PyEval_InitThreads(void)
{
if (interpreter_lock)
return;
interpreter_lock = PyThread_allocate_lock();
PyThread_acquire_lock(interpreter_lock, 1);
main_thread = PyThread_get_thread_ident();
}

终于见识到了神秘的GIL(interpreter_lock),没想到吧,万万没想到,它居然指示一个简单的void*。但是转念一想,在C 中void*几乎可以是任何东西,这家伙,可是个万能容器啊。

可以看到,无论创建多少个线程,Python 建立多线程环境的动作只会执行一次。在PyEval_InitThreads 的开始,Python 会检查GIL 是否已经被创建,如果是,则不再进行任何动作,否则,就会创建这个GIL。创建GIL 的工作由PyThread_allocate_lock 完成,我们来看一看这个GIL 到底是何方神圣。

[thread_nt.h]
PyThread_type_lock PyThread_allocate_lock(void)
{
PNRMUTEX aLock;
if (!initialized)
PyThread_init_thread();
aLock = AllocNonRecursiveMutex() ;
return (PyThread_type_lock) aLock;
}

在这里,我们终于看到了Python 中多线程机制的平台相关性,在Python25\Python 目录下,有一大批thread_***.h 这样的文件,在这些文件中,包装了不同操作系统的原生线程,并通过统一的接口暴露给Python,比如这里的PyThread_allocate_lock 就是这样一个接口。我们这里的thread_nt.h 中包装的是Win32 平台的原生thread,在本章中后面的代码剖析中,还会有大量与平台相关的代码,我们都以Win32 平台为例。

在PyThread_allocate_lock 中,与PyEval_InitThreads 非常类似的,它会检查一个initialized 的变量,如果说GIL 指示着Python 的多线程环境是否已经建立,那么这个initialized 变量就指示着为了使用底层平台所提供的原生thread,必须的初始化动作是否完成。这些必须的初始化动作通常都是底层操作系统所提供的API,不同的操作系统可能需要不同的初始化动作。在Win32 平台下,不需要任何的初始化动作,所以PyThread_init_thread 的唯一作用就是设置initialized 变量:

[thread.c]
void PyThread_init_thread(void)
{
if (initialized)
return;
initialized = 1;
PyThread__init_thread();
}
[thread_nt.h]
static void PyThread__init_thread(void) { }
在PyThread_allocate_lock 中,出现了一个关键的结构体PNRMUTEX,我们发现,这个结构体是函数的返回值,实际上也就是PyEval_InitThread 中需要创建的那个interperter_lock(GIL)。原来GIL 就是这个家伙,我们来看一看它的真身。
 [thread_nt.h]
typedef struct NRMUTEX {
LONG owned ;
DWORD thread_id ;
HANDLE hevent ;
} NRMUTEX, *PNRMUTEX ;
在NRMUTEX 中,所有的数据成员的类型都是Win32 平台下的类型风格了,owned和thread_id 都很普通,而其中的HANDLE hevent 却值得注意,我们来看看AllocNon-RecursiveMutex 究竟为这个hevent 准备了什么。
[thread_nt.h]
PNRMUTEX AllocNonRecursiveMutex(void)
{
PNRMUTEX mutex = (PNRMUTEX)malloc(sizeof(NRMUTEX)) ;
if(mutex && !InitializeNonRecursiveMutex(mutex)) {
free(mutex);
Mutex = NULL;
}
return mutex ;
}
BOOL InitializeNonRecursiveMutex(PNRMUTEX mutex)
{
……
mutex->owned = -1 ; /* No threads have
entered NonRecursiveMutex */
mutex->thread_id = 0 ;
mutex->hevent = CreateEvent(NULL, FALSE, FALSE, NULL) ;
return mutex->hevent != NULL ; /* TRUE
if the mutex is created */
}


一切真相大白了,原来,GIL(NRMUTEX)中的hevent 就是Win32 平台下的Event这个内核对象,而其中的thread_id 将记录任一时刻获得GIL 的线程的id。

到了这里,Python 中的线程互斥机制的真相渐渐浮出水面,看来Python 是通过Win32下的Event 来实现了线程的互斥,熟悉Win32 的朋友马上就可能想到,与这个Event 对应的,必定有一个WaitForSingleObject。

在PyEval_InitThreads 通过PyThread_allocate_lock 成功地创建了GIL 之后,当前线程就开始遵循Python 的多线程机制的规则:在调用任何Python C API 之前,必须首先获得GIL。因此PyEval_InitThreads 紧接着通过PyThread_acquire_lock 尝试获得GIL。

 [thread_nt.h]
int PyThread_acquire_lock(PyThread_
type_lock aLock, int waitflag)
{
int success ;
success = aLock && EnterNonRecursiveMutex
((PNRMUTEX) aLock, (waitflag
== 1 ? INFINITE : 0)) == WAIT_OBJECT_0 ;
return success;
}
DWORD EnterNonRecursiveMutex(PNRMUTEX mutex, BOOL wait)
{
/* Assume that the thread waits successfully */
DWORD ret;
/* InterlockedIncrement(&mutex->owned) ==
0 means that no thread
currently owns the mutex */
if (!wait)
{
if (InterlockedCompareExchange((PVOID *)
&mutex->owned, (PVOID)0,
(PVOID)-1) != (PVOID)-1)
return WAIT_TIMEOUT ;
ret = WAIT_OBJECT_0 ;
}
else
ret = InterlockedIncrement(&mutex->owned) ?
/* Some thread owns the mutex, let's wait... */
WaitForSingleObject(mutex->hevent,
INFINITE) : WAIT_OBJECT_0 ;
mutex->thread_id = GetCurrentThreadId()
; /* We own it */
return ret ;
}


PyThread_acquire_lock 有两种工作方式,通过函数参数waitflag 来区分。这个waitflag 指示当GIL 当前不可获得时,是否进行等待,更直接地说,就是当前线程是否通过WaitForSingleObject 将自身挂起,直到别的线程释放GIL,然后由操作系统将自己唤醒。

如果waitflag 为0,Python 会检查当前GIL 是否可用,GIL 中的owned 是指示GIL是否可用的变量,在前面的InitializeNonRecursiveMutex 中我们看到这个值被初始化为-1,Python 会检查这个值是否为-1,如果是,则意味着GIL 可用,必须将其置为0,当owned 为0 后,表示该GIL 已经被一个线程占用,不再可用。对于我们这里分析的调用PyEval_InitThread 的主线程而言, 由于在初始化GIL 之后就调用PyThread_acquire_lock 申请GIL,到这时,并没有第二个线程被创建,所以主线程会轻而易举地获得GIL 的使用权。

注意这里的检查和更新owned 的操作是通过一个Win32 的系统API——Interlocked-CompareExchange——来完成的。这个API 是一个原子操作,其函数原形和功能如下。

 原形:InterlockedCompareExchange
(PLONG dest, long exchange, long compared)
功能:如果*dest == compared,那么*dest = exchange

与InterlockedCompareExchange 相同的,InterlockedIncrement 也是一个原子操作,其功能是将mutex->owned 的值增加1。从这里可以看到,当一个线程开始等待GIL时,其owned 就会被增加1。显然我们可以猜测,当一个线程最终释放GIL 时,一定会将GIL 的owned 减1,这样当所有需要GIL 的线程都最终释放了GIL 之后,owned 会再次变为-1,意味着GIL 再次变为可用。

为了清晰地展示这一点,我们现在就来看看PyThread_aquire_lock 的逆运算,PyThread_release_lock 每一个将从运行转态转为等待状态的线程都会在被挂起之前调用它以释放对GIL 的占有。

[thread_nt.h]
void PyThread_release_lock(PyThread_type_lock aLock)
{
LeaveNonRecursiveMutex((PNRMUTEX) aLock);
}
BOOL LeaveNonRecursiveMutex(PNRMUTEX mutex)
{
/* We don't own the mutex */
mutex->thread_id = 0 ;
return
InterlockedDecrement(&mutex->owned) < 0 ||
SetEvent(mutex->hevent) ; /* Other
threads are waiting, wake one on
them up */
}
[thread_nt.h]
void PyThread_release_lock(PyThread_type_lock aLock)
{
LeaveNonRecursiveMutex((PNRMUTEX) aLock);
}
BOOL LeaveNonRecursiveMutex(PNRMUTEX mutex)
{
/* We don't own the mutex */
mutex->thread_id = 0 ;
return
InterlockedDecrement(&mutex->owned) < 0 ||
SetEvent(mutex->hevent) ; /* Other
threads are waiting, wake one on
them up */
}

最终,一个线程在释放GIL 时,会通过SetEvent 通知所有在等待GIL 的hevent 这个Event 内核对象的线程,结合前面的分析,如果这时候有线程在等待GIL 的hevent,那么将被操作系统唤醒。这就是我们在前面介绍的Python 将线程调度的第二个难题委托给操作系统来实现的机制。

到了这时,调用PyEval_InitThread 的线程(也就是Python 主线程)已经成功获得了GIL,最后会调用PyThread_get_thread_ident(),通过Win32 的API:GetCurrent-ThreadId,获得当前Python 主线程的id,并将其赋给main_thread,main_thread 是一个静态全局变量,专职存储Python 主线程的线程id,用以判断一个线程是否是Python 主线程。

最后,我们在图15-4 中给出整个PyEval_InitThread 的函数调用关系。


 
图15-4 PyEval_InitThreads 中的
函数调用关系
【责任编辑:夏书 TEL:(010)68476606】

回书目   上一节   下一节
专题
Python实用指南
Head First EJB(中文版)
Python源码剖析--深度探索动态语言核心技术
ActionScript 3.0权威指南
C# 与VB.NET 网络通信开发实战
我也说两句

匿名发表

(如果看不清请点击图片进行更换)


中 国 最 大 的 网 络 技 术 网 站 ·
技 术 成 就 梦 想
订阅技术快讯
电子杂志下载
名称:SQL Server数据库管理精品黄皮书
简介:书中文章经过精挑细选,便于用户能根据自己的实际工作和学习,快速在本书寻找到相关资料。内容涵盖了SQL Server的安装与升级、语句查询、数据备份和恢复、自动化任务、数据同步、数据字典、安全和预防、性能和优化、集群等各方面应用信息,以及DBA管理人员在数据库管理工作中
名称:2007路由技术大全
简介:《2007路由技术大全》由51CTO.com网站特别策划制作,该书包括路由器技术、路由器产品、路由器配置、安全设置、路由器故障处理、路由器密码恢复,以及广大网友在实践使用中的心得经验和技巧文章,内容注重实用性,适用于初学者入门,也适合多年从业者提高,是一本实践和理论完
名称:网络安全精品应用黄皮书
简介:《2007精品网络安全黄皮书》包括了9个大类24个小类, 800余篇文章,内容包含了熊猫烧香病毒、DDOS攻击、ARP病等热点问题的介绍及解决方案。从病毒查杀、防范、系统、数据等各方面的安全设置到黑客技术的了解、防范,涉及到了安全应用的全部领域, 由浅至深内容全面。
CCNA认证考试Pass必备
CCNA认证考试Pass必备
无线网络环境
无线网络环境
无线路由器故障处理
无线路由器故障处理
· 无线路由器故障处理
· 解析35岁技术人的价值..
· 无线重中之重:安全问题
· 无线局域网基本知识
· 家庭无线局域网
· 华为七千人主动辞职规..
· 微软出价446亿美元收购..
· 虚拟化的“赤壁之战”
· Windows Server 2008专..
· 802.11n:下一代的无线..
· 脉冲无线电uwb专题
· AIX操作系统管理应用
· 云计算时代来临
· 求职必杀技 决战面试官
· 龙芯要做中国的“奔腾”
· 2008年上半年IT技术图..
ARP攻击防范与解决方案
ARP攻击防范与解决方案
SQL Server 2008/2005全解
SQL Server 2008/2005全解
SOA 面向服务架构
SOA 面向服务架构
· SOA 面向服务架构
· SQL Server 2008/2005..
· Apache技术专题
· 三层交换技术专题
· SQL Server入门到精通
· 无线网状网(MESH)
· Windows远程桌面应用
· C#技术开发指南
· Apache技术专题
· Windows集群服务应用
· C#技术开发指南
· 文档格式标准开战 OOXM..
· 路由器设置与口令恢复
· Linux 集群技术专题
· PHP开发应用手册
· SOA 面向服务架构
ARP攻击防范与解决方案
ARP攻击防范与解决方案
SQL Server 2008/2005全解
SQL Server 2008/2005全解
SQL Server入门到精通
SQL Server入门到精通
· SQL Server入门到精通
· SQL Server 2008/2005..
· SOA 面向服务架构
· Apache技术专题
· C#技术开发指南
· 三层交换技术专题
· Apache技术专题
· C#技术开发指南
· Windows远程桌面应用
· 企业数据恢复指南
· Windows集群服务应用
· 路由器设置与口令恢复
· Linux 集群技术专题
· SOA 面向服务架构
· 了解统一威胁管理(UTM)..
· 解析35岁技术人的价值..