python中的多进程与线程比较:你所要知道的!
如果你不想了解下面的解释说明,那么你就会处于一直等待状态。如果你的程序和网络绑定,你就可以使用多线程。如果你的程序是和CPU绑定,那你就可以使用多进程。
我们创建这篇指南文章,是因为我们将更好的看到线程与多进程的区别,我们发现这些信息并不是很难以理解。他们陷入越深,就没有真正触及到这些信息,这将有助于我们决定使用什么和如何实现它。
什么是线程,你为什么要怎么做?
其本质而言,python是一门线性语言,当你想要多一点处理能力时,线程模块就派上用场啦。虽然python中的线程不能用于CPU并行计算,它的完美之处在于I/O操作,例如网页爬虫,因为多线程进程可以利用处理器IO阻塞等待时的空闲时间执行其他线程,提升效率。
线程改变了游戏规则,因为许多与网络/数据 I/O 相关的脚本大部分时间都花费在等待来自远程源的数据上。因为下载可能无法访问链接(i.e., 爬取单独的网站),处理器可以并行从不同的数据源下载,并在结尾合并结果。对于CPU处理密集型,使用多线程就没有太大的效率。
很荣幸的是,线程模块已经包含在python标准库中
import threading
from queue import Queue
import time
你可以使用target作为调用对象,args作为这个函数的输入变量,start作为线程启动。
def testThread(num):
print num
if __name__ == '__main__':
for i in range(5):
t = threading.Thread(target=testThread, arg=(i,))
t.start()
解释一下if name == ‘main’: 这个写法表示指定在当前模块下才能调用它下面的代码块,简单理解就是指定函数执行入口,避免该文件模块作为其他的模块引用时会自动调用这个模块下面的代码块。区别外部调用时不会自动执行该模块。
锁机制
你通常希望你的线程能够使用或修改线程之间常见的变量,但要做到这一点,你必须使用锁机制。每当一个功能函数希望修改一个变量,它就会锁住那个变量。当其他功能函数希望使用该变量时,这个功能函数必须等待这个变量解锁为止。
想象一下两个函数都是以1为变量。在另一个功能函数访问同一个变量前,这个锁确保你允许只有一个功能函数能访问那个变量,执行计算,回写这个变量。
使用线程模块时,打印时也可能发生这种情况,因为文本可能会变得混乱(并导致数据损坏)。你可以使用打印锁确保一次只能打印一条线程。
print_lock = threading.Lock()
def threadTest():
# when this exits, the print_lock is released
with print_lock:
print(worker)
def threader():
while True:
# get the job from the front of the queue
threadTest(q.get())
q.task_done()
q = Queue()
for x in range(5):
thread = threading.Thread(target = threader)
# this ensures the thread will die when the main thread dies
# can set t.daemon to False if you want it to keep running
t.daemon = True
t.start()
for job in range(10):
q.put(job)
在这里,我们有10个工作,我们希望完成和5名工人,将工作。
多线程并不总是完美的解决方案
我发现许多指导倾向于跳过使用他们刚刚试图教你的工具的底片。重要的是要明白,使用所有这些工具既有利有弊。例如: 1.有与管理线程相关的开销,因此你不想将其用于基本任务(例如示例) 2.增加程序的复杂度,这会使调试更加困难。
什么是多进程?它与线程有何不同?
劈开多进程,python程序很难最大限度地发挥系统效率,因为GIL(全局解释性锁)。考虑到个人计算机可能具有多个内核(告诉你语言有多旧),Python 的设计并不是这样设计的,所以GIL是必要的,因为python没有线程安全,当访问一个python对象时这是一个全局强制锁。虽不完美,这是一个相当有效的内存管理机制。我们能做什么?
多进程允许你创建的程序能使用整个CPU内核来执行并行操作(绕过GIL)。虽然多进程与线程库有着根本的不同,语法还是十分相似。多处理库为每个进程提供自己的 Python 解释器,为每个进程提供自己的 GIL。
正因如此,与线程相关的常见问题(如数据损坏和死锁)也不再是一个问题。由于这些过程不共享内存,它们无法同时修改相同的内存。
import multiprocessing
def spawn():
print('test!')
if __name__ == '__main__':
for i in range(5):
p = multiprocessing.Process(target=spawn)
p.start()
如果你有一个共享库,你希望能确保你正在等待相关进程完成,然后再开始新的进程。
for i in range(5):
p = multiprocessing.Process(target=spawn)
p.start()
p.join() # this line allows you to wait for processes
如果你想传递参数到你的进程,你需要使用args来传递
import multiprocessing
def spawn(num):
print(num)
if __name__ == '__main__':
for i in range(25):
## right here
p = multiprocessing.Process(target=spawn, args=(i,))
p.start()
这是一个很好的例子你值得关注,不过其中数字不会按照你期望的顺序(并没有使用p.join())
由于是线程,与多进程还是有很多不足之处…你值得注意:
1.数据在进程之间读写时,导致 I/O 开销。
2.整个内存被复制到每个子进程中,这可能导致一个更重要的程序开销很多。
多进程与线程使用场景
如果你的代码有许多I/O或者网络使用:
多线程是你最好的选择,因为它的开销低。
如果你有GUI:
多线程以便你的UI线程不会被锁定
如果你的代码是CPU密集型:
你需要使用多进程(如果你的集群是多核)



被折叠的 条评论
为什么被折叠?



