multithreading - Python 无法并行化缓冲区读取
问题描述
我在多线程中遇到性能问题。
我有一个并行读取 8MB 缓冲区的代码片段:
import copy
import itertools
import threading
import time
# Basic implementation of thread pool.
# Based on multiprocessing.Pool
class ThreadPool:
def __init__(self, nb_threads):
self.nb_threads = nb_threads
def map(self, fun, iter):
if self.nb_threads <= 1:
return map(fun, iter)
nb_threads = min(self.nb_threads, len(iter))
# ensure 'iter' does not evaluate lazily
# (generator or xrange...)
iter = list(iter)
# map to results list
results = [None] * nb_threads
def wrapper(i):
def f(args):
results[i] = map(fun, args)
return f
# slice iter in chunks
chunks = [iter[i::nb_threads] for i in range(nb_threads)]
# create threads
threads = [threading.Thread(target = wrapper(i), args = [chunk]) \
for i, chunk in enumerate(chunks)]
# start and join threads
[thread.start() for thread in threads]
[thread.join() for thread in threads]
# reorder results
r = list(itertools.chain.from_iterable(map(None, *results)))
return r
payload = [0] * (1000 * 1000) # 8 MB
payloads = [copy.deepcopy(payload) for _ in range(40)]
def process(i):
for i in payloads[i]:
j = i + 1
if __name__ == '__main__':
for nb_threads in [1, 2, 4, 8, 20]:
t = time.time()
c = time.clock()
pool = ThreadPool(nb_threads)
pool.map(process, xrange(40))
t = time.time() - t
c = time.clock() - c
print nb_threads, t, c
输出:
1 1.04805707932 1.05
2 1.45473504066 2.23
4 2.01357698441 3.98
8 1.56527090073 3.66
20 1.9085559845 4.15
为什么线程模块在并行化缓冲区读取时会惨遭失败?是因为 GIL 吗?或者因为我的机器上有一些奇怪的配置,一个进程一次只能访问一次 RAM(如果我将ThreadPool切换为多处理,我有不错的加速。Pool是上面的代码)?
我在 linux 发行版上使用 CPython 2.7.8。
解决方案
是的,Python 的 GIL 阻止 Python 代码跨多个线程并行运行。您将代码描述为“缓冲读取”,但它实际上是在运行任意 Python 代码(在这种情况下,迭代一个列表,将 1 添加到其他整数)。如果您的线程正在进行阻塞系统调用(例如从文件或网络套接字读取),那么 GIL 通常会在线程阻塞等待外部数据时被释放。但是由于对 Python 对象的大多数操作都会产生副作用,因此您不能同时执行其中的几个操作。
造成这种情况的一个重要原因是 CPython 的垃圾收集器使用引用计数作为了解何时可以清理对象的主要方式。如果多个线程尝试同时更新同一个对象的引用计数,它们可能会最终陷入竞争状态,并使对象的计数错误。GIL 可以防止这种情况发生,因为一次只能有一个线程进行此类内部更改。每次您的process
代码执行此操作j = i + 1
时,它都会更新整数对象的引用计数,0
并且每次都会更新1
几次。这正是 GIL 所要保护的东西。
推荐阅读
- python - Python算术数学游戏
- python - 在 Python 中使用方法 .append() 的好处
- r - ggplot中具有不同小数位的缩放轴
- excel - 如果单元格中满足某个条件,我需要获取另一个包含公式的单元格的“快照/副本”,并将其作为值粘贴到第三个单元格中
- google-app-engine - gcloud 应用程序版本会立即删除删除实例吗?
- c# - 我如何让字符串获得属性?C#
- c# - 以编程方式验证 Windows 密码策略?
- java - 如何在响应式环境中处理文件访问
- javascript - ElementClickInterceptedException:消息:元素单击被拦截元素不可点击错误单击使用 Selenium 和 Python 的单选按钮
- java - 如何在 Spring proejct 中获取配置文件的类路径资源基名?