Python 中的多进程与线程


原文链接:https://timber.io/blog/multiprocessing-vs-multithreading-in-python-what-you-need-to-know/

什么是线程?

从本质上讲,Python 是一种线性语言,但是当您想要更多的处理能力时,线程模块会派上用场。虽然 Python 中的线程不能用于并行 CPU 计算,但它非常适合 I/O 操作,例如网页抓取,因为处理器处于空闲状态等待数据。

线程正在改变游戏规则(game-changing),因为许多与网络/数据 I/O 相关的脚本大部分时间都在等待来自远程源的数据。因为下载可能没有链接(即,抓取单独的网站),处理器可以从不同的数据源并行下载并在最后组合结果。对于 CPU 密集型进程,使用线程模块几乎没有什么好处。

在这里插入图片描述

线程包含在标准库中:

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()

您通常希望您的线程能够使用或修改线程之间通用的变量,但要做到这一点,您必须使用lock. 每当一个函数想要修改一个变量时,它就会锁定该变量。当另一个函数想要使用一个变量时,它必须等到该变量被解锁。

在这里插入图片描述

想象两个函数都将变量迭代 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 对象时存在全局强制锁定。虽然不完美,但它是一种非常有效的内存管理机制。我们能做什么?

多处理允许您创建可以并发运行(绕过 GIL)并使用整个 CPU 内核的程序。尽管它与线程库有着根本的不同,但语法却非常相似。多处理库为每个进程提供了自己的 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 或网络使用:

  • 多线程是最好的选择,因为它的开销很低

如果有图形用户界面

  • 多线程,因此 UI 线程不会被锁定

如果代码受 CPU 限制:

  • 应该使用多进程(如果机器有多个内核)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值