当前位置:首页 » 编程语言 » python利用多核

python利用多核

发布时间: 2022-07-25 06:58:28

① 如何让一个python脚本跑满多核的CPU

python由于GIL的关系,python的多线程并没有发挥多核的作用,这些线程都是在在单核上跑的
所以要想发挥多核的作用,就需要使用多进程,尽可能的在每一个CPU核心上分配到一个python进程。
所以要想跑满多核CPU就得多进程多线程互相结合

② Python如何利用多核处理器

GIL 与 Python 线程的纠葛

GIL 是什么东西?它对我们的 python 程序会产生什么样的影响?我们先来看一个问题。运行下面这段 python 程序,CPU 占用率是多少?

# 请勿在工作中模仿,危险:)def dead_loop(): while True: passdead_loop()

答案是什么呢,占用 100% CPU?那是单核!还得是没有超线程的古董 CPU。在我的双核 CPU 上,这个死循环只会吃掉我一个核的工作负荷,也就是只占用 50% CPU。那如何能让它在双核机器上占用 100% 的 CPU 呢?答案很容易想到,用两个线程就行了,线程不正是并发分享 CPU 运算资源的吗。可惜答案虽然对了,但做起来可没那么简单。下面的程序在主线程之外又起了一个死循环的线程

import threadingdef dead_loop(): while True: pass# 新起一个死循环线程t = threading.Thread(target=dead_loop)t.start()# 主线程也进入死循环dead_loop()t.join()

按道理它应该能做到占用两个核的 CPU 资源,可是实际运行情况却是没有什么改变,还是只占了 50% CPU 不到。这又是为什么呢?难道 python 线程不是操作系统的原生线程?打开 system monitor 一探究竟,这个占了 50% 的 python 进程确实是有两个线程在跑。那这两个死循环的线程为何不能占满双核 CPU 资源呢?其实幕后的黑手就是 GIL。

GIL 的迷思:痛并快乐着

GIL 的全称为Global Interpreter Lock,意即全局解释器锁。在 Python 语言的主流实现 CPython 中,GIL 是一个货真价实的全局线程锁,在解释器解释执行任何 Python 代码时,都需要先获得这把锁才行,在遇到 I/O 操作时会释放这把锁。如果是纯计算的程序,没有 I/O 操作,解释器会每隔 100 次操作就释放这把锁,让别的线程有机会执行(这个次数可以通过sys.setcheckinterval来调整)。所以虽然 CPython 的线程库直接封装操作系统的原生线程,但 CPython 进程做为一个整体,同一时间只会有一个获得了 GIL 的线程在跑,其它的线程都处于等待状态等着 GIL 的释放。这也就解释了我们上面的实验结果:虽然有两个死循环的线程,而且有两个物理 CPU 内核,但因为 GIL 的限制,两个线程只是做着分时切换,总的 CPU 占用率还略低于 50%。

看起来 python 很不给力啊。GIL 直接导致 CPython 不能利用物理多核的性能加速运算。那为什么会有这样的设计呢?我猜想应该还是历史遗留问题。多核 CPU 在 1990 年代还属于类科幻,Guido van Rossum 在创造 python 的时候,也想不到他的语言有一天会被用到很可能 1000+ 个核的 CPU 上面,一个全局锁搞定多线程安全在那个时代应该是最简单经济的设计了。简单而又能满足需求,那就是合适的设计(对设计来说,应该只有合适与否,而没有好与不好)。怪只怪硬件的发展实在太快了,摩尔定律给软件业的红利这么快就要到头了。短短 20 年不到,代码工人就不能指望仅仅靠升级 CPU 就能让老软件跑的更快了。在多核时代,编程的免费午餐没有了。如果程序不能用并发挤干每个核的运算性能,那就意谓着会被淘汰。对软件如此,对语言也是一样。那 Python 的对策呢?

