听说Python有鸡肋?一起聊聊...

简介: 听说Python有鸡肋?一起聊聊...

听说是鸡肋

一直以来,关于Python的多线程和多进程是否是鸡肋的争议一直存在,今晚抽空谈谈我的看法,以下是我的观点:

对于多线程:

Python 的多线程库 threading 在某些情况下确实是鸡肋的,这是因为 Python 的全局解释器锁(Global Interpreter Lock, GIL)导致了多线程的并发性能不能真正发挥出来。简单来说,这意味着在任何给定时刻只有一个线程能够真正地运行 Python 代码,这就限制了多线程的性能。

然而,对于一些特定类型的任务,比如 I/O 密集型的任务,多线程还是可以带来性能提升的。这是因为 I/O 操作通常会导致线程阻塞,让其他线程得以运行。此外,在 Python3 中,对于一些特殊情况,比如使用 asyncio 库,也可以通过协程实现并发执行,从而规避 GIL 的限制。

对于多进程:

Python 的多进程库 multiprocessing 是可以真正发挥出多核处理器的性能的,因为每个进程都有自己的解释器和 GIL。这意味着每个进程可以独立地运行 Python 代码,从而实现真正的并行处理。

当然,多进程也有一些缺点,比如进程之间的通信和数据共享比较麻烦。此外,每个进程的启动和销毁都会涉及到一定的开销,因此如果任务很小,多进程可能反而会带来性能下降。

多线程和多进程怎么选

对于不同类型的任务,多线程和多进程都有它们的优缺点,需要根据具体情况进行选择。如果你要处理的任务是 CPU 密集型的,那么多进程可能是更好的选择;如果是 I/O 密集型的,那么多线程可能更合适。

实战验证

  1. 下面我写一个简单的代码示例,用来说明 Python 多线程在 CPU 密集型任务中的性能问题:
import threading
counter = 0
def worker():
    global counter
    for i in range(10000000):
        counter += 1
threads = []
for i in range(4):
    t = threading.Thread(target=worker)
    threads.append(t)
for t in threads:
    t.start()
for t in threads:
    t.join()
print(counter)

这个代码示例定义了一个全局变量 counter,然后创建了 4 个线程,每个线程都会执行一个简单的循环,将 counter 的值加 1。最后输出 counter 的值。

在单线程模式下,循环完成后 counter 的值应该是 40000000,但是在多线程模式下,由于 GIL 的限制,多个线程并不能真正并行地执行代码,导致 counter 的最终值小于 40000000。例如,在我的机器上运行这个代码示例,最终的输出结果可能是 36092076,远小于预期的值。

这个示例表明,在一些 CPU 密集型的任务中,Python 多线程的性能受到 GIL 的限制,不能真正地发挥出多核处理器的优势。

  1. 我再写一个简单的代码示例,用来说明在 I/O 密集型任务中,多线程可以带来性能提升的情况:
import threading
import requests
urls = [
    "https://www.google.com",
    "https://www.baidu.com",
    "https://www.github.com",
    "https://www.python.org"
]
def worker(url):
    res = requests.get(url)
    print(f"{url} : {len(res.content)} bytes")
threads = []
for url in urls:
    t = threading.Thread(target=worker, args=(url,))
    threads.append(t)
for t in threads:
    t.start()
for t in threads:
    t.join()

这个代码示例创建了 4 个线程,每个线程负责访问一个 URL 并打印出该 URL 返回的内容长度。由于访问 URL 的操作是 I/O 密集型的,因此线程在等待服务器响应时会阻塞,让其他线程有机会执行。

在我的机器上运行这个代码示例,可以看到 4 个线程几乎同时执行,并在几乎相同的时间内完成了任务,证明了多线程在 I/O 密集型任务中的性能优势。

对于 Python3 中的 asyncio 库,它提供了基于协程的并发执行模型,可以在一定程度上规避 GIL 的限制。下面写了一个简单使用 asyncio 库的代码示例:

