首页 > 编程语言 >c++多线程调用python

c++多线程调用python

时间:2023-01-30 17:35:37浏览次数:59  
标签:解释器 PyThreadState python 代码 c++ Python 线程 多线程

脚本语言是快速编写富有弹性的代码的重要方法之一,在 Unix 系统自动化管理中已经应用了多种脚本语言。现在,在许多应用开发中,也提供了脚本层,这大大方便用户实现通用任务自动处理或者编写应用扩展,许多成功的应用,诸如 GIMP、Emacs、MS Office、PhotoShop、AutoCAD 等都应用了脚本技术。在某种意义上,一切皆可脚本化。

在另一篇文章中,我们已经介绍了如何在 C 应用中嵌入 Python 语言,通过这项技术,可以让应用的高级用户来修改或定制化他们的程序,你可以充分利用 Python 的语言能力而不用自己去实现嵌入语言。Python 是一个不错的的选择,因为它提供了干净直观的 C 语言 API。关于如何在 C 应用中嵌入 Python 解释器,你可以参考:让Python成为嵌入式语言一文。

现在我们来更深入地探讨一些问题。 鉴于许多复杂的应用都会利用多线程技术,本文将着重介绍如何创建线程安全的界面来调用Python解释器。

这里的所有例子都是用 Python 2.7.2,所有的 Python 函数都以extern “C”定义,因此对于 C 和 C++,其使用是别无二致的。
Python C 和线程

在C程序中创建执行线程是很简单的。在 Linux 中,通常的做法是使用 POSIX 线程(pthread) API 并调用 pthread_create 函数。关于如何使用 pthreads,你可以参考 Felix Garcia 和Javier Fernandez 著的 “POSIX Thread Libraries”一文。为了支持多线程, Python 使用了互斥使访问内部数据结构串行化。这种互斥即 “全局解释器锁 – global interpreter lock”,当某个线程想使用 Python 的C API的时候,它必须获得 全局解释器锁,这避免了会导致解析器状态崩溃的竞争条件(race condition)。

互斥的锁定和释放是通过 PyEval_AcquireLock 和 Eval_ReleaseLock 来描述的。调用了 PyEval_AcquireLock 之后,可以安全地假定你的线程已经持有了锁,其他相关线程不是被阻塞就是在执行与 Python 解析器无关的代码。现在你可以任意调用 Python 函数了。一旦取得了锁,你必须确保调用 PyEval_ReleaseLock 来释放它,否则就会导致线程死锁并冻结其他 Python 线程。

更复杂的情况是,每个运行 Python 的线程维护着自己的状态信息。这些和特定线程相关的数据存储在称为 PyThreadState 的对象中。当在多线程应用中用 C 语言调用 Python API 函数时,你必须维护自己的 PyThreadState 对象以便能安全地执行并发的 Python 代码。

如果你对开发多线程应用相当有经验,你可能会发现全局解释器锁的概念相当不方便。不过,现在它已经不像首次出现时那样糟糕了。当 Python 对脚本进行解释时,它会定期切换出当前 PyThreadState 对象并释放全局解释器锁,从而将控制权释放给其他线程。之前被阻塞的线程可以试图锁定全局解释器锁从而被运行。有些时候,原来的线程会再次获得全局解释器锁再次切回解释器。

这意味着当调用 PyEval_SimpleString 时,即使你持有全局解释器锁,其他线程仍有机会被执行,这样的副作用无可避免。另外,当你调用以 C 语言写就的 Python 模块(包括许多内置模块) 存在着将控制权释放给其他线程的可能性。基于这个原因,当你用两个 C 线程来执行计算密集的 Python 脚本,它们确实能分享 CPU 时间并发运行,但由于全局解释器锁的存在,在多处理器的计算机上,Python 无法通过线程充分计算机的 CPU 处理能力。

