Python中的多处理与多线程:新手简介

开发 后端
Python是一种线性语言。但是,当您需要更多的处理能力时,线程模块就派上用场了。

什么是线程?你为什么想要它?

Python是一种线性语言。但是,当您需要更多的处理能力时,线程模块就派上用场了。

Python中的线程不能用于并行CPU计算。但是它非常适合于I/O操作,比如web抓取,因为处理器处于空闲状态,等待数据。

线程化改变了游戏规则,因为许多与网络/数据 I/O相关的脚本将大部分时间花费在等待来自远程数据源上。有时候,下载可能没有链接(例如,如果您正在抓取不同的网站),处理器可以并行地从不同的数据源下载并在最后合并结果。

线程包含在标准库中:

  1. import threading 
  2. from queue import Queue 
  3. import time 

您可以使用target作为可调用的对象,args将参数传递给函数,并开始启动线程:

  1. def testThread(num): 
  2.     print num 
  3.  
  4. if __name__ == '__main__': 
  5.     for i in range(5): 
  6.         t = threading.Thread(target=testThreadarg=(i,)) 
  7.         t.start() 

[[326033]]

锁(lock)

您通常希望您的线程能够使用或修改线程之间的公共变量。要做到这一点,你必须使用一种叫做锁(lock)的东西。

每当一个函数想要修改一个变量时,它就会锁定该变量。当另一个函数想要使用一个变量时,它必须等待,直到该变量被解锁。

假设有两个函数都对一个变量进行了1次迭代。锁允许您确保一个函数可以访问变量、执行计算并在另一个函数访问相同的变量之前写回该变量。

您可以使用打印锁来确保一次只能打印一个线程。这可以防止文本在打印时变得混乱(并导致数据损坏)。

在下面的代码中,我们有10个我们想要完成的工作和5个将要工作的工人:

  1. print_lock = threading.Lock() 
  2.  
  3. def threadTest(): 
  4.     # when this exits, the print_lock is released 
  5.     with print_lock: 
  6.         print(worker) 
  7.  
  8. def threader(): 
  9.   while True: 
  10.     # get the job from the front of the queue 
  11.     threadTest(q.get()) 
  12.     q.task_done() 
  13.  
  14. q = Queue() 
  15. for x in range(5): 
  16.     thread = threading.Thread(target = threader
  17.     # this ensures the thread will die when the main thread dies 
  18.     # can set t.daemon to False if you want it to keep running 
  19.     t.daemon = True 
  20.     t.start() 
  21.  
  22. for job in range(10): 
  23.     q.put(job) 

多线程并不总是完美的解决方案

我们发现许多教程都倾向于忽略使用他们刚教过你的工具的缺点。理解使用所有这些工具的利弊是很重要的。

例如:

  • 管理线程需要时间,因此它适用于基本任务(如示例)
  • 线程化增加了程序的复杂性,从而增加了调试的难度

多处理是什么?它与线程有什么不同?

在没有多处理(multiprocessing)的情况下,由于GIL(全局解释器锁 Global Interpreter Lock),Python程序很难最大化系统的规格。Python的设计并没有考虑到个人计算机可能有多个核心。因此GIL是必要的,因为Python不是线程安全的,而且在访问Python对象时存在一个全局强制锁。虽然不完美,但它是一种非常有效的内存管理机制。

多处理允许您创建可以并发运行的程序(绕过GIL)并使用整个CPU内核。尽管它与线程库有本质的不同,但是语法非常相似。多处理库为每个进程提供了自己的Python解释器,以及各自的GIL。

因此,与线程相关的常见问题(如数据损坏和死锁)不再是问题。因为进程不共享内存,所以它们不能并发地修改相同的内存。

让我们开始代码演示:

  1. import multiprocessing 
  2. def spawn(): 
  3.   print('test!') 
  4.  
  5. if __name__ == '__main__': 
  6.   for i in range(5): 
  7.     p = multiprocessing.Process(target=spawn
  8.     p.start() 

如果您有一个共享数据库,您希望确保在启动新数据库之前,正在等待相关进程完成。

  1. for i in range(5): 
  2.   p = multiprocessing.Process(target=spawn
  3.   p.start() 
  4.   p.join() # this line allows you to wait for processes 

如果希望将参数传递给进程,可以使用args实现这一点:

  1. import multiprocessing 
  2. def spawn(num): 
  3.   print(num) 
  4.  
  5. if __name__ == '__main__': 
  6.   for i in range(25): 
  7.     ## right here 
  8.     p = multiprocessing.Process(target=spawnargs=(i,)) 
  9.     p.start() 

这是一个简单的例子,因为正如您所注意到的,数字的排列顺序与您所期望的不一致(没有p.join())。

与线程一样,多处理仍然有缺点……你必须选择其中一个坏处:

  • 在进程之间转移数据会带来I/O开销
  • 整个内存被复制到每个子进程中,对于更重要的程序来说,这会带来很大的开销

我们该用哪个

  • 如果你的代码有很多I/O或网络使用:多线程是您的最佳选择,因为它的开销很低
  • 如果你有一个图形用户界面:多线程是您的最佳选择,这样你的UI线程就不会被锁定
  • 如果你的代码是CPU限制:您应该使用多处理(如果您的机器有多个核心)

 

责任编辑:赵宁宁 来源: 今日头条
相关推荐

2020-04-29 09:10:26

Python多线程多处理

2010-06-21 15:11:54

Linux apt-g

2014-05-08 10:39:55

Python并发编程

2010-06-17 15:42:53

WAP协议技术

2009-06-29 17:49:47

Java多线程

2024-04-30 12:56:00

多线程.NET

2024-10-18 16:58:26

2024-06-12 12:50:06

2011-06-16 10:38:13

Qt多线程编程

2024-06-04 07:52:04

2010-03-18 16:02:09

python 多线程

2010-03-10 19:25:04

python多线程

2015-11-18 18:56:36

Java多线程处理

2024-10-14 16:25:59

C#线程锁代码

2013-03-27 10:32:53

iOS多线程原理runloop介绍GCD

2010-03-24 10:32:05

Python多线程

2010-04-14 09:20:26

.NET多线程

2024-09-26 10:51:51

2010-03-16 17:16:38

Java多线程

2009-09-22 17:21:24

线程局部变量
点赞
收藏

51CTO技术栈公众号