import asyncio
import aiohttp
urls = [
    "https://www.google.com",
    "https://www.baidu.com",
    "https://www.github.com",
    "https://www.python.org"
]
async def worker(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            content = await response.read()
            print(f"{url} : {len(content)} bytes")
async def main():
    tasks = []
    for url in urls:
        task = asyncio.create_task(worker(url))
        tasks.append(task)
    await asyncio.gather(*tasks)
asyncio.run(main())

这个代码示例使用 asyncio 库来实现异步访问多个 URL,其中 worker 函数是一个异步函数,使用 aiohttp 库发送异步 HTTP 请求。在主函数中,使用 asyncio.create_task 创建多个协程任务,并使用 asyncio.gather 函数等待所有协程任务完成。

在我的机器上运行这个代码示例,可以看到几乎同时访问 4 个 URL,并在几乎相同的时间内完成了任务,证明了 asyncio 库在 I/O 密集型任务中的性能优势。

  1. 我们继续看多进程,下面我写了一个简单的代码示例,用来说明 multiprocessing 库是否可以真正发挥出多核处理器的性能:
import multiprocessing
def worker(start, end):
    for i in range(start, end):
        print(i * i)
if __name__ == '__main__':
    processes = []
    num_processes = 4
    num_tasks = 20
    for i in range(num_processes):
        start = i * num_tasks // num_processes
        end = (i + 1) * num_tasks // num_processes
        p = multiprocessing.Process(target=worker, args=(start, end))
        processes.append(p)
    for p in processes:
        p.start()
    for p in processes:
        p.join()

这个代码示例创建了 4 个进程,每个进程负责计算一段整数的平方并打印出结果。由于每个进程有自己的解释器和 GIL,因此每个进程可以独立地运行 Python 代码,从而实现真正的并行处理。

在我的机器上运行这个代码示例,可以看到 4 个进程几乎同时执行,并在几乎相同的时间内完成了任务,证明了 multiprocessing 库可以真正发挥出多核处理器的性能。

  1. 之前提到,多进程在处理小任务时可能会带来性能下降,下面我写了一个简单的代码示例,说明以下这种情况:
import multiprocessing
def worker(num):
    result = num * num
    print(result)
if __name__ == '__main__':
    processes = []
    num_processes = 4
    for i in range(num_processes):
        p = multiprocessing.Process(target=worker, args=(i,))
        processes.append(p)
    for p in processes:
        p.start()
    for p in processes:
        p.join()

这个代码示例创建了 4 个进程,每个进程负责计算一个整数的平方并打印出结果。由于任务非常小,每个进程的计算时间非常短,因此进程的启动和销毁所涉及的开销可能会成为性能的瓶颈。

在我的机器上运行这个代码示例,可以看到进程的启动和销毁所涉及的开销导致整个程序的运行时间远远超过了单进程的运行时间。这说明在处理小任务时,多进程可能会带来性能下降,因此需要根据实际情况选择合适的并发处理方式。

最后的总结

Python 的并发编程有多种实现方式,包括多线程、多进程和协程等。其中,多线程通常适用于 I/O 密集型的任务,但由于 GIL 的存在,不能真正发挥出多核处理器的性能;而多进程则可以真正发挥出多核处理器的性能,但进程之间的通信和数据共享比较麻烦,每个进程的启动和销毁也会涉及到一定的开销。协程则是一种轻量级的并发处理方式,适用于 I/O 密集型任务和部分计算密集型任务,可以通过 async/await 关键字和 asyncio 库来实现。

在实际编程中,需要根据任务类型、数据量、机器配置等因素来选择合适的并发处理方式。对于小型任务,多进程可能会带来性能下降;对于计算密集型任务,可以考虑使用多进程或者协程;对于 I/O 密集型任务,可以使用多线程、多进程或者协程等方式。同时,还需要注意并发处理带来的数据竞争、死锁、线程安全等问题,以保证程序的正确性和性能。

相关文章
|
3月前
|
机器学习/深度学习 数据可视化 数据处理
python自动化办公太难?学这些就够用了
python自动化办公太难?学这些就够用了
|
6月前
|
SQL 分布式计算 Python
2024年最新520节日快到了,教你用Python画动态爱心表白!_mac python 画心,2024年最新2024年大厂Python岗面试必问
2024年最新520节日快到了,教你用Python画动态爱心表白!_mac python 画心,2024年最新2024年大厂Python岗面试必问
2024年最新520节日快到了,教你用Python画动态爱心表白!_mac python 画心,2024年最新2024年大厂Python岗面试必问
|
算法 数据可视化 程序员
谈一谈|给python小白的学习建议
谈一谈|给python小白的学习建议
101 0
|
数据采集 程序员 API
Python 大神 kennethreitz 又搞事了
Python 大神 kennethreitz 又搞事了
84 0
|
Python 容器
python真的很骚可惜你不会
int类型:整数 str类型:字符串、以成对单引号或双引号包裹 float类型:浮点数 bool:True或False
python真的很骚可惜你不会
|
存储 算法 Python
5个python中编程的大坑
5个python中编程的大坑
200 0
|
算法 JavaScript Unix
怎么样才算是精通 Python?
程序员写过简历的都知道,先说精通->后说熟悉->最后说了解,要把精通放在最前面。 But 很少人会说自己精通Python,这也是因为Python的应用领域是真的多,最好还是说自己精通的领域,而不是语言,除非你真的是大佬。
254 0
怎么样才算是精通 Python?
|
运维 前端开发 安全
为什么我不建议你搞Python
之所以从事Python是因为很不爽PHP的语法,虽然PHP被称为世界上最好的编程语言。于是,为了这个原因,付出了沉重的代价,失业了好几回。 身边有些人时不时总是会问我一些Python的技术问题,但是大多数情况下都是比较基础的。要不是近几年Python火了起来,说不定我真的转行送外卖了。 对于Python这么语言,可以当作一门兴趣或爱好来学习,但是若是想找到份好的工作还是谨慎为主,这也是为什么不建议你搞Python的原因。
为什么我不建议你搞Python
|
存储 Python
10分钟教你用python如何正确把妹-不知道妹子为何生气?那是因为你没学python
10分钟教你用python如何正确把妹-不知道妹子为何生气?那是因为你没学python
181 0
10分钟教你用python如何正确把妹-不知道妹子为何生气?那是因为你没学python
|
C++ UED Python
微软再出神器,这次终于对Python下手了!
微软又出良心工具了! 微软于7月1日发布一款新的VS Code插件,名为Pylance,这个名称是向Monty Python的Lancelot致敬。
微软再出神器,这次终于对Python下手了!
下一篇
无影云桌面