关于GIL光放文档给出了这样的说法:全局解释器锁 (GIL) 对这种情况有所帮助,它确保只有一个线程可以同时使用 Python 解释器及其 API,而非 Python 操作(系统调用和扩展代码)可以解锁 GIL 。
fn main() {
let start = Instant::now();
let py_foo = include_str!("G:/Rust/Rust_Project/rust_senior/try_connnect/import_try.py");
let k = thread::spawn(||{ let mut h:u64 = 1;while h <= 100000000{ h += 1;}});
let q = thread::spawn(||{ let mut h:u64 = 1;while h <= 100000000{ h += 1;}});
// let m = thread::spawn(||{Python::with_gil(|py|{
// let func = PyModule::from_code(py,
// "def t():
// for i in range(8200000):
// pass
// ",
// "",
// ""
// ).unwrap().getattr("t").unwrap();
// func.call0();
// })});
// let n = thread::spawn(||{Python::with_gil(|py|{
// let func = PyModule::from_code(py,
// "def t():
// for i in range(8200000):
// pass
// ",
// "",
// ""
// ).unwrap().getattr("t").unwrap();
// func.call0();
// })});
k.join();
q.join();
// m.join();
// n.join
println!("time cost: {:?} ms", start.elapsed().as_millis());// ms
}
我们对这样的一份代码做测试:
k单独运行: 320ms
m单独运行: 345ms
kq一起运行: 368ms
km一起运行: 410ms
mn一起运行: 1137ms
这样的结果说明的问题已经不言而喻,对于两段Python代码只能串行,而Rust的多线程与Python代码线程之间没有必然关系,所以可以并行。
此外, PyO3允许我们传递实现了真并行函数给Python:
// 计算密集型函数
pub fn test_real_cocu(py:Python){
// 使用并行包需要使用allow_threads方法,该方法接受实现F类型参数,也就是闭包或者函数都接受
py.allow_threads(||{let mut h = 1; while h <= 900000000{ h += 1;}});
}
from rust_give_python import *
from concurrent.futures import ThreadPoolExecutor
import time
if __name__ == '__main__':
executor = ThreadPoolExecutor(max_workers=2)
a = time.time()
future_1 = executor.submit(
test_real_cocu,
)
future_2 = executor.submit(
test_real_cocu,
)
result_1 = future_1.result()
result_2 = future_2.result()
b = time.time()
print(b-a)
a = time.time()
test_real_cocu()
b = time.time()
print(b-a)
# 两个线程执行的时间比一个线程快了0.1秒,而且函数是计算密集型的,说明真的并行成功了
2.176496
2.018431
当然,直接用Threading封装也是可以的,结果相差不大,说明Rust真的为Python提供并行包
我们都知道Rust语法以及编译器对内存安全要求的严苛程度,没有与编译器抗争过的程序员不是好Rust 而对于Python这种依赖于GC的松散的内存管理结构来说实现共存不是一件容易的事情,虽然我们大部分人的目的不是Python->Rust而是Rust->Python
PYO3 提供了i昂中策略访问Python堆上的内存:GIL绑定的或“拥有”的引用,以及独立GIL的只能指针Py<Any>
PyO3的GIL 绑定的“拥有的引用”(&PyAny
等)通过确保 PyO3 的生命周期永远不会超过 Python GIL 的作用域(这一点我之前提到过)这意味着 PyO3 的大部分 API 是在GIL作用域的基础上使用的。
Python::with_gil(|py|{
let hello: &PyString = py.eval("\"Hello World!\"", None, None)?.extract()?;
})?;
这样的一段代码,我们在上面经常写但实际上暗藏玄机:
with_gil
虽然就如同字面意思申请GIL标志或者说加锁,担官网的解释为创建一个 GILPool
拥有引用指向的内存的对象 。在GIL作用域内的引用类型生命周期与GILPool绑定(比如本例中的hello),当with_gil()
闭包结束或GILGuard
fromacquire_gil()
被删除时, theGILPool
也被删除并且它拥有的变量的 Python 引用计数减少,**将它们释放到 Python 垃圾收集器。**没错,Rust没有释放它,而是交给了Python GC处理,这样我们不得不考虑下面这种情况:
Python::with_gil(|py| -> PyResult<()> {
for _ in 0..10 {
let hello: &PyString = py.eval("\"Hello World!\"", None, None)?.extract()?;
println!("Python says: {}", hello);
}
// There are 10 copies of `hello` on Python's heap here.
Ok(())
})?;
这种情况乍一看 hello 每次创建之后在循环作用域之外就会被清理,实际上hello是一个引用类型,在GIL锁内的引用类型,其生命周期与GILPool绑定,这意味着hello在每次循环后不会被清理掉这也就导致了出现了10个hello 所指向的内存副本存在于内存中,在GIL结束后才释放极大的消耗了内存,这种写法非常不可取
对于这种问题的解决也不是没有办法:
Python::with_gil(|py| -> PyResult<()> {
for _ in 0..10 {
let pool = unsafe { py.new_pool() };
let py = pool.python();
let hello: &PyString = py.eval("\"Hello World!\"", None, None)?.extract()?;
println!("Python says: {}", hello);
}
Ok(())
})?;
我们使用unsafe方法在循环内嵌套GIL令牌,这样引用变量的生命周期将与新的GIL令牌绑定。然鹅,用了unsafe模块就知道这玩应有点危险指数.
官网给出的原理:当它GILPool
被删除时,在此之后创建的所有 PyO3 拥有的引用GILPool
都将减少其 Python 引用计数,从而可能允许 Python 删除相应的 Python 对象。PyO3 的高级使用执行长时间运行的任务,从不释放 GIL,可能需要使用此 API 来清除内存,因为 PyO3 通常在释放 GIL 之前不会清除内存。
关于其安全性,可参见:https://pyo3.rs/main/doc/pyo3/marker/struct.Python.html#method.new_pool。
对于某些特殊的引用类型,或者说智能指针,没错,特指Py<Any>。对Py<Any>的克隆可以增加其引用计数,这意味着它可以比GC更加长寿(话说这词有点怪怪的),例子如下
let hello: Py<PyString> = Python::with_gil(|py| {
py.eval("\"Hello World!\"", None, None).unwrap().extract()
}).unwrap();
// 我们利用闭包的特性让Python执行hello
Python::with_gil(|py| {
println!("Python says: {}", hello.as_ref(py));
});
// 这里我们虽然将变量删除,然而Python 堆上的指向内存不会发生任何事情,因为如果我们不持有 GIL,就不会发生任何事情。
// 所以我们drop的操作应该放在GIL里面去进行运作!!
drop(hello); // Memory *not* released here.
// 最后打开GIL是因为hello的引用计数已经为0,让GIL触发Python GC回收内存
Python::with_gil(|py| {}
// Memory for `hello` is released here.
);
// 正确写法
let hello: Py<PyString> = Python::with_gil(|py| {
py.eval("\"Hello World!\"", None, None).unwrap().extract()
}).unwrap();
Python::with_gil(|py| {
println!("Python says: {}", hello.as_ref(py));
drop(hello);
});