Python 的应对很简单,以不变应万变。在最新的 python 3 中依然有 GIL。之所以不去掉,原因嘛,不外以下几点:

  • 欲练神功,挥刀自宫:

    CPython 的 GIL 本意是用来保护所有全局的解释器和环境状态变量的。如果去掉 GIL,就需要多个更细粒度的锁对解释器的众多全局状态进行保护。或者采用 Lock-Free 算法。无论哪一种,要做到多线程安全都会比单使用 GIL 一个锁要难的多。而且改动的对象还是有 20 年历史的 CPython 代码树,更不论有这么多第三方的扩展也在依赖 GIL。对 Python 社区来说,这不异于挥刀自宫,重新来过。

  • 就算自宫,也未必成功:

    有位牛人曾经做了一个验证用的 CPython,将 GIL 去掉,加入了更多的细粒度锁。但是经过实际的测试,对单线程程序来说,这个版本有很大的性能下降,只有在利用的物理 CPU 超过一定数目后,才会比 GIL 版本的性能好。这也难怪。单线程本来就不需要什么锁。单就锁管理本身来说,锁 GIL 这个粗粒度的锁肯定比管理众多细粒度的锁要快的多。而现在绝大部分的 python 程序都是单线程的。再者,从需求来说,使用 python 绝不是因为看中它的运算性能。就算能利用多核,它的性能也不可能和 C/C++ 比肩。费了大力气把 GIL 拿掉,反而让大部分的程序都变慢了,这不是南辕北辙吗。

  • 难道 Python 这么优秀的语言真的仅仅因为改动困难和意义不大就放弃多核时代了吗?其实,不做改动最最重要的原因还在于:不用自宫,也一样能成功!

  • 其它神功

    那除了切掉 GIL 外,果然还有方法让 Python 在多核时代活的滋润?让我们回到本文最初的那个问题:如何能让这个死循环的 Python 脚本在双核机器上占用 100% 的 CPU?其实最简单的答案应该是:运行两个 python 死循环的程序!也就是说,用两个分别占满一个 CPU 内核的 python 进程来做到。确实,多进程也是利用多个 CPU 的好方法。只是进程间内存地址空间独立,互相协同通信要比多线程麻烦很多。有感于此,Python 在 2.6 里新引入了multiprocessing这个多进程标准库,让多进程的 python 程序编写简化到类似多线程的程度,大大减轻了 GIL 带来的不能利用多核的尴尬。

    这还只是一个方法,如果不想用多进程这样重量级的解决方案,还有个更彻底的方案,放弃 Python,改用 C/C++。当然,你也不用做的这么绝,只需要把关键部分用 C/C++ 写成 Python 扩展,其它部分还是用 Python 来写,让 Python 的归 Python,C 的归 C。一般计算密集性的程序都会用 C 代码编写并通过扩展的方式集成到 Python 脚本里(如 NumPy 模块)。在扩展里就完全可以用 C 创建原生线程,而且不用锁 GIL,充分利用 CPU 的计算资源了。不过,写 Python 扩展总是让人觉得很复杂。好在 Python 还有另一种与 C 模块进行互通的机制 : ctypes

    利用 ctypes 绕过 GIL

    ctypes 与 Python 扩展不同,它可以让 Python 直接调用任意的 C 动态库的导出函数。你所要做的只是用 ctypes 写些 python 代码即可。最酷的是,ctypes 会在调用 C 函数前释放 GIL。所以,我们可以通过 ctypes 和 C 动态库来让 python 充分利用物理内核的计算能力。让我们来实际验证一下,这次我们用 C 写一个死循环函数

  • extern"C"{ void DeadLoop() { while (true); }}

  • 用上面的 C 代码编译生成动态库libdead_loop.so(Windows 上是dead_loop.dll)

    ,接着就要利用 ctypes 来在 python 里 load 这个动态库,分别在主线程和新建线程里调用其中的DeadLoop

  • from ctypes import *from threading import Threadlib = cdll.LoadLibrary("libdead_loop.so")t = Thread(target=lib.DeadLoop)t.start()lib.DeadLoop()

  • 这回再看看 system monitor,Python 解释器进程有两个线程在跑,而且双核 CPU 全被占满了,ctypes 确实很给力!需要提醒的是,GIL 是被 ctypes 在调用 C 函数前释放的。但是 Python 解释器还是会在执行任意一段 Python 代码时锁 GIL 的。如果你使用 Python 的代码做为 C 函数的 callback,那么只要 Python 的 callback 方法被执行时,GIL 还是会跳出来的。比如下面的例子:

  • extern"C"{ typedef void Callback(); void Call(Callback* callback) { callback(); }}

  • from ctypes import *from threading import Threaddef dead_loop(): while True: passlib = cdll.LoadLibrary("libcall.so")Callback = CFUNCTYPE(None)callback = Callback(dead_loop)t = Thread(target=lib.Call, args=(callback,))t.start()lib.Call(callback)

  • 注意这里与上个例子的不同之处,这次的死循环是发生在 Python 代码里 (DeadLoop函数) 而 C 代码只是负责去调用这个 callback 而已。运行这个例子,你会发现 CPU 占用率还是只有 50% 不到。GIL 又起作用了。

    其实,从上面的例子,我们还能看出 ctypes 的一个应用,那就是用 Python 写自动化测试用例,通过 ctypes 直接调用 C 模块的接口来对这个模块进行黑盒测试,哪怕是有关该模块 C 接口的多线程安全方面的测试,ctypes 也一样能做到。

    结语

    虽然 CPython 的线程库封装了操作系统的原生线程,但却因为 GIL 的存在导致多线程不能利用多个 CPU 内核的计算能力。好在现在 Python 有了易经筋(multiprocessing), 吸星大法(C 语言扩展机制)和独孤九剑(ctypes),足以应付多核时代的挑战,GIL 切还是不切已经不重要了,不是吗。

