python多线程(Multiprocessing)与多线程(Multithreading)区别优缺点最详细解释

原文链接

Python中多线程与多进程的区别

Multiprocessing V.S. Threading

摘要

如果你不想读整篇文章,这里有你所需要的本文精华:

  • 如果你的程序运行效率瓶颈在于网络传输时延,那么你可以使用多线程。
  • 如果你的程序运行效率瓶颈在于CPU数量,那么你就可以尝试多进程。

之所以写出这一部分指引是因为我发现在其他关于多线程与多进程的区别的介绍中,大部分信息都十分晦涩难懂。而我们往往需要花很多时间去研究得很深入,到头来发现对自己如何去解决自己手上的实际问题还是没有什么帮助。

什么是多线程?为什么我们要用多线程?

我们都知道,python是一个线性的语言,然而当你需要更大的处理性能时,python多线程模块就有他的作用了。即使Python无法用于CPU的并行计算,他在处理I/O操作时仍然很棒,比如在做多线程爬虫的时候,大部分时间处理器都处于空闲状态等待数据的进入。

python多线程实际上改变了多线程的规则,这是因为许多脚本都会花大部分的时间处理与网络/数据之间的I/O或等待数据从远端传入。由于下载的数据不需要是连续的(比如爬取不同的网站信息),处理器就能够并行地从不同数据源下载数据最后把它们拼接起来。然而对于比较集中的CPU操作,threading模块可能就没有那么大的收益了。
python多线程(Multiprocessing)与多线程(Multithreading)区别优缺点最详细解释_第1张图片

多线程threading使用方法

我们可以直接从python的标准库中引入多线程模块threading:

import threading
from queue import Queue
import time

同时你可以通过target参数定义线程调用的函数,通过args为该函数传参,用start方法启动线程,如下demo:

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__':这条语句:这条语句是一个能够保证代码在内部嵌套的基本的方法,他只会在你直接运行该脚本的时候执行,而不会在被import时调用。

锁机制

通常情况下,我们希望多线程能够使用或更改同一个变量的时候不发生冲突,这个时候我们就会用到锁机制。任何时候当一个函数需要更改一个变量的数值,他就会锁住这个变量,而当其他变量需要使用时,必须等待这个变量被当前锁住他的线程释放出来。
python多线程(Multiprocessing)与多线程(Multithreading)区别优缺点最详细解释_第2张图片
假如有两个函数需要迭代地处理一个变量。这个时候锁机制就能够保证一次只能有一个函数获得这个变量的使用权限,对这个变量进行相关的操作,并且在他未释放这个变量之前都不会有其他函数具有访问权限。

在使用python threading模块时,在使用打印功能时经常会发生文本输出的混淆(数据也可能会损坏)。这个时候可以尝试用打印锁来保证一次只有一个线程在进行打印操作。

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. 同时它会让程序更为复杂,在debug的时候也会更麻烦。

什么是多进程?它与多线程的区别是什么?

在没有多进程(Multi-processing)的概念时,由于GIL(Global Interpreter Lock)机制的影响(详解GIL),python无法最大化系统的性能。实际上,由于python非常古老,开发者在开发的过程时并没有考虑过会使用在多cpu上,在当时的情况下,因为python并不是thread-safe的,所以需要有一个在访问python对象时的全局锁。即使这个GIL锁并不是完美的,但是它确实是一种非常有效的内存管理机制。

多进程能够让程序绕过GIL锁,去并行地处理程序,并能够更充分地使用cpu。虽然它与threading模块本质不同,但是语法上非常相似。多进程库会为每个进程提供各自的解释器和GIL锁。

在多线程上会发生的问题(如数据混淆、死锁等)在多进程上并不会发生。这是因为在多线程上,不同的线程直接的存储不共享,因此也就不会发生同时不同空间同时更改同一内存空间这一情况。

多进程Multiprocessing的使用方法

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

如果需要往函数中传参,方法也很简单:

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(),这个程序的输出并不是按照你既定的顺序。

多进程Multiprocessing的缺点

跟多线程一样,多进程同样也有一些自己的缺点。当你在选择的时候需要参考自己的场景及他们二者各自的缺点来确定该用哪一种模式:

  1. 在进程间进行数据的交互会产生额外的I/O开销。
  2. 整个内存空间被复制到每个子进程中,这样对于比较复杂的程序造成的额外开销也很大。

两者分别在何种场景下应用?

  • 如果你的程序有大量与数据交互/网络交互,可以使用多线程,因为程序时间瓶颈不在于GIL而是在I/O,这时多线程的小开销就比多进程更实用。
  • 如果你的程序有图形界面GUI,使用多线程,GIL锁会帮助你让你的UI线程不会产生死锁等问题。
  • 如果你的程序的运行效率与CPU密切相关的,即瓶颈在于计算等情况,且有多核可用时,就可以考虑用多进程提高效率。

你可能感兴趣的:(python机制)