Python中的多处理与多线程:新手简介
什么是线程?你为什么想要它?
Python是一种线性语言。但是,当您需要更多的处理能力时,线程模块就派上用场了。
Python中的线程不能用于并行CPU计算。但是它非常适合于I/O操作,比如web抓取,因为处理器处于空闲状态,等待数据。
线程化改变了游戏规则,因为许多与网络/数据 I/O相关的脚本将大部分时间花费在等待来自远程数据源上。有时候,下载可能没有链接(例如,如果您正在抓取不同的网站),处理器可以并行地从不同的数据源下载并在最后合并结果。
线程包含在标准库中:
import threading
from queueimport Queue
import time
您可以使用target作为可调用的对象,args将参数传递给函数,并开始启动线程:
def testThread(num):
print num
if __name__ == '__main__':
for i in range(5):
t = threading.Thread(target=testThread, arg=(i,))
t.start()
锁(lock)
您通常希望您的线程能够使用或修改线程之间的公共变量。要做到这一点,你必须使用一种叫做锁(lock)的东西。
每当一个函数想要修改一个变量时,它就会锁定该变量。当另一个函数想要使用一个变量时,它必须等待,直到该变量被解锁。
假设有两个函数都对一个变量进行了1次迭代。锁允许您确保一个函数可以访问变量、执行计算并在另一个函数访问相同的变量之前写回该变量。
您可以使用打印锁来确保一次只能打印一个线程。这可以防止文本在打印时变得混乱(并导致数据损坏)。
在下面的代码中,我们有10个我们想要完成的工作和5个将要工作的工人:
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)
多线程并不总是完美的解决方案
我们发现许多教程都倾向于忽略使用他们刚教过你的工具的缺点。理解使用所有这些工具的利弊是很重要的。
例如:
- 管理线程需要时间,因此它适用于基本任务(如示例)
- 线程化增加了程序的复杂性,从而增加了调试的难度
多处理是什么?它与线程有什么不同?
在没有多处理(multiprocessing)的情况下,由于GIL(全局解释器锁 Global Interpreter Lock),Python程序很难最大化系统的规格。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())。
与线程一样,多处理仍然有缺点……你必须选择其中一个坏处:
- 在进程之间转移数据会带来I/O开销
- 整个内存被复制到每个子进程中,对于更重要的程序来说,这会带来很大的开销
我们该用哪个
如果你的代码有很多I/O或网络使用:
多线程是您的最佳选择,因为它的开销很低
如果你有一个图形用户界面
多线程是您的最佳选择,这样你的UI线程就不会被锁定
如果你的代码是CPU限制:
您应该使用多处理(如果您的机器有多个核心)
参考:见文末阅读原文
·END·
- 一次切割日志引发的血案
- lncRNA实战项目-第三步-了解参考基因组及注释文件
- 如何通过iframe调用其他页面的内容
- WCF 学习总结1 -- 简单实例
- Java8 + Tomcat8 实现Websocket 例子
- Python Syslog Server 开发实例
- WCF 学习总结2 -- 配置WCF
- SQLite事务 SQLite插入多条语句为什么这么慢?.net (C#)
- Linux 应用程序开发入门
- LINQ to XML LINQ学习第一篇
- PHP 安全与性能
- Extjs 项目中常用的小技巧,也许你用得着(5)--设置 Ext.data.Store 传参的请求方式
- WPF备忘录(5)怎样修改模板中的控件
- Linux 系统与数据库安全
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法