③ python的多线程为什么不能利用多核CPU

因为python的解释器cpython中有 GIL全局解释器锁,他保证了解释器一次只能跑一个线程,并不能实现并行,而只能实现并发,所以就算你有再多的核,也不能用上。

④ Python 不能利用多核的问题以后能被解决吗

Ruby也有GIL,其实GIL并不是性能问题的根源,性能问题的根源是GC。
假设去掉GIL,像Java那样的多核多线程,你会面临更多头疼的OOM问题,以及GC问题,Java的一次Full GC是stop whole world的,你不希望你整个服务器8颗内核一起stop,等待GC完成吧?
所以Java这么多年疯狂投入努力改进VM的GC效率,搞出来各种GC算法,特别是并行GC算法。而且在大内存服务器上,为了提高GC效率,避免过大的内存堆扫描开销,Java现在也强调单机跑多进程呢。
所以你认为Python/Ruby去掉GIL,就解决问题了吗?事实上会引入更多更麻烦的问题。
如果为了提高IO并发性能,用协程就同样可以达到目的,Ruby现在原生支持协程,Lua则是协程方面效率极高的脚本语言,已经证明了这条路。
最后说到多核的问题,Ruby未来发展方向是MVM,即单进程里面跑多个VM,每个CPU内核跑一个VM,每个VM有自己的GC。这个办法其实很不错,既有效利用了进程共享内存,又可以支持多核并行,还解决了全局GC的性能问题。

⑤ python能多核并行吗

可以的,使用多进程就行

importmultiprocessingasmp
importtime

deffoo_pool(x):
time.sleep(2)
returnx*x

result_list=[]
deflog_result(result):
#Thisiscalledwheneverfoo_pool(i)returnsaresult.
#result_,notthepoolworkers.
result_list.append(result)

defapply_async_with_callback():
pool=mp.Pool()
foriinrange(10):
pool.apply_async(foo_pool,args=(i,),callback=log_result)
pool.close()
pool.join()
print(result_list)

if__name__=='__main__':
apply_async_with_callback()

⑥ 怎么让python用多个cpu

