Python支持多线程吗?它可以加快执行时间吗?

时间:2014-01-05 21:17:13

标签: python multithreading

我对多线程是否在Python中工作有点困惑。

我知道有很多关于这方面的问题,我已经阅读了很多,但我仍然感到困惑。我从自己的经验中了解到并且已经看到其他人在StackOverflow上发布他们自己的答案和示例,在Python中确实可以实现多线程。那么为什么每个人都一直说Python被GIL锁定并且一次只能运行一个线程呢?它显然确实有效。或者是否有一些区别我没有到这里?

许多海报/受访者也一直提到线程有限,因为它没有使用多个核心。但我会说它们仍然有用,因为它们可以同时工作,从而更快地完成组合工作量。我的意思是为什么甚至会有一个Python线程模块呢?

更新

感谢目前为止的所有答案。我理解的方式是多线程只能并行运行某些IO任务,但一次只能运行一个用于CPU绑定的多个核心任务。

我不完全确定这对我来说在实际意义上是什么意思,所以我只举一个我想要多线程的任务的例子。例如,假设我想遍历很长的字符串列表,我想对每个列表项执行一些基本的字符串操作。如果我拆分列表,将每个子列表发送给我的循环/字符串代码在新线程中处理,并将结果发送回队列,这些工作负载会大致同时运行吗?最重要的是,理论上这会加快运行脚本所需的时间吗?

另一个例子可能是如果我可以在四个不同的线程中使用PIL渲染和保存四个不同的图片,并且这比一个接一个地逐个处理图片更快?我想这个速度组件是我真正想知道的,而不是正确的术语。

我也知道多处理模块,但我现在主要关心的是中小型任务负载(10-30秒),所以我认为多线程会更合适,因为子进程可能很慢启动。

5 个答案:

答案 0 :(得分:101)

GIL不会阻止线程化。所有GIL都确保一次只有一个线程正在执行Python代码;控制仍然在线程之间切换。

GIL阻止的是利用多个CPU核心或单独的CPU并行运行线程。

这仅适用于Python代码。 C扩展可以并且确实发布GIL以允许多个C代码线程和一个Python线程跨多个内核运行。这扩展到内核控制的I / O,例如select()调用套接字读取和写入,使Python在多线程多核设置中合理有效地处理网络事件。

然后执行多少服务器部署,运行多个Python进程,让操作系统处理进程之间的调度,以最大限度地利用CPU内核。您还可以使用multiprocessing library处理来自一个代码库和父进程的多个进程的并行处理(如果适合您的用例)。

请注意,GIL仅适用于CPython实现; Jython和IronPython使用不同的线程实现(分别是本机Java VM和.NET公共运行时线程)。

直接解决您的更新:任何尝试使用纯Python代码从并行执行中获得速度提升的任务都不会加速,因为线程Python代码被锁定到一次执行的一个线程。但是,如果混合使用C扩展和I / O(例如PIL或numpy操作),并且任何C代码都可以与一个活动Python线程并行运行。

Python线程非常适合创建响应式GUI,或者用于处理多个短Web请求,其中I / O是比Python代码更多的瓶颈。它不适合并行化计算密集型Python代码,坚持使用multiprocessing模块执行此类任务或委托给专用外部库。

答案 1 :(得分:3)

是。 :)

您拥有低级thread模块和更高级别threading模块。但是你只想使用多核机器,multiprocessing模块就是你的选择。

来自docs

的引用
  

在CPython中,由于Global Interpreter Lock,只有一个线程可以   一次执行Python代码(即使某些性能导向   图书馆可能会克服这个限制)。如果你想要你的   应用程序,以更好地利用计算资源   多核机器,建议您使用多处理。然而,   如果要运行多个线程,线程仍然是一个合适的模型   同时进行I / O绑定任务。

答案 2 :(得分:0)

Python允许线程化,唯一的问题是GIL将确保一次只执行一个线程(无并行性)。

因此,基本上,如果您想对代码进行多线程处理以加快计算速度,则不会一次执行一个线程就加快了处理速度,但是例如,如果您使用它与数据库进行交互,它将加快速度。

答案 3 :(得分:0)

我很喜欢发布者,因为答案总是“取决于您想做什么”。但是,就我的经验而言,即使是进行多处理,Python中的并行速度也一直很糟糕。

例如,请查看本教程(在Google中排名第二至第一):https://www.machinelearningplus.com/python/parallel-processing-python/

我在此代码周围添加了时间,并增加了池映射函数的进程数(2、4、8、16),并得到了以下不良时间:

serial 70.8921644706279 
parallel 93.49704207479954 tasks 2
parallel 56.02441442012787 tasks 4
parallel 51.026168536394835 tasks 8
parallel 39.18044807203114 tasks 16

代码: #在开始时增加数组大小 #我的计算节点有40个CPU,所以我在这里有很多备用

arr = np.random.randint(0, 10, size=[2000000, 600])
.... more code ....
tasks = [2,4,8,16]

for task in tasks:
    tic = time.perf_counter()
    pool = mp.Pool(task)

    results = pool.map(howmany_within_range_rowonly, [row for row in data])

    pool.close()
    toc = time.perf_counter()
    time1 = toc - tic
    print(f"parallel {time1} tasks {task}")

答案 4 :(得分:-1)

Python 3具有Launching parallel tasks的功能。这使我们的工作更加轻松。

它具有thread poolingProcess pooling的名称。

以下内容提供了一个见解:

ThreadPoolExecutor示例

import concurrent.futures
import urllib.request

URLS = ['http://www.foxnews.com/',
        'http://www.cnn.com/',
        'http://europe.wsj.com/',
        'http://www.bbc.co.uk/',
        'http://some-made-up-domain.com/']

# Retrieve a single page and report the URL and contents
def load_url(url, timeout):
    with urllib.request.urlopen(url, timeout=timeout) as conn:
        return conn.read()

# We can use a with statement to ensure threads are cleaned up promptly
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
    # Start the load operations and mark each future with its URL
    future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
    for future in concurrent.futures.as_completed(future_to_url):
        url = future_to_url[future]
        try:
            data = future.result()
        except Exception as exc:
            print('%r generated an exception: %s' % (url, exc))
        else:
            print('%r page is %d bytes' % (url, len(data)))

ProcessPoolExecutor

import concurrent.futures
import math

PRIMES = [
    112272535095293,
    112582705942171,
    112272535095293,
    115280095190773,
    115797848077099,
    1099726899285419]

def is_prime(n):
    if n % 2 == 0:
        return False

    sqrt_n = int(math.floor(math.sqrt(n)))
    for i in range(3, sqrt_n + 1, 2):
        if n % i == 0:
            return False
    return True

def main():
    with concurrent.futures.ProcessPoolExecutor() as executor:
        for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
            print('%d is prime: %s' % (number, prime))

if __name__ == '__main__':
    main()