《python源码剖析》笔记 python多线程机制

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie

1.GIL与线程调度

Python中的线程是操作系统的原生线程,Python虚拟机使用一个全局解释器锁(Global Interpreter Lock)来互斥线程对Python虚拟机的使用

为了支持多线程机制,一个基本的要求就是需要实现不同线程对共享资源访问的互斥,所以引入了GIL。

GIL:在一个线程拥有了解释器的访问权之后,其他的所有线程都必须等待它释放解释器的访问权,即使这些线程的下一条指令并不会互相影响。

在调用任何Python C API之前,要先获得GIL

GIL缺点:多处理器退化为单处理器;优点:避免大量的加锁解锁操作

线程调度的两个问题:

1.何时挂起当前线程

Python在执行了N条指令之后,通过软件模拟了时钟中断,开始了线程调度机制

2.选择哪个等待的线程

Python借用了底层操作系统所提供的线程调度机制决定下一个进入解释器的线程。

2.Python Thread

Python中所提供的最基础的多线程机制的接口是thread module,用C实现

在thread module的基础上,Python提供了一个更高层的多线程机制接口,threading module

thread module中的多线程接口

static PyMethodDef thread_methods[] = {
    {"start_new_thread",        (PyCFunction)thread_PyThread_start_new_thread,
                            METH_VARARGS,
                            start_new_doc},
    {"start_new",               (PyCFunction)thread_PyThread_start_new_thread,
                            METH_VARARGS,
                            start_new_doc},
    {"allocate_lock",           (PyCFunction)thread_PyThread_allocate_lock,
     METH_NOARGS, allocate_doc},
    {"allocate",                (PyCFunction)thread_PyThread_allocate_lock,
     METH_NOARGS, allocate_doc},
    {"exit_thread",             (PyCFunction)thread_PyThread_exit_thread,
     METH_NOARGS, exit_doc},
    {"exit",                    (PyCFunction)thread_PyThread_exit_thread,
     METH_NOARGS, exit_doc},
    {"interrupt_main",          (PyCFunction)thread_PyThread_interrupt_main,
     METH_NOARGS, interrupt_doc},
    {"get_ident",               (PyCFunction)thread_get_ident,
     METH_NOARGS, get_ident_doc},
    {"_count",                  (PyCFunction)thread__count,
     METH_NOARGS, _count_doc},
    {"stack_size",              (PyCFunction)thread_stack_size,
                            METH_VARARGS,
                            stack_size_doc},
    {NULL,                      NULL}           /* sentinel */
};

在Python虚拟机启动时,多线程机制并没有激活,它只支持单线程,一旦用户调用thread.start_new_thread,明确指示Python虚拟机创建新的线程,

Python就能意识到用户需要多线程的支持,这个时候,Python虚拟机会自动创立多线程机制需要的数据结构、环境以及那个至关重要的GIL

建立多线程环境 PyEval_InitThreads()

typedef void *PyThread_type_lock;
static PyThread_type_lock interpreter_lock = 0; //这就是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();
}

Win32中,GIL 指向了PNRMUTEX类型的一个对象

typedef struct NRMUTEX{
	LONG owned;  //指示GIL是否可用
	DWORD thread_id;//获得GIL的线程id
	HANDLE hevent;//Win32平台下Event这个内核对象
}NRMUTEX, *PNRMUTEX;

PyThread_acquire_lock

两种工作方式

1.如果GIL不可用,不等待

GIL被初始化为-1,意味着GIL可用,当有线程要使用GIL时,将其置为0,表示GIL已经被一个线程占用了。

2.如果GIL不可用,通过WaitForSingleObject将自身挂起,直到别的线程释放GIL,然后由操作系统将自己唤醒。

当有一个线程开始等待GIL时,其owned会被增加1,当有线程释放GIL时,owned减1

创建线程 PyThread_start_new_thread

在bootstrap中,子线程完成了三个动作:

1.获得线程id

2.通知obj->done内核对象

3.调用t_bootstrap

在t_bootstrap中,子线程开始与主线程竞争GIL,进行PyEval_AcquireThread申请GIL,

获得GIL后通过PyEval_CallObjectWithKeywords调用 PyEval_EvalFrameEx,即Python的字节码引擎。

在PyEval_CallObjectWithKeyWords之后,子线程释放GIL,并完成销毁线程的收尾工作。

线程状态保护机制

在Python内部,维护着一个全局变量:PyThreadState *_PyThread_State_Current。当前活动线程所对应

的线程状态对象就保存在这个变量里,当Python调度线程时,会将被激活的线程所对应的线程状态对象