启用线程支持
在多线程的 C 程序使用 Python API 之前,必须调用一些初始化例程。如果编译解释器库时启用了多线程支持(通常情况如此),你就有了一个是否启用线程的运行时选项。除非你计划使用线程,否则不建议启用该选项。未启用该选项,Python 可以避免因互斥锁定其内部数据结构而产生的系统开销。但是如果你打算用 Python 来扩展多线程应用,你就需要在初始化解释器的时候启用线程支持。我个人建议,应该在主线程执行时就初始化 Python,最好是在应用程序启动的时候,就调用下面两行代码:

// initialize Python
Py_Initialize();
// initialize thread support
PyEval_InitThreads();

这两个函数都返回 void,所以无需检查错误代码。现在,我们可以假定 Python 解释器已准备好执行 Python 代码。Py_Initialize 分配解释器库使用的全局资源。调用PyEval_InitThreads 则启用运行时线程支持。这导致 Python 启用其内部的互斥锁机制,用于解释器内代码关键部分的系列化访问。此函数的另一个作用是锁定全局解释器锁。该函数完成后,需要由用户负责释放该锁。不过,在释放锁之前, 你应该捕获当前 PyThreadState 对象的指针。后续创建新的 Python 线程以及结束使用 Python 时要正确关闭解释器,都需要用到该对象。下面这段代码用来捕获 PyThreadState 对象指针:

PyThreadState * mainThreadState = NULL;
// save a pointer to the main PyThreadState object
mainThreadState = PyThreadState_Get();
// release the lock
PyEval_ReleaseLock();


创建新的执行线程

在 Python 里,每个执行 Python 代码的线程都需要一个 PyThreadState 对象。解释器使用此对象来管理每个线程独立的数据空间。理论上,这意味着一个线程中的动作不会牵涉到另一个线程的状态。例如,你在一个线程中抛出异常,其他 Python 代码片段仍会继续运行,就好象什么事情都没有发生一样。你必须帮助 Python 管理每个线程的数据。为此,你需要为每个执行 Python 代码的 C 线程手工创建一个 PyThreadState 对象.要创建 PyThreadState 对象,你需要用到既有的 PyInterpreterState 对象。PyInterpreterState 对象带有为所有参与的线程所共享的信息。当你初始化 Python 时,它就会创建一个 PyInterpreterState 对象,并将它附加在主线程的 PyThreadState 对象上。你可以使用该解释器对象为你自己的 C 现成创建新的 PyThreadState。请参考下面代码

// get the global lock
PyEval_AcquireLock();
// get a reference to the PyInterpreterState
PyInterpreterState * mainInterpreterState = mainThreadState->interp;
// create a thread state object for this thread
PyThreadState * myThreadState = PyThreadState_New(mainInterpreterState);
// free the lock
PyEval_ReleaseLock();

执行 Python 代码
现在我们已创建 PyThreadState 对象,你的 C 线程就可以开始使用 Python API 执行 Python 脚本。从 C 线程执行 Python 代码时,你必须遵守一些简单的规则。首先,您在进行任何会改变当前线程状态的操作前必须持有全局解释器锁。第二,必须在执行任何 Python 代码之前,必须将该线程特定的 PyThreadState 对象加载到解释器。一旦您已经满足这些条件,您可以通过诸如 PyEval_SimpleString 函数来执行任意的 Python 代码,并记得在执行结束时切出 PyThreadState 对象并释放全局解释器锁。请参考下面代码,注意代码中“锁定、 切换、 执行、 切换,解锁”的对称关系:

// grab the global interpreter lock
PyEval_AcquireLock();
// swap in my thread state
PyThreadState_Swap(myThreadState);
// execute some python code
PyEval_SimpleString("import sys\n");
PyEval_SimpleString("sys.stdout.write(‘Hello from a C thread!\n‘)\n");
// clear the thread state
PyThreadState_Swap(NULL);
// release our hold on the global interpreter
PyEval_ReleaseLock();


清除线程
一旦你的 C 线程不再需要 Python 解释器,你必须释放相关资源。为此,需要删除该线程的 PyThreadState 对象,相关代码如下:

