python多核编程
① 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可以实现吗
python并没有真正意义上的多线程,如果是计算型任务可以考虑多进程或者换个编程语言,如果是io型的python的多线程或者协程都可以。最好不要使用python进行多任务,python并不能真正发挥cpu多核优势
③ python多线程对多核的利用
GIL 与 Python 线程的纠葛
GIL 是什么东西?它对我们的 python 程序会产生什么样的影响?我们先来看一个问题。运行下面这段 python 程序,CPU 占用率是多少?
# 请勿在工作中模仿,危险:)
def dead_loop():
while True:
pass
dead_loop()
答案是什么呢,占用 100% CPU?那是单核!还得是没有超线程的古董 CPU。在我的双核 CPU 上,这个死循环只会吃掉我一个核的工作负荷,也就是只占用 50% CPU。那如何能让它在双核机器上占用 100% 的 CPU 呢?答案很容易想到,用两个线程就行了,线程不正是并发分享 CPU 运算资源的吗。可惜答案虽然对了,但做起来可没那么简单。下面的程序在主线程之外又起了一个死循环的线程
import threading
def 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 Thread
lib = 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 Thread
def dead_loop():
while True:
pass
lib = 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实现并发编程
多线程几乎是每一个程序猿在使用每一种语言时都会首先想到用于解决并发的工具(JS程序员请回避),使用多线程可以有效的利用CPU资源(Python例外)。然而多线程所带来的程序的复杂度也不可避免,尤其是对竞争资源的同步问题。
然而在python中由于使用了全局解释锁(GIL)的原因,代码并不能同时在多核上并发的运行,也就是说,Python的多线程不能并发,很多人会发现使用多线程来改进自己的Python代码后,程序的运行效率却下降了,这是多么蛋疼的一件事呀!如果想了解更多细节,推荐阅读这篇文章。实际上使用多线程的编程模型是很困难的,程序员很容易犯错,这并不是程序员的错误,因为并行思维是反人类的,我们大多数人的思维是串行(精神分裂不讨论),而且冯诺依曼设计的计算机架构也是以顺序执行为基础的。所以如果你总是不能把你的多线程程序搞定,恭喜你,你是个思维正常的程序猿:)
Python提供两组线程的接口,一组是thread模块,提供基础的,低等级(Low Level)接口,使用Function作为线程的运行体。还有一组是threading模块,提供更容易使用的基于对象的接口(类似于Java),可以继承Thread对象来实现线程,还提供了其它一些线程相关的对象,例如Timer,Lock
使用thread模块的例子
import thread
def worker():
"""thread worker function"""
print 'Worker'
thread.start_new_thread(worker)
使用threading模块的例子
import threading
def worker():
"""thread worker function"""
print 'Worker'
t = threading.Thread(target=worker)
t.start()
或者Java Style
import threading
class worker(threading.Thread):
def __init__(self):
pass
def run():
"""thread worker function"""
print 'Worker'
t = worker()
t.start()
⑤ python是单线程的解释语言,为何运行时多核cpu的占用率会同时提升
(1) 在RTOS系统启动前, 使用Tick中断测试CPU的处理能力基准 CPUPerformanceBase;
(2) 在系统进入运行后, 使用空闲任务执行与测试CPU处理能力基准完全相同的算法, 得到RTCPUPerformance.
(3) 周期地计算CPU占用率, 并清除RTCPUPerformance的值, 一般每秒钟计算一次:
RealTime CPU Load = 1 - (RTCPUPerformance/CPUPerformanceBase) * 100%
⑥ 怎么让python用多个cpu
python由于GIL的关系,python的多线程并没有发挥多核的作用,这些线程都是在在单核上跑的所以要想发挥多核的作用,就需要使用多进程,尽可能的在每一个CPU核心上分配到一个python进程。所以要想跑满多核CPU就得多进程多线程互相结合
⑦ python 多线程和多进程的区别 mutiprocessing theading
在socketserver服务端代码中有这么一句:
server = socketserver.ThreadingTCPServer((ip,port), MyServer)
ThreadingTCPServer这个类是一个支持多线程和TCP协议的socketserver,它的继承关系是这样的:
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
右边的TCPServer实际上是主要的功能父类,而左边的ThreadingMixIn则是实现了多线程的类,ThreadingTCPServer自己本身则没有任何代码。
MixIn在Python的类命名中很常见,称作“混入”,戏称“乱入”,通常为了某种重要功能被子类继承。
我们看看一下ThreadingMixIn的源代码:
class ThreadingMixIn:
daemon_threads = False
def process_request_thread(self, request, client_address):
try:
self.finish_request(request, client_address)
self.shutdown_request(request)
except:
self.handle_error(request, client_address)
self.shutdown_request(request)
def process_request(self, request, client_address):
t = threading.Thread(target = self.process_request_thread,
args = (request, client_address))
t.daemon = self.daemon_threads
t.start()
在ThreadingMixIn类中,其实就定义了一个属性,两个方法。其中的process_request()方法实际调用的正是Python内置的多线程模块threading。这个模块是Python中所有多线程的基础,socketserver本质上也是利用了这个模块。
socketserver通过threading模块,实现了多线程任务处理能力,可以同时为多个客户提供服务。
那么,什么是线程,什么是进程?
进程是程序(软件,应用)的一个执行实例,每个运行中的程序,可以同时创建多个进程,但至少要有一个。每个进程都提供执行程序所需的所有资源,都有一个虚拟的地址空间、可执行的代码、操作系统的接口、安全的上下文(记录启动该进程的用户和权限等等)、唯一的进程ID、环境变量、优先级类、最小和最大的工作空间(内存空间)。进程可以包含线程,并且每个进程必须有至少一个线程。每个进程启动时都会最先产生一个线程,即主线程,然后主线程会再创建其他的子线程。
线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不独立拥有系统资源,但它可与同属一个进程的其它线程共享该进程所拥有的全部资源。每一个应用程序都至少有一个进程和一个线程。在单个程序中同时运行多个线程完成不同的被划分成一块一块的工作,称为多线程。
举个例子,某公司要生产一种产品,于是在生产基地建设了很多厂房,每个厂房内又有多条流水生产线。所有厂房配合将整个产品生产出来,单个厂房内的流水线负责生产所属厂房的产品部件,每个厂房都拥有自己的材料库,厂房内的生产线共享这些材料。公司要实现生产必须拥有至少一个厂房一条生产线。换成计算机的概念,那么这家公司就是应用程序,厂房就是应用程序的进程,生产线就是某个进程的一个线程。
线程的特点:
线程是一个execution context(执行上下文),即一个cpu执行时所需要的一串指令。假设你正在读一本书,没有读完,你想休息一下,但是你想在回来时继续先前的进度。有一个方法就是记下页数、行数与字数这三个数值,这些数值就是execution context。如果你的室友在你休息的时候,使用相同的方法读这本书。你和她只需要这三个数字记下来就可以在交替的时间共同阅读这本书了。
线程的工作方式与此类似。CPU会给你一个在同一时间能够做多个运算的幻觉,实际上它在每个运算上只花了极少的时间,本质上CPU同一时刻只能干一件事,所谓的多线程和并发处理只是假象。CPU能这样做是因为它有每个任务的execution context,就像你能够和你朋友共享同一本书一样。
进程与线程区别:
同一个进程中的线程共享同一内存空间,但进程之间的内存空间是独立的。
同一个进程中的所有线程的数据是共享的,但进程之间的数据是独立的。
对主线程的修改可能会影响其他线程的行为,但是父进程的修改(除了删除以外)不会影响其他子进程。
线程是一个上下文的执行指令,而进程则是与运算相关的一簇资源。
同一个进程的线程之间可以直接通信,但是进程之间的交流需要借助中间代理来实现。
创建新的线程很容易,但是创建新的进程需要对父进程做一次复制。
一个线程可以操作同一进程的其他线程,但是进程只能操作其子进程。
线程启动速度快,进程启动速度慢(但是两者运行速度没有可比性)。
由于现代cpu已经进入多核时代,并且主频也相对以往大幅提升,多线程和多进程编程已经成为主流。Python全面支持多线程和多进程编程,同时还支持协程。
⑧ Python可以做大数据吗
Python是数据科学家十分喜爱的编程语言,其内置了很多由C语言编写的库,操作起来更加方便,Python在网络爬虫的传统应用领域,在大数据的抓取方面具有先天优势,目前,最流行的爬虫框架Scrapy,HTTP工具包urlib2,HTML解析工具beautifulsoup,XML解析器lxml,等等,都是能够独当一面的Python类库。
相关推荐:《Python基础教程》
Python十分适合数据抓取工作,对于大数据的处理,具有一定的局限性:
Python在大数据处理方面的优势:
1. 异常快捷的开发速度,代码量少;
2. 丰富的数据处理包,使用十分方便;
3. 内部类型使用成本低;
4. 百万级别数据可以采用Python处理。
Python在大数据处理方面的劣势:
1. python线程有gil,多线程的时候只能在一个核上跑,浪费了多核服务器;
2. python执行效率不高,在处理大数据的时候,效率不高;
3. 10亿级别以上的数据Python效率低。
Python适合大数据的抓取、载入和分发,相比于其他语言更加简单、高效;求一些常用的统计量和求一些基本算法的结果,Python也有现成的高效的库,但是针对大数据处理,Python具有一定的局限于,因此,涉及大数据处理时,可以用Python做整个流程的框架,核心CPU密集操作可以采用C语言等编程语言!
⑨ python的多线程是否能利用多核计算
比方我有一个4核的CPU,那么这样一来,在单位时间内每个核只能跑一个线程,然后时间片轮转切换。但是Python不一样,它不管你有几个核,单位时间多个核只能跑一个线程,然后时间片轮转。看起来很不可思议?但是这就是GIL搞的鬼。任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。通常我们用的解释器是官方实现的CPython,要真正利用多核,除非重写一个不带GIL的解释器。
⑩ python如何利用多核cpu
Python,想利用多核CPU,那么就应该进行处理,这样才行的利用,所以一定要研究透彻