赋给 _PyThread_State_Current,使其始终保存着活动线程的状态对象。

Python内部会通过一个单向链表来管理所有的Python线程的状态对象。

static struct key *
find_key(int key, void *value)
{
    struct key *p, *prev_p;
	//[1]:获取当前线程的线程id,并锁住线程状态对象链表
    long id = PyThread_get_thread_ident();
	PyThread_acquire_lock(keymutex, 1);
    //[2]:遍历线程状态对象链表,寻找key和id都匹配的元素
	for (p = keyhead; p != NULL; p = p->next) {
        if (p->id == id && p->key == key)
            goto Done;
    }
    //[3]:如果[2]处搜索失败,则创建新的元素,并加入线程状态对象链表
	p = (struct key *)malloc(sizeof(struct key));
    if (p != NULL) {
        p->id = id;
        p->key = key;
        p->value = value;
        p->next = keyhead;
        keyhead = p;
    }
 Done:
	//[4]:释放锁住的线程状态对象链表
    PyThread_release_lock(keymutex);
    return p;
}

在上面的代码的[1]和[4]处,Python通过在 _PyGILState_Init中创建的keymutex来互斥对状态

对象列表的的访问。Python为状态对象列表所提供的接口就是链表的插入、删除和查询操作。

//查询操作
void *PyThread_get_key_value(int key)
//插入操作
int PyThread_set_key_value(int key, void *value)
//删除操作
void PyThread_delete_key(int key)

操作系统级的线程调度和Python级的线程调度是不同的。Python级的线程调度一定意味着GIL

拥有权的易手,而操作系统级的线程设计并不一定意味着GIL的易手。当所有的线程都完成了初始化

动作之后,操作线程调度和Python的线程调度才会统一。

3.Python线程的调度

Python的线程调度机制是内建在Python的解释器核心PyEval_EvalFrameEx中的

/*Interpreter main loop*/
PyObject *PyEval_EvalFrameEx(PyFrameObject *f){
	for(;;){
		if (--_Py_Ticker < 0) {
            //在切换线程之前,重置_Py_Ticker为100,为下一个线程做准备
            _Py_Ticker = _Py_CheckInterval;
            tstate->tick_counter++;
            if (interpreter_lock) {
                //[1]:撤销当前线程状态对象,释放GIL,给别的线程一个机会
                PyThreadState_Swap(NULL)
				PyThread_release_lock(interpreter_lock);
				//[2]:别的线程现在已经开始执行了,咱们重新再申请GIL,等待下一次被调度
                PyThread_acquire_lock(interpreter_lock, 1);
                PyThreadState_Swap(tstate)
            }
        }

    fast_next_opcode:
		//...
	}
}

PyEval_EvalFrameEx每执行一条字节码指令,_Py_Ticker就将减少1;当执行了_Py_CheckInterval条指令之后,

_Py_Ticker将减少到0,这就将进入线程调度了。

4.Python线程的用户级互斥与同步

内核级通过GIL实现的互斥保护了内核的共享资源,用户级互斥保护了用户程序中的共享资源

import thread
import time

input = None
lock = thread.allocate_lock() #创建一个Lock对象

def threadProc():
	while True:
		print 'sub thread id: ', thread.get_ident()
		print 'sub thread %d wait lock...' % thread.get_ident()
		lock.acquire()
		print 'sub thread %d get lock...' % thread.get_ident()
		print 'sub thread %d receive input : %s' % (thread.get_ident(), input)
		print 'sub thread %d release lock...' % thread.get_ident()
		lock.release()
		time.sleep(1)

thread.start_new_thread(threadProc, ())
print 'main thread id : ', thread.get_ident()
while True:
	print 'main thread %d wait lock...' % thread.get_ident()
	lock.acquire() #线程在用户级需要访问共享资源之前需要先申请用户级的lock
	print 'main thread %d get lock...' % thread.get_ident()
	input = raw_input()
	print 'main thread %d release lock...' % thread.get_ident()
	lock.release() #释放lock
	time.sleep(1)

在Win32平台下的Python实现中,用户级线程的互斥与同步机制是通过Event来完成的。

5.高级线程库——threading

import threading
import time

class MyThread(threading.Thread):
	def run(self):
		while True:
			print 'sub thread : ', threading._get_ident()
			time.sleep(1)

mythread = MyThread()
mythread.start()
while True:
	print 'main thread : ', threading._get_ident()
	time.sleep(1)

《python源码剖析》笔记 python多线程机制

时间: 2024-12-25 06:55:28

《python源码剖析》笔记 python多线程机制的相关文章

