起初的串行调度
串行调度,主要是执行效率低、没有充分利用资源。
串行调度的目的,主要是为了发现每一步操作的耗时问题。
Python 的多线程
当任务串行成功时,考虑使用并行方式,提高操作的效率。
使用背景:
- 同时可以运行 3 个任务,分别称为 convertA, convertB, convertC
- 每个任务内有系列的计算,需要串行
使用 Threading 效果远低于串行调度:
- 使用 threading 模块 threading.Thread() 创建线程,其中 target 参数值为需要调用的方法,同样将其他多个线程放在一个列表中,遍历这个列表就能同时执行里面的函数了
start = time.time()
threads = [threading.Thread(target=convertA, args=(sql_jar_path, odpscmd, replace_data, )),
threading.Thread(target=convertB, args=(sql_jar_path, odpscmd, replace_data, )),
threading.Thread(target=convertC, args=(sql_jar_path, odpscmd, replace_data, ))]
for t in threads:
# 启动线程
t.start()
end = time.time()
print(str(round("total time:", end-start,3))+'s')
-
使用串行方式和上述 threading 方式对比,串行时间用时 1736 s、threading 用时 2790 s
-
多线程比单线程慢的原因是,Python 的全局锁 GIL 导致多线程没有利用多核 CPU 的优势,且涉及到线程切换
-
全局锁 GIL
- 解释器的 C 语言实现内容,并行执行时线程不安全
- 因此通过 GIL 保护解释器,确保一个进程内只有一个线程执行
- GIL 的最大问题就是 Python 的多线程程序不能利用多核 CPU 的优势
- 使用 Python 多线程的程序只会在一个 CPU 核上运行
- GIL 只影响严重依赖 CPU 的程序
- 若程序大部分只涉及到 I/O,使用多线程很合适,因为线程大部分时间在等待
- 对于依赖 CPU 的程序,需要弄清楚执行的计算特点
- 注意:线程不是专门用来优化性能的
- 一个 CPU 依赖程序可能会使用线程来管理图形用户界面、网络连接或其他服务
- 此时若一个线程长期拥有 GIL,会导致其他非 CPU 型线程一直等待
- 一个 CPU 依赖程序可能会使用线程来管理图形用户界面、网络连接或其他服务
- 解决 GIL 的缺点,如下提供两种策略
Python 的多进程 multiprocessing.Pool
- 任务并行操作
print('Parent process %s.' % os.getpid())
p = Pool(4)
p.apply_async(convertA, args=(sql_jar_path, ods, replace_data, ))
p.apply_async(convertB, args=(sql_jar_path, ods, replace_data, ))
p.apply_async(convertC, args=(sql_jar_path, ods, replace_data, ))
p.close()
p.join()
print('All subprocesses done.')
-
subprocess 的使用
-
subprocess 模块是 Python 2.4 版本开始引入的模块,主要用来取代 一些旧的模块方法,如
os.system
、os.spawn
、os.popen
、commands.*
等. -
通过子进程来执行外部命令,并通过 input/output/error 管道,获取子进程的执行返回信息
-
# 方式 1:命令行拼接
subprocess.run([cmd_name, running_args, new_osql_path])
# 方式 2:直接使用命令行的脚本,需要 subprocess 去解析命令
cmdstr = spark_path + " --files " + upload_files + " " \
+ " --class " + class_name + " " + jar_path + " " + parameters
print(cmdstr)
subprocess.run(cmdstr, shell=True)
C 扩展编程
使用 C 计算密集型任务,跟 Python 独立,在工作的时候在 C 代码中释放 GIL
#include "Python.h"
...
PyObject *pyfunc(PyObject *self, PyObject *args) {
...
Py_BEGIN_ALLOW_THREADS
// Threaded C code
...
Py_END_ALLOW_THREADS
...
}