在很多混合技术栈的项目中,我们可能会需要用Go或Rust作为主程序,调用Python脚本完成机器学习推理、数据清洗等特定任务。但不少人会发现,即使开启了多个协程或线程,Python脚本的执行还是串行化的,没法实现真正的并行。这背后主要是Python全局解释器锁的限制,以及Go和Rust常规调用Python的方式没有正确隔离运行环境导致的。

为什么常规调用无法实现真正并行
Python的全局解释器锁(GIL)会保证同一时刻只有一个线程执行Python字节码,即使在多核CPU上,多线程的Python程序也无法利用多核实现并行。如果我们在Go中用协程直接调用Python,或者在Rust中用线程直接拉起Python解释器,所有的Python脚本实例都会共享同一个GIL,最终结果就是串行执行。
要实现真正的并行,核心思路是让每个Python脚本实例运行在独立的进程中,这样每个进程都有自己独立的GIL,互相之间不会阻塞,就可以利用多核CPU实现并行执行。
Go调用Python脚本实现并行
Go本身支持多协程,但调用外部命令是操作系统级别的进程创建,我们可以利用Go的协程同时拉起多个Python进程,实现并行执行。下面的示例会同时启动3个Python脚本实例,每个实例执行不同的任务。
Go调用代码示例
package main
import (
"fmt"
"os/exec"
"sync"
"time"
)
func main() {
// 要执行的Python脚本路径,这里假设你有三个不同的脚本,或者同一个脚本传不同参数
scripts := []string{"./task1.py", "./task2.py", "./task3.py"}
var wg sync.WaitGroup
startTime := time.Now()
// 遍历脚本列表,为每个脚本启动一个Go协程,在协程中创建独立的Python进程
for _, script := range scripts {
wg.Add(1)
go func(s string) {
defer wg.Done()
// 执行Python脚本,这里的python命令需要确保环境中有对应解释器
cmd := exec.Command("python3", s)
// 获取执行输出
output, err := cmd.CombinedOutput()
if err != nil {
fmt.Printf("执行脚本%s出错:%v\n", s, err)
return
}
fmt.Printf("脚本%s执行完成,输出:%s\n", s, output)
}(script)
}
// 等待所有协程执行完成
wg.Wait()
fmt.Printf("所有任务执行完成,总耗时:%v\n", time.Since(startTime))
}对应的Python测试脚本示例
这里给出其中一个Python脚本的示例,其他两个可以类似编写,只是执行的任务不同:
import time
import sys
def task():
# 模拟耗时任务,比如数据处理或者模型推理
print(f"任务开始执行,进程ID:{sys.argv[0]}")
time.sleep(2)
print("任务执行完成")
if __name__ == "__main__":
task()这种方式下,每个exec.Command都会创建一个独立的Python进程,这些进程之间互不影响,GIL也不会互相阻塞,所以三个脚本会同时执行,总耗时大概是2秒左右,而不是串行的6秒。
Rust调用Python脚本实现并行
Rust的标准库也支持创建子进程,同时可以结合Rust的线程或者async运行时实现并行拉起多个Python进程。下面的示例用Rust的标准库线程来并行执行多个Python脚本。
Rust调用代码示例
use std::process::Command;
use std::thread;
use std::time::Instant;
fn main() {
let scripts = vec!["./task1.py", "./task2.py", "./task3.py"];
let mut handles = Vec::new();
let start_time = Instant::now();
// 遍历脚本列表,为每个脚本启动一个线程,在线程中创建Python子进程
for script in scripts {
let handle = thread::spawn(move || {
// 执行Python脚本
let output = Command::new("python3")
.arg(script)
.output()
.expect("执行Python脚本失败");
if output.status.success() {
println!("脚本{}执行完成,输出:{}", script, String::from_utf8_lossy(&output.stdout));
} else {
println!("脚本{}执行出错:{}", script, String::from_utf8_lossy(&output.stderr));
}
});
handles.push(handle);
}
// 等待所有线程执行完成
for handle in handles {
handle.join().unwrap();
}
println!("所有任务执行完成,总耗时:{:?}", start_time.elapsed());
}和Go的实现思路一致,每个线程中创建的Python子进程都是独立的,拥有自己的GIL,所以多个脚本可以同时执行,实现真正的并行。
优化建议
- 如果Python脚本执行频率很高,频繁创建进程会有开销,可以考虑预先启动固定数量的Python进程,用进程池或者消息队列的方式分发任务,减少进程创建销毁的成本。
- 如果需要传递复杂数据,可以用共享文件、Redis、消息队列等方式在Go/Rust主程序和Python脚本之间传递,避免直接通过标准输入输出传递大量数据导致阻塞。
- 注意Python环境的隔离,如果不同的脚本需要不同的依赖,可以用虚拟环境或者容器的方式隔离每个Python进程的运行环境,避免依赖冲突。
总结
利用Go或Rust调用Python脚本实现真正并行的核心,是规避Python GIL的限制,通过创建独立的Python进程让每个脚本实例拥有自己的运行环境。Go可以结合协程和exec.Command实现,Rust可以结合线程和std::process::Command实现,两者本质都是利用操作系统的进程隔离能力,让多个Python任务真正并行执行,充分利用多核CPU的性能。
GoRustPython脚本调用并行执行多进程修改时间:2026-05-28 21:16:32