Python源码剖析笔记6-函数机制

Python的函数机制是很重要的部分,很多时候用python写脚本,就是几个函数简单解决问题,不需要像java那样必须弄个class什么的. 本文简书地址:http://www.jianshu.com/p/d00108741a18 1 函数对象PyFunctionObject PyFunctionObject对象的定义如下: typedef struct { PyObject_HEAD PyObject *func_code; /* A code object */ PyObject *func

Python源码剖析笔记5-模块机制

本文简书地址: http://www.jianshu.com/p/14586ec50ab6 python中经常用到模块,比如import xxx,from xxx import yyy这样子,里面的机制也是需要好好探究一下的,这次主要从黑盒角度来探测模块机制,源码分析点到为止,详尽的源码分析见陈儒大神的<python源码剖析>第14章. 1 如何导入模块 首先来看一个导入模块的例子.创建一个文件夹demo5,文件夹中有如下几个文件. [email protected] ~/demo5 $ ls

Python源码剖析笔记3-Python执行原理初探

Python源码剖析笔记3-Python执行原理初探 本文简书地址:http://www.jianshu.com/p/03af86845c95 之前写了几篇源码剖析笔记,然而慢慢觉得没有从一个宏观的角度理解python执行原理的话,从底向上分析未免太容易让人疑惑,不如先从宏观上对python执行原理有了一个基本了解,再慢慢探究细节,这样也许会好很多.这也是最近这么久没有更新了笔记了,一直在看源码剖析书籍和源码,希望能够从一个宏观层面理清python执行原理.人说读书从薄读厚,再从厚读薄方是理解了

python源码剖析笔记1——Python对象初见

python源码剖析笔记1--Python对象初见 工作整两年了,用python最多,然而对于python内部机制不一定都清楚,每天沉醉于增删改查的简单逻辑编写,实在耗神.很多东西不用就忘记了,比如C语言,正好,python源码用C写的,分析python源码的同时又能温故C语言基础,实在是件很好的事情.另外,还有陈儒大神的<python源码剖析>做指引,分析也不至于没头没脑.期望在一个月的业余时间,能有所小成,以此为记. 1 python中的对象 python中,一切东西都是对象,在c语言实现

Python源码剖析笔记4-内建数据类型

Python源码剖析笔记4-内建数据类型 Python内建数据类型包括整数对象PyIntObject,字符串对象PyStringObject,列表对象PyListObject以及字典对象PyDictObject等.整数对象之前已经分析过了,这一篇文章准备分析下余下几个对象,这次在<python源码剖析>中已经写的很详细的部分就不赘述了,主要是总结一些之前看书时疑惑的地方. 1 整数对象-PyIntObject 参见 python整数对象. 2 字符串对象-PyStringObject 2.1

Python源码剖析笔记2-Python整数对象

Python源码剖析笔记2-Python整数对象 本文简书地址: http://www.jianshu.com/p/0136ed90cd46 千里之行始于足下,从简单的类别开始分析,由浅入深也不至于自己丧失信心.先来看看Python整数对象,也就是python中的PyIntObject对象,对应的类型对象是PyInt_Type. 1 Python整数对象概览 为了性能考虑,python中对小整数有专门的缓存池,这样就不需要每次使用小整数对象时去用malloc分配内存以及free释放内存.pyth

Python源码剖析笔记0 ——C语言基础

python源码剖析笔记0--C语言基础回顾 要分析python源码,C语言的基础不能少,特别是指针和结构体等知识.这篇文章先回顾C语言基础,方便后续代码的阅读. 1 关于ELF文件 linux中的C编译得到的目标文件和可执行文件都是ELF格式的,可执行文件中以segment来划分,目标文件中,我们是以section划分.一个segment包含一个或多个section,通过readelf命令可以看到完整的section和segment信息.看一个栗子: char pear[40]; static

[Python源码剖析]字符缓冲池intern机制

static PyStringObject *characters[UCHAR_MAX + 1]; ... /* This dictionary holds all interned strings. Note that references to strings in this dictionary are *not* counted in the string's ob_refcnt. When the interned string reaches a refcnt of 0 the st

《python源码剖析》笔记 pythonm内存管理机制

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.内存管理架构 Python的内存管理机制都有两套实现:debug模式和release模式 Python内存管理机制的层次结构: 图16-1 第0层是操作系统提供的内存管理接口,如malloc.free 第1层是Python基于第0层操作系统的内存管理接口包装而成的,主要是为了处理与平台相关的内存分配行为. 实现是一组以PyMem_为前缀的函数族 两套接口:函数和宏. 宏,可以避免函数调