python调用C++并突破GIL限制(非多进程)

 python的多线程由于GIL的限制,无法使用多核,如果想使用多核就需要用到多进程,但多进程资源消耗巨大,所以比较合理的方法是在计算密集型任务上使用C/C++来实现构建python模块。

下面的示例利用了pybind11把c++函数封装成python类,然后再使用python来调用模块。

1.C++代码,指定pybind11来绑定C++的函数,这里绑定了thread_task和perform_multithreading两个函数

// example.cpp
#include <pybind11/pybind11.h>
#include <pybind11/functional.h>
#include <thread>
#include <vector>
#include <iostream>

namespace py = pybind11;

// 一个简单的多线程任务,用于模拟长时间运行的情况
void thread_task(int id, int iterations, py::function callback) {
    // 在这里执行需要关闭 GIL 的多线程任务
    int sum = 1;
    for (int i = i; i < iterations; ++i) {
        // 模拟一些计算或其他密集型任务
        sum=sum*i;
    }
    // 必须获取全局解释器锁才能执行py::function的callback函数
    py::gil_scoped_acquire acquire;
    callback(id, "Thread task completed");
}

void perform_multithreading(int num_threads, int iterations, py::function callback) {
    // 关闭全局解释器锁
    py::gil_scoped_release gil;

    // 创建线程组
    std::vector<std::thread> threads;
    for (int i = 0; i < num_threads; ++i) {
        threads.emplace_back(thread_task, i, iterations, callback);
    }

    // 等待所有线程完成
    for (auto &t : threads) {
        t.join();
    }
}

PYBIND11_MODULE(example, m) {
    m.def("perform_multithreading", &perform_multithreading, "Perform multithreading with GIL released");
    m.def("thread_task",&thread_task,"single task");
}

 2.编写setup.py利用setuptools和pybind11构建模块,这里需要确保你的环境已经安装了pybind11,可以使用”pip install pybind11“来安装。另外如果是windows电脑需要安装visual stido的C+/C++相关的模块(MSVC/C++CL支持/win10sdk等),安装中如果有其它问题请自行查找原因

# setup.py
import setuptools
from setuptools import setup, Extension
import os
import sys
import platform
import pybind11
# 修改为你自己的Pybind11路径
pybind11_path = pybind11.get_include()

# 根据不同平台设置不同的编译参数
extra_compile_args = []
if platform.system() == 'Windows':
    extra_compile_args.append('/std:c++latest')

ext_modules = [
    Extension(
        'example',
        ['example.cpp'],
        include_dirs=[pybind11_path],
        language='c++',
        extra_compile_args=extra_compile_args
    )
]

# 执行setup
setup(
    name='example',
    version='0.0.1',
    author='Your Name',
    description='A simple example of using Pybind11 with Setuptools on Windows',
    ext_modules=ext_modules,
)

 执行下面命令可得到example.so或者example.pyd的动态链接库

python setup.py build develop

 3. 使用测试样例测试效果

# test.py
import example
import time
import threading
from multiprocessing.dummy import Pool

def my_callback(id, message):
    pass
    #print(f"Thread {id} says: {message}\n")

def sum_thread(iterations,call_back):
    tid = threading.current_thread().ident
    example.thread_task(tid,iterations,my_callback)

t0 = time.time()
example.perform_multithreading(100, 5000000, my_callback)
print('C++多线程耗时:',time.time()-t0)



params = [5000000]*100
t0 = time.time()
with Pool(100) as run_pool:
    run_pool.map(lambda x:sum_thread(x,my_callback),params)
print('python多线程耗时:',time.time()-t0)

这里用了C++的多线程和python的多线程计算了100遍4999999!,我们可以看下消耗的时间:

python test.py
C++多线程耗时: 0.015626907348632812
python多线程耗时: 0.11573576927185059

实践证明使用C++多线程关闭GIL,在这个计算型任务上得到了进10倍的加速。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
如果要在 Go 中调用 Python 代码,且只能使用协程并发执行,可以使用 `go-python` 这个第三方库来解决 GIL 问题。 `go-python` 库是一个 Go 语言Python 语言之间的桥梁,它可以在 Go 中执行 Python 代码,并且可以通过 Go 协程并发执行 Python 代码,从而避开 GIL限制。 具体的步骤如下: 1. 安装 `go-python` 库,可以使用 `go get` 命令进行安装:`go get github.com/sbinet/go-python` 2. 在 Go 代码中导入 `go-python` 包。 3. 初始化 Python 解释器,可以使用 `go-python` 包中的 `Initialize()` 函数进行初始化。 4. 创建一个 Python 模块对象,可以使用 `go-python` 包中的 `NewModule()` 函数进行创建。 5. 将 Python 代码加载到模块对象中,可以使用 `go-python` 包中的 `AddCode()` 函数将 Python 代码添加到模块对象中。 6. 调用 Python 函数,可以使用 `go-python` 包中的 `Py.Run()` 函数来执行 Python 函数。 7. 可以使用 Go 协程并发执行 Python 代码,从而避开 GIL限制。 下面是一个简单的示例代码: ```go package main import ( "fmt" "github.com/sbinet/go-python" ) func main() { // 初始化 Python 解释器 python.Initialize() // 创建一个 Python 模块对象 module := python.NewModule("mymodule") // 将 Python 代码添加到模块对象中 err := python.AddCode(` def do_something(): # 在 Python 中执行一些任务,比如计算斐波那契数列 a, b = 0, 1 while True: yield a a, b = b, a + b `, module) if err != nil { fmt.Println("添加 Python 代码出错:", err) return } // 在 Go 协程中调用 Python 函数 for i := 0; i < 10; i++ { go func() { // 调用 Python 函数 result, err := python.Py.RunString("mymodule.do_something()", python.FileInput, module.Dict()) if err != nil { fmt.Println("调用 Python 函数出错:", err) return } // 输出结果 fmt.Println(result) }() } // 等待协程执行完成 select {} } ``` 在上面的示例中,我们使用 `go-python` 包初始化了 Python 解释器,并创建了一个名为 `mymodule` 的 Python 模块对象。然后将计算斐波那契数列的 Python 代码添加到模块对象中。在 Go 协程中调用 Python 函数 `do_something()`,使用协程并发执行 Python 代码,从而避开 GIL限制。 需要注意的是,使用 `go-python` 包在 Go 中调用 Python 代码需要注意一些细节,比如需要手动进行 Python 对象的引用计数管理,避免内存泄漏等问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值