// grab the lock
PyEval_AcquireLock();
// swap my thread state out of the interpreter
PyThreadState_Swap(NULL);
// clear out any cruft from thread state object
PyThreadState_Clear(myThreadState);
// delete my thread state object
PyThreadState_Delete(myThreadState);
// release the lock
PyEval_ReleaseLock();

通过使用 Python API ,这个线程很有效率地完成了上述工作。现在你可以安全地调用 pthread_ext 来结束该线程的运行。
关闭解释器
一旦应用不在需要 Python 解释器,你可以用下面的代码将 Python 关闭掉:

// shut down the interpreter
PyEval_AcquireLock();
Py_Finalize();

注意:因为 Python 已经被关系,这里就不需要释放锁。请确保在调用 Py_Finalize 之前用 PyThreadState_Clear 和 PyThreadState_Delete 删除掉所有线程状态对象。

小结:
作为嵌入式语言,Python 是一个不错的选择。Python 解释器同时支持嵌入和扩展,它允许 C 应用程序代码和嵌入的 Python 脚本之间的双向通信。此外,多线程支持促进了与多线程应用程序的集成,而且不影响性能。

你可以从本文的后面下载有关案例Python embedded HTTP Server (29),该案例实现了一个内嵌 Python 解释器的多线程 HTTP 服务器。此外我推荐您去 http://www.python.org/docs/api/ 阅读有关的 Python C API 文档。另外 Python 解释器本身的代码也是一个很有价值的参考。

标签:解释器,PyThreadState,python,代码,c++,Python,线程,多线程
From: https://www.cnblogs.com/lidabo/p/17076752.html

相关文章

  • 在python代码中,写其他编程语言的hello world
    1.helloworld不论哪种编程语言,在你最开始学习时,都会给你一个在终端输出helloworld的示例print("helloworld")这已经成为一种惯例,最近在github上闲逛时,偶遇了一个特别有......
  • python 系统更新跟踪
    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++#需求:#动态捕获指定服务器最新发布接种应用系统路......
  • QT(c++) 线程 调用python问题
    1、背景简单说一下需求,Qt开发的上位机界面程序,需要调用Python编写的算法跑一个结果返回到界面上显示。2、度娘出一篇博客,按照步骤进行环境搭建和简单的代码测试......
  • 用python制作一个简单的zip压缩工具
    1.功能设计对文件或文件夹压缩,是日常工作中经常要做的事情,市面上也有非常多的压缩工具,在linux系统里,有zip,tar等命令可以用于压缩。最近学习了python的zipfile模块,它提供......
  • 掌握hashtable,深度理解python字典的数据结构
    文章目录​​1.hash函数​​​​2.hashtable​​​​2.1链地址法实现hashtable​​​​2.2解决冲突​​​​2.3开放寻址法实现hashtable​​​​2.4逻辑删除key​​​......
  • 忘掉python的os.path吧,pathlib巨好用
    1.pathlib取代os.path模块从python3.4开始,pathlib正式成为标准库,旨在取代老旧的os.path模块和一些os模块中对系统路径的操作。pathlib提供了表示文件系统路径的类,而os.pat......
  • python web框架多进程部署下数据库连接问题
    python常用的web框架,诸如flask,django,在生产部署时,都会选择多进程的部署方式,选用的中间件多为uwsgi或者gunicorn。如果项目里使用了数据库,那么就要考虑数据库连接在多进程下......
  • python使用正则表达式实现字符串替换
    python的字符串提供了replace方法,可以将子串替换成其他字符串,例如下面的代码name='flask_script'name=name.replace('_','-')print(name)#flask-script替换的前提......
  • C++子线程中调用python代码
    项目需要C++调用python的算法,由于python算法比较耗时,因此采用在C++里启动workingthread来调用python脚本,python代码里含有cv2.imread()等opencv的调用,在子线程里调用会卡......
  • 【Python笔记2.1】Python Unicode字符编解码
    以下部分参考[1],这里复制了其中一部分是为了防止原文被移动或删除。概述Python中有字符串类型(str)和字节类型(byte),以及Python编码中最常见也是最顽固的两个错误:Unic......