最近在学习Python多线程相关的部分,遇到了这样的一句话:“对于任何Python程序,不管有多少的处理器,任何时候都总是只有一个线程在执行”,即Python中的多线程是“假的多线程”,是什么原因造成这样的说法,搜集查阅了一些资料,并且对比Java,如下是个人对于这个问题的一些理解:
解释型语言
编译性语言例如c语言:用c语言开发了程序后,需要通过编译器把程序编译成机器语言(即计算机识别的二进制文件,因为不同的操作系统计算机识别的二进制文件是不同的),所以c语言程序进行移植后,要重新编译。(如windows编译成ext文件,linux编译成erp文件)。
Java与Python都是解释型语言,而相对的,解释性语言编写的程序不进行预先编译,以文本方式存储程序代码。在发布程序时,看起来省了道编译工序。但是,在运行程序的时候,解释性语言必须先解释再运行。例如java语言,java程序首先通过编译器编译成class文件,如果在windows平台上运行,则通过windows平台上的java虚拟机(VM)进行解释。如果运行在linux平台上,则通过linux平台上的java虚拟机进行解释执行。所以说能跨平台,前提是平台上必须要有相匹配的java虚拟机。如果没有java虚拟机,则不能进行跨平台。
前者由于程序执行速度快,同等条件下对系统要求较低,因此像开发操作系统、大型应用程序、数据库系统等时都采用它,像C/C++、Pascal/Object Pascal(Delphi)等都是编译语言,而一些网页脚本、服务器脚本及辅助开发接口这样的对速度要求不高、对不同系统平台间的兼容性有一定要求的程序则通常使用解释性语言,如Java、JavaScript、VBScript、Perl、Python、Ruby、MATLAB 等等。
造成Python没有真正意义上的多线程的原因
要想利用多核系统,Python必须支持多线程运行。作为解释型语言,Python的解释器必须做到既安全又高效。我们都知道多线程编程会遇到的问题,解释器要留意的是避免在不同的线程操作内部共享的数据,同时它还要保证在管理用户线程时保证总是有最大化的计算资源。那么,不同线程同时访问时,数据的保护机制是怎样的呢?答案是解释器全局锁。从名字上看能告诉我们很多东西,很显然,这是一个加在解释器上的全局(从解释器的角度看)锁(解释可以参考之前的一篇文章/megustas_jjc/article/details/79110284)。但是这也导致了上面提到的问题:对于任何Python程序,不管有多少的处理器,任何时候都总是只有一个线程在执行(这点上Python是不同于Java的,对于多核的情况,Java可以同时开启多个线程进行处理,具体例子可以参考下面例子)。
”为什么我全新的多线程Python程序运行得比其只有一个线程的时候还要慢?“许多人在问这个问题时还是非常犯晕的,因为显然一个具有两个线程的程序要比其只有一个线程时要快(假设该程序确实是可并行的)。事实上,这个问题被问得如此频繁以至于Python的专家们精心制作了一个标准答案:”不要使用多线程,请使用多进程”。
所以,对于计算密集型的,我还是建议不要使用python的多线程而是使用多进程方式,而对于IO密集型的,还是建议使用多进程方式,因为使用多线程方式出了问题,最后都不知道问题出在了哪里(个人理解,对于计算密集和IO密集可以关注之前的一篇文章/megustas_jjc/article/details/79110063)。
先谈谈“多进程”
Java
Java编写的程序都运行在在Java虚拟机(JVM)中,每用java命令启动一个java应用程序,就会启动一个JVM进程。在同一个JVM进程中,有且只有一个进程,就是它自己。在这个JVM环境中,所有程序代码的运行都是以线程来运行的。JVM找到程序程序的入口点main(),然后运行main()方法,这样就产生了一个线程,这个线程称之为主线程。当main方法结束后,主线程运行完成。JVM进程也随即退出。
Python
但是听闻Python的多线程实际上并不能真正利用多核,所以如果使用多线程实际上还是在一个核上做并发处理。不过,如果使用多进程就可以真正利用多核,因为各进程之间是相互独立的,不共享资源,可以在不同的核上执行不同的进程,达到并行的效果。
Python多进程库multiprocessing
基于Process的多进程
multiprocessing模块提供process类实现新建进程。下述代码是新建一个子进程。
from multiprocessing import Process
def f(name):
print 'hello', name
if __name__ == '__main__':
p = Process(target=f, args=('bob',)) # 新建一个子进程p,目标函数是f,args是函数f的参数列表
() # 开始执行进程
() # 等待子进程结束
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
上述代码中()的意思是等待子进程结束后才执行后续的操作,一般用于进程间通信。例如有一个读进程pw和一个写进程pr,在调用pw之前需要先写(),表示等待写进程结束之后才开始执行读进程。
基于进程池Pool的多进程
如果要同时创建多个子进程可以使用类。该类可以创建一个进程池,然后在多个核上执行这些进程。
import multiprocessing
import time
def func(msg):
print multiprocessing.current_process().name + '-' + msg
if __name__ == "__main__":
pool = (processes=4) # 创建4个进程
for i in xrange(10):
msg = "hello %d" %(i)
pool.apply_async(func, (msg, ))
() # 关闭进程池,表示不能在往进程池中添加进程
() # 等待进程池中的所有进程执行完毕,必须在close()之后调用
print "Sub-process(es) done."
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
结果:
Sub-process(es) done.
PoolWorker-34-hello 1
PoolWorker-33-hello 0
PoolWorker-35-hello 2
PoolWorker-36-hello 3
PoolWorker-34-hello 7
PoolWorker-33-hello 4
PoolWorker-35-hello 5
PoolWorker-36-hello 6
PoolWorker-33-hello 8
PoolWorker-36-hello 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
Pool可以使用apply()函数或者map函数进行数据的处理
再说说“多线程”
Java
可以参考之前的文章:
Java多线程实现及控制
多线程学习笔记(一)之线程创建与线程状态
多线程学习笔记(二)之线程安全问题
多线程学习笔记(三)之单例模式中的线程问题
多线程学习笔记(四)之线程间通信—等待唤醒机制
多线程学习笔记(五)之多生产者多消费者中的线程问题
多线程学习笔记(六)之锁对象Lock
多线程学习笔记(七)之wait与sleep的区别、线程停止及守护线程等
对于并发线程数的设置的一些理解
Python
可以参考之前的文章:
Python 多线程
进程与线程的数据共享
#-*- coding:utf-8 -*-
#multiprocessing模块提供了一个Process类来代表一个进程对象
import multiprocessing
from multiprocessing import Process
import threading
def run(lock1,info_list,i):
with lock1:
info_list.append(i)
print info_list
def run2(info_list,i):
()
info_list.append(i)
print info_list
()
info = []
#多进程执行,内存是独立的,每个进程都独立的copy一份
lock1 = ()
for i in range(10):
p = Process(target=run,args=(lock1,info,i))
()
#多线程执行,内存是共享的
lock2 = ()
for i in range(10):
p = (target=run2,args=(info,i))
()
#因此想进行进程间的通信,需要一个桥梁
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
运行结果:
[0]
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]
[0]
[0, 1]
[0, 1, 2]
[0, 1, 2, 3]
[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4, 5]
[0, 1, 2, 3, 4, 5, 6]
[0, 1, 2, 3, 4, 5, 6, 7]
[0, 1, 2, 3, 4, 5, 6, 7, 8]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
由于多进程间内存不共享,因此要进行进程间通信的时候,需要一个队列Queue进行中转,from multiprocessing import Queue即可,也可以使用Value,Array来共享内存,使得进程间共享数据,可以进行修改等操作,例如:
#-*- coding:utf-8 -*-
from multiprocessing import Process,Array,Value
def f(n,a):
= 3.1415926
for i in range(len(a)):
a[i] = -a[i]
if __name__ == "__main__":
num = Value("d",0.0)
array = Array("i",range(10))
print , array[:]
p = Process(target=f,args=(num,array))
()
()
print , array[:]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
结果:
0.0 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
3.1415926 [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
- 1
- 2
通过Value与Array使得子进程可以修改num与array