用Python进行并发编程不是您想的那样。

最初发布于 melvinkoh.me

在本文中,我将首先带您了解并发编程和并行执行之间的区别,讨论有关Python内置的并发编程机制以及Python中多线程的陷阱。

了解并发编程与并行执行

尽管事实上这两个术语经常互换使用,但并发编程并不等同于并行执行。

>没有并行的并发插图

并发是一个属性,可以同时运行多个操作,但这并不意味着可以并发。 (想象一下,如果您的处理器是单线程的。)

>并行性说明

并行是实际上实际上同时运行操作的属性。 通常由硬件限制决定。

将您的程序视为一条快餐链,当构建两个单独的订单和收款柜台时,并发即被合并。 但是,它不能确保并行性,因为它取决于可用雇员的数量。 如果只有一名员工同时处理订单和收款请求,则这些操作不能并行运行。 仅当有两个员工同时服务于订单和收货时,才出现并行。

Python内置

现在,我们在Python中拥有什么? Python是否有内置程序可以帮助我们构建并发程序并使它们并行运行?

在下面的讨论中,我们假定我们的程序都是在多线程或多核处理器中编写并运行的。

答案是Jein (德语为是和否)。 为什么是? Python确实具有用于最常见的并发编程结构(多处理和多线程)的内置库。 您可能会想,既然Python同时支持,为什么选择Jein? 原因是,由于Python中的GIL,Python中的多线程并不是真正的多线程。

多线程—基于线程的并行性

threading是提供用于创建和管理线程的API的软件包。 Python中的线程始终是不确定的,它们的调度由操作系统执行。 但是,多线程可能无法达到您的预期。

为什么Python中的多线程可能不是您想要的?

除了常见的陷阱(如死锁)之外,多线程通常会出现饥饿。 Python因其在多线程中的性能较差而臭名昭著。

让我们看一下以下代码片段:

import threading

def countdown () :
     x = 1000000000
     while x > 0 :
           x -= 1
# Implementation 1: Multi-threading
def implementation_1 () :
     thread_1 = threading.Thread(target=countdown)
     thread_2 = threading.Thread(target=countdown)
     thread_1.start()
     thread_2.start()
     thread_1.join()
     thread_2.join()
# Implementation 2: Run in serial
def implementation_2 () :
     countdown()
     countdown()

哪个实施会更快? 让我们执行一个定时。

>两种实现的时序结果

令人惊讶的是,连续运行2个countdown()胜过多线程吗? 怎么会这样 多亏了臭名昭著的Global Interpreter Lock(GIL)。

什么是全局翻译锁定(GIL)?

在大多数情况下,取决于Python的分布是CPython的实现。 CPython是Python的原始实现,您可以在此StackOverflow线程中阅读有关它的更多信息。

在CPython中,通过引入称为全局解释器锁(又称为GIL)的互斥锁来支持多线程。 这是为了防止多个线程同时访问同一Python对象。 这很有意义,在处理对象时,您不希望其他人对您的对象进行突变。

>实施示意图_1

因此,从上面的代码段中, implementation_1创建2个线程,并应在多线程系统上并行运行。 但是,一次只能有一个线程可以容纳GIL,一个线程必须等待另一线程释放GIL才能运行。 同时,由操作系统完成的调度和切换会带来开销,从而使implementation_1变得更加缓慢。

如何绕过GIL?

我们如何在保持多线程使用的同时绕过GIL? 这个问题没有一个通用的好答案,因为这与代码的目的有所不同。

可以选择使用Jython,PyPy或IronPython等不同的Python实现。 我个人不主张使用不同的Python实现,因为大多数编写的库都没有针对不同的Python实现进行测试。

另一个可能的解决方法是使用C扩展,或更好地称为Cython 。 请注意,Cython和CPython是不同的。 您可以在此处阅读有关Cython的更多信息。

请改用多处理。 由于在多处理中,将为每个子进程创建一个解释器。 不存在争用GIL简单线程的情况,因为在每个进程中始终只有一个主线程。

尽管存在所有陷阱,我们是否仍应使用多线程?

如果您的任务是受I / O约束的,则意味着线程将大部分时间用于处理I / O,例如执行网络请求。 仍然可以使用多线程,因为在大多数情况下,线程被操作系统阻塞并放入阻塞队列中。 线程也总是比进程少耗费资源。

多处理—基于过程的并行性

让我们使用多重处理实现我们之前的代码片段。

import multiprocessing
# countdown() is defined in the previous snippet.
def implementation_3 () :
     process_1 = multiprocessing.Process(target=countdown)
     process_2 = multiprocessing.Process(target=countdown)
     process_1.start()
     process_2.start()
     process_1.join()
     process_2.join()

结果本身是不言自明的。

>多处理与多线程的计时结果

结论

GIL的约束在最初作为Python开发人员时吸引了我。 直到我确定时机之前,我才意识到使用线程的决定是毫无价值的。 希望本文对您有所帮助。

From: https://hackernoon.com/concurrent-programming-in-python-is-not-what-you-think-it-is-b6439c3f3e6a

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值