如何正确区分Python线程

这篇文章给大家介绍如何正确区分Python线程,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

创新互联公司2013年开创至今,先为建水等服务建站,建水等地企业,进行企业商务咨询服务。为建水企业网站制作PC+手机+微官网三网同步一站式服务解决您的所有建站问题。

在Python语言中Python线程可以从这里开始与主线程对GIL的竞争,在t_bootstrap中,申请完了GIL,也就是说子线程也就获得了GIL,使其始终保存着活动线程的状态对象。

当PyEval_AcquireThread结束之后,子线程也就获得了GIL,并且做好了一切执行的准备。接下来子线程通过PyEval_ CallObjectWithKeywords,将最终调用我们已经非常熟悉的PyEval_EvalFrameEx。

也就是Python的字节码执行引擎。传递进PyEval_CallObjectWithKeywords的boot->func是一PyFunctionObject对象,正是therad1.py中定义的threadProc编译后的结果。在PyEval_CallObjectWithKeywords结束之后,子线程将释放GIL,并完成销毁线程的所有扫尾工作,到了这里,子线程就结束了。

从t_bootstrap的代码看上去,似乎子线程会一直执行,直到子线程的所有计算都完成,才会通过PyThreadState_DeleteCurrent释放GIL。如此一来,那主线程岂非一直都会处于等待GIL的状态?如果真是这样,那Python线程显然就不可能支持多线程机制了。

实际上在PyEval_EvalFrameEx中,图15-2中显示的Python内部维护的那个模拟时钟中断会不断地激活线程的调度机制,在子线程和主线程之间不断地进行切换。从而真正实现多线程机制,当然,这一点我们将在后面详细剖析。现在我们感兴趣的是子线程在PyEval_AcquireThreade中到底做了什么。

到这里,了解了PyEval_AcquireThread,似乎创建线程的机制都清晰了。但实际上,有一个非常重要的机制——线程状态保护机制——隐藏在了一个毫不起眼的地方:PyThreadState_New。

[threadmodule.c]   static PyObject* thread_PyThread_start_new_thread(PyObject *self, PyObject     *fargs)   {       PyObject *func, *args, *keyw = NULL;       struct bootstate *boot;       long ident;       PyArg_UnpackTuple(fargs, "start_new_thread", 2, 3, &func, &args, &keyw);       //[1]:创建bootstate结构       boot = PyMem_NEW(struct bootstate, 1);       boot->interp = PyThreadState_GET()->interp;       boot->funcfunc = func;       boot->argsargs = args;       boot->keywkeyw = keyw;       //[2]:初始化多线程环境       PyEval_InitThreads(); /* Start the interpreter's thread-awareness */       //[3]:创建线程       ident = PyThread_start_new_thread(t_bootstrap, (void*) boot);       return PyInt_FromLong(ident);   [thread.c]   /* Support for runtime thread stack size tuning.      A value of 0 means using the platform's default stack size      or the size specified by the THREAD_STACK_SIZE macro. */   static size_t _pythread_stacksize = 0;   [thread_nt.h]   long PyThread_start_new_thread(void (*func)(void *), void *arg)   {       unsigned long rv;       callobj obj;       obj.id = -1;    /* guilty until proved innocent */       obj.func = func;       obj.arg = arg;       obj.done = CreateSemaphore(NULL, 0, 1, NULL);       rv = _beginthread(bootstrap, _pythread_stacksize, &obj); /* use default stack size */       if (rv == (unsigned long)-1) {           //创建raw thread失败           obj.id = -1;       }       else {           WaitForSingleObject(obj.done, INFINITE);       }       CloseHandle((HANDLE)obj.done);       return obj.id;   }

这个机制对于理解Python线程的创建和维护是非常关键的。要剖析线程状态的保护机制,我们首先需要回顾一下线程状态。在Python中,每一个Python线程都会有一个线程状态对象与之关联。

在线程状态对象中,记录了每一个线程所独有的一些信息。实际上,在剖析Python的初始化过程时,我们曾经见过这个对象。每一个线程对应的线程状态对象都保存着这个线程当前的PyFrameObject对象,线程的id这样一些信息。有时候,线程是需要访问这些信息的。

比如考虑一个最简单的情形,在某种情况下,每个线程都需要访问线程状态对象中所保存的thread_id信息,显然,线程A获得的应该是A的thread_id,线程B亦然。倘若线程A获得的是B的thread_id,那就坏菜了。这就意味着Python线程内部必须有一套机制,这套机制与操作系统管理进程的机制非常类似。

我们知道,在操作系统从进程A切换到进程B时,首先会保存进程A的上下文环境,再进行切换;当从进程B切换回进程A时,又会恢复进程A的上下文环境,这样就保证了进程A始终是在属于自己的上下文环境中运行。

这里的线程状态对象就等同于进程的上下文,Python同样会有一套存储、恢复线程状态对象的机制。同时,在Python内部,维护着一个全局变量:PyThreadState * _PyThread- State_Current。

当前活动线程所对应的线程状态对象就保存在这个变量里,当Python调度线程时,会将被激活的线程所对应的线程状态对象赋给_PyThreadState_Current,使其始终保存着活动线程的状态对象。

这就引出了这样的一个问题:Python如何在调度进程时,获得被激活线程对应的状态对象?Python内部会通过一个单向链表来管理所有的Python线程的状态对象,当需要寻找一个线程对应的状态对象时。

  • 初次接触Python部署问题解析

  • 强大快捷的Python操作语言全解析

  • 对Python 调试器丰富资源介绍

  • 对Python交互式技巧总结之谈

  • 如何正确认识Python 源文件

就遍历这个链表,搜索其对应的状态对象。在此后的描述中,我们将这个链表称为“状态对象链表”。下面我们来看一看实现这个机制的关键数据结构在Python中,对于这个状态对象链表的访问,不必在GIL的保护下进行。

因为对于这个状态对象链表,Python线程会创建一个独立的锁,专职对状态对象链表进行保护。这个锁的创建是在Python进行初始化的时候完成的。PyThread_create_key将创建一个新的key。注意,这里的key都是一个整数,而且,当PyThread_create_key***次被调用时(在_PyGILState_Init中的调用正是***次调用)。

会通过PyThread_allcate_lock创建一个keymutex。根据我们前面的分析,这个keymutex实际上和GIL一样,都是一个PNRMUTEX结构体,而在这个结构体中,维护着一个Win32下的Event内核对象。这个keymutex的功能就是用来互斥对状态对象链表的访问。

在_PyGILState_Init中,创建的新key被Python维护的全局变量autoTLSkey接收,其中的TLS是Thread Local Store的缩写,这个autoTLSkey将用作Python保存所有线程的状态对象的一个参数。的key值。也就是说,状态对象列表中所有key结构体中的key值都会是autoTLSkey。哎,那位看官说了,你看PyThread_create_key返回的是nkeys的递增后的值啊。

就是说每create一次,得到的结果都是不同的,怎么能说所有的key都是一样的呢?事实上,在整个Python的源码中,PyThread_create_key只在_PyGILState_Init中被调用了,而这个_PyGILState_Init只会在Python运行时环境初始化时调用一次。

关于如何正确区分Python线程就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。


分享标题:如何正确区分Python线程
分享链接:http://myzitong.com/article/igpeds.html