python由于GIL的关系,python的多线程并没有发挥多核的作用,这些线程都是在在单核上跑的所以要想发挥多核的作用,就需要使用多进程,尽可能的在每一个CPU核心上分配到一个python进程。所以要想跑满多核CPU就得多进程多线程互相结合

⑦ 为什么python下想要充分利用多核cpu,就用多进程

因为python多线程是只用一个cpu.
所以多个cpu, 你得用多进程, cpu自己调度,才能充分利用

⑧ python如何利用多核cpu

Python,想利用多核CPU,那么就应该进行处理,这样才行的利用,所以一定要研究透彻

⑨ python多线程不能使用多核吗

首先,语言应该在什么级别支持多线程。C 通过操作系统的 preemptive scheler 支持多线程,同时提供 critical section, wait/notify 这样的同步机制。问题在于,这样的同步机制太低级,在实际应用中经常需要封装为高级的同步机制,比如多线程的生产者-消费者队列。高级动态语言的设计者面临三个选择:

在语言中直接提供类似 C 的机制(Java);
设计良好的 C 接口,在同一进程中运行多个虚拟机,利用 C 把低级同步机制封装成多虚拟机之间的高级同步机制(Lua),或者把多线程优化完全封装在一个 API 之内(比如 Intel MKL 的各种多核算法)。
多进程。
方法 1 是得不偿失的。因为低级同步机制的优势在于效率,而考虑效率就必须考虑诸如 L1/L2/L3 cache 实效之类的底层情况。这对于解释执行的语言(即使有 JIT)来说是无法控制的。所以高级动态语言提供底层同步机制实际没有必要。应该多走方法 2 和 3。其中又以 2 最为灵活。
移除 GIL 难吗?不难,因为根本没有必要移除。如果 Python 能在一个进程中初始化多个 VM,同时其标准库在 C 级别做出足够多的常用多核优化就没有问题。Python 其实是希望实现另一个功能,异步操作。尽管异步操作和并行计算都可以通过多线程来完成,但是其实前者更加适合用协程或者用户级线程来完成。但是 Python 是 stackful 实现,也就是 byte code 借用 C runtime stack 来维护自己的运行状态。这种机制的弱点就是不容易用跨平台的方式来实现协程,所以利用 OS 多线程加 GIL 也就成了模拟协程语意的妥协手段。
补充一下。C 的线程和线程同步机制虽然底层,但是只有这种机制才能覆盖所有 use case。而其它的高层抽象只能适合某种 case。所以,当我说高级动态语言需要高级的线程操作时,我的隐含意思是这种语言同时还要有和 C 进行良好的互操作来随时扩展这种抽象。

⑩ python的多线程是否能利用多核计算

比方我有一个4核的CPU,那么这样一来,在单位时间内每个核只能跑一个线程,然后时间片轮转切换。但是Python不一样,它不管你有几个核,单位时间多个核只能跑一个线程,然后时间片轮转。看起来很不可思议?但是这就是GIL搞的鬼。任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。通常我们用的解释器是官方实现的CPython,要真正利用多核,除非重写一个不带GIL的解释器。

热点内容
IR实现编译器 发布:2025-01-27 13:30:38 浏览:459
android图片过大 发布:2025-01-27 13:30:34 浏览:426
公司服务器怎么搭建网站 发布:2025-01-27 13:24:43 浏览:920
光可以存储 发布:2025-01-27 13:14:47 浏览:676
手机网盘加密文件 发布:2025-01-27 13:11:43 浏览:694
唐山有线机顶盒密码是多少 发布:2025-01-27 13:10:25 浏览:251
如何让电脑上拥有移动式服务器 发布:2025-01-27 13:04:46 浏览:67
漫威超级战争如何配置核心组合 发布:2025-01-27 13:03:19 浏览:954
c语言五子棋程序 发布:2025-01-27 12:58:43 浏览:157
win10流媒体服务器怎么搭建 发布:2025-01-27 12:58:04 浏览:384