返回顶部
首页 > 资讯 > 后端开发 > Python >如何正确区分Python线程
  • 419
分享到

如何正确区分Python线程

2023-06-17 16:06:29 419人浏览 安东尼

Python 官方文档:入门教程 => 点击学习

摘要

这篇文章给大家介绍如何正确区分python线程,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。在Python语言中Python线程可以从这里开始与主线程对GIL的竞争,在t_bootstrap中,申请完了GIL,也就是说

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

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();        //[3]:创建线程       ident = PyThread_start_new_thread(t_bootstrap, (void*) boot);       return PyInt_FromLong(ident);   [thread.c]      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;           obj.func = func;       obj.arg = arg;       obj.done = CreateSemaphore(NULL, 0, 1, NULL);       rv = _beginthread(bootstrap, _pythread_stacksize, &obj);        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线程就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

--结束END--

本文标题: 如何正确区分Python线程

本文链接: https://lsjlt.com/news/290738.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 如何正确区分Python线程
    这篇文章给大家介绍如何正确区分Python线程,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。在Python语言中Python线程可以从这里开始与主线程对GIL的竞争,在t_bootstrap中,申请完了GIL,也就是说...
    99+
    2023-06-17
  • 如何正确删除数据库分区
    从DPF删除一个数据库分区决不是像直接编辑db2nodes.cfg那么简单,正确的做法是使用命令“db2stop drop partitionnum”。 如果你直接编辑db2nodes....
    99+
    2024-04-02
  • 如何正确使用Android线程详解
    前言 对于移动开发者来说,“将耗时的任务放到子线程去执行,以保证UI线程的流畅性”是线程编程的第一金科玉律,但这条铁则往往也是UI线程不怎么流畅的主因。我们在督促自己更多的使用...
    99+
    2022-06-06
    android线程 Android
  • Python线程池的正确使用方法
    目录Python线程池的正确使用1、为什么要使用线程池呢?2、线程池怎么用呢?3、如何非阻塞的获取线程执行的结果4、线程池的运行策略Python线程池的正确使用 1、为什么要使用线程...
    99+
    2024-04-02
  • php时区不正确如何解决
    本文小编为大家详细介绍“php时区不正确如何解决”,内容详细,步骤清晰,细节处理妥当,希望这篇“php时区不正确如何解决”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。解决方法:1、在php.ini文件中,设置“d...
    99+
    2023-07-05
  • win10系统安装固态硬盘如何正确分区
    这篇文章主要为大家展示了“win10系统安装固态硬盘如何正确分区”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“win10系统安装固态硬盘如何正确分区”这篇文章吧。一、固态硬盘的容量相比于机械硬盘...
    99+
    2023-06-27
  • python如何正确使用yield
    目录生成器nextsendthrowclose使用场景大集合的生成简化代码结构协程与并发总结生成器 如果在一个方法内,包含了 yield 关键字,那么这个函数就是一个「生成器」。 生成器其实就是一个特殊的迭代器,它...
    99+
    2022-06-02
    python yield 使用yield
  • 如何正确使用Python命令
    本篇内容主要讲解“如何正确使用Python命令”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“如何正确使用Python命令”吧!我想你最常用到的 Python 命令就是运行 Python 脚本文件...
    99+
    2023-06-16
  • 如何正确运行python命令
    本篇内容介绍了“如何正确运行python命令”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Python已经成为全球最受欢迎的编程语言之一。原...
    99+
    2023-06-16
  • Python中print如何正确使用
    Python中print如何正确使用,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。Python print会对输出的文本做自动的编码转换,而文件对象的write方法就不会做,因...
    99+
    2023-06-17
  • 如何正确进行Python编写
    这篇文章给大家介绍如何正确进行Python编写,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。Python编写的Zope是开放源代码领域使用最广泛和最容易理解的内容管理框架,而这正是Microsoft .NET的CLR技...
    99+
    2023-06-17
  • 如何让python程序正确高效地并发
    目录python线程何时需要拥有GIL?认知模型1:同一时刻只有一个线程运行python代码模型2:不保证每 5 毫秒释放一次 GIL模型3:非 Python 代码可以显式释放 GI...
    99+
    2024-04-02
  • MySQL分区表的正确使用方法
    MySQL分区表概述 我们经常遇到一张表里面保存了上亿甚至过十亿的记录,这些表里面保存了大量的历史记录。 对于这些历史数据的清理是一个非常头疼事情,由于所有的数据都一个普通的表里。所以只能是启用一个或多个...
    99+
    2024-04-02
  • Windows7-如何正确卸载程序
    要正确卸载程序,您可以按照以下步骤操作:1. 打开控制面板:点击开始菜单,然后选择“控制面板”。2. 在控制面板中找到“程序”或“程...
    99+
    2023-09-08
    Windows7
  • Python 教程:如何在 bash 中设置正确的 path?
    在学习 Python 的过程中,我们经常需要使用一些第三方库或者工具,而这些工具一般都需要在系统的 path 中设置,以便我们可以在命令行中直接调用它们。本文将介绍如何在 bash 中设置正确的 path,以便我们可以方便地使用 Pyth...
    99+
    2023-07-03
    教程 bash path
  • 如何正确理解python装饰器
    目录一、闭包二、装饰器三、带参数的装饰器四、类装饰器一、闭包 要想了解装饰器,首先要了解一个概念,闭包。什么是闭包,一句话说就是,在函数中再嵌套一个函数,并且引用外部函数的变量,这就是一个闭包了。光说没有概念,直接上...
    99+
    2022-06-02
    python 装饰器
  • 如何正确的使用Python闭包
    如何正确的使用Python闭包?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。一、Python 中的作用域规则和嵌套函数每当执行一个函数时,就会创建一个新的局部命...
    99+
    2023-06-15
  • Python中如何正确记录日志?
    Python是一种高级编程语言,它被广泛应用于各种领域,包括Web开发、数据科学和人工智能等。在开发Python应用程序时,日志记录是一个重要的任务,它可以帮助开发人员跟踪应用程序的行为并排查错误。本文将介绍如何在Python中正确记录日志...
    99+
    2023-08-11
    日志 numy load
  • 详解Redis单线程的正确理解
    很多同学对Redis的单线程和I/O多路复用技术并不是很了解,所以我用简单易懂的语言让大家了解下Redis单线程和I/O多路复用技术的原理,对学好和运用好Redis打下基础。 一、R...
    99+
    2024-04-02
  • 关于Redis单线程的正确理解
    很多同学对Redis的单线程和I/O多路复用技术并不是很了解,所以我用简单易懂的语言让大家了解下Redis单线程和I/O多路复用技术的原理,对学好和运用好Redis打下基础。 一、R...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作