python是否支持并行开发呢?

什么是并发编程和并行编程,他们的区别是什么

 - 并发(Concurency):同一时间内管理多个任务的执行,但并不是要求多个任务真正在物理层面上运行。 
 - 并行(Parallelism): 真正意义的“同时执行”需要多个核心来在同一时刻独自完成。 

下面的这个短视频大致会让我们有一个简单的理解。

MythBusters流言终结者使用GPU绘画:蒙娜丽莎

在这个视频里, 流言终结组分别用CPU和GPU控制的机器人完成了一副画,这个动画其实很好的向我们普及了并发和并行的区别。

python是否支持并行开发呢?

 关于这个话题, 其实众说纷纭。由于有着全局解释器锁(Global Interpreter Lock, GIL),所以多线程很难实现在CPU密集任务上的并行。所以一部分人不认为python是适合高并发项目。 

GIL是什么?

GIL 是 CPython 实现中的一个全局互斥锁,它保证同一时刻只会有一个线程在解释器层面执行 Python 字节码。
•	对于 CPU 密集型任务,多线程因为 GIL 无法得到真正的并行化,所以性能提升有限甚至可能变差。
•	对于 I/O 密集型任务(如网络请求、文件读写),线程经常会因等待 I/O 而主动让出执行权,从而让其他线程有机会运行,这样在总体上能提升程序对 I/O 请求的处理能力。

我们来测试一下多线程。

  优点:
•	对 I/O 密集型任务比较有效率,如网络请求、文件读写、数据库访问等。
•	线程启动和切换开销相对进程较小。
缺点:
•	受 GIL 限制,对于 CPU 密集型任务不适合。
•	对共享内存变量需要小心加锁,否则容易出现数据竞争问题。

下面的这张图就可以很好的显示了多线程的工作情况。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
样例代码如下:

import threading
import time
import matplotlib.pyplot as plt
import random

# 全局数据结构:记录每个线程的执行区间
# { thread_name: [(start_time, end_time), (start_time, end_time), ...], ...}
execution_intervals = {}

def cpu_bound_task(duration=0.005):
    # 一个CPU密集函数(简单版本),做一些无意义的计算以消耗CPU时间
    start = time.time()
    while time.time() - start < duration:
        # 做一些无意义的计算
        x = 0
        for i in range(1000):
            x += i*i

def worker(name, iterations=10):
    intervals = []
    for _ in range(iterations):
        start = time.time()
        cpu_bound_task()  # 执行CPU密集任务的一小段
        end = time.time()
        intervals.append((start, end))
        # 加一点随机sleep让图表更清晰,以防过于密集挤在一起
        time.sleep(random.uniform(0.001, 0.003))
    execution_intervals[name] = intervals

if __name__ == "__main__":
    threads = []
    # 创建5个线程
    for i in range(5):
        t = threading.Thread(target=worker, args=(f"Thread-{i}", 20))
        threads.append(t)
        t.start()

    for t in threads:
        t.join()

    # 所有线程完成后绘制甘特图
    fig, ax = plt.subplots(figsize=(10, 6))
    ax.set_title("Gantt Chart of CPU-bound Multi-thread Execution in Python")
    ax.set_xlabel("Time (seconds)")
    ax.set_ylabel("Threads")

    # 为每个线程分配一个y位置
    # 例如 Thread-0 在 y=10, Thread-1 在 y=20, ..., 每个高度为5
    height = 5
    yticks = []
    ylabels = []
    base_y = 10

    # 为了在图中清晰表示,将y值根据线程索引分层
    for idx, (thread_name, intervals) in enumerate(execution_intervals.items()):
        y_pos = base_y + idx * (height * 2)
        yticks.append(y_pos + height/2)
        ylabels.append(thread_name)

        # 绘制该线程的执行区间条段
        # intervals为[(start, end), ...]
        # broken_barh需要((start, width))对形式
        bar_data = [(start, end - start) for (start, end) in intervals]
        ax.broken_barh(bar_data, (y_pos, height), facecolors="tab:blue")

    ax.set_yticks(yticks)
    ax.set_yticklabels(ylabels)
    ax.grid(True)

    plt.tight_layout()
    plt.show()

我们来测试一下多进程。

  优点:
•	对 I/O 密集型任务比较有效率,如网络请求、文件读写、数据库访问等。
•	线程启动和切换开销相对进程较小。
缺点:
•	受 GIL 限制,对于 CPU 密集型任务不适合。
•	对共享内存变量需要小心加锁,否则容易出现数据竞争问题。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

代码如下:

import multiprocessing
import time
import random

def worker(name, start_time):
    # 记录进程开始运行的时间(相对于主进程启动所有进程的时间点)
    process_start = time.time() - start_time
    
    # 模拟工作负载:这里让进程随机等待0.1到1.0秒不等
    delay = random.uniform(0.1, 1.0)
    time.sleep(delay)
    
    # 任务结束时间
    process_end = time.time() - start_time
    
    # 时间差
    elapsed = process_end - process_start

    # 打印进程信息
    print(f"进程名称: {name}, 启动时间: {process_start:.4f}s, 结束时间: {process_end:.4f}s, 运行时长: {elapsed:.4f}s")


if __name__ == "__main__":
    start_time = time.time()

    processes = []
    for i in range(20):
        p = multiprocessing.Process(target=worker, args=(f"Process-{i}", start_time))
        processes.append(p)
        p.start()
    
    for p in processes:
        p.join()

    print("所有进程执行完毕。")

最后我们来测试一下python的携程。

协程是一种轻量级的并发实现手段,通过单线程实现高并发的 I/O 操作。在 Python 3.5+ 引入的 async / await 语法,使得编写协程代码更加直观。
• 核心思想是异步 I/O:当协程遇到 I/O 操作(网络请求等)时,不会阻塞当前协程的执行,而是让出控制权给事件循环,让其他协程继续运行。
• 通过这样一种事件驱动机制,可以在单线程下以类似并发的方式执行大量 I/O 密集的异步任务。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
)

import asyncio
import time
import matplotlib.pyplot as plt

# 用来存储每个协程任务的执行时间
# 格式: {task_name: (start_time, end_time)}
execution_times = {}

async def io_bound_task(name, start_time):
    # 记录任务开始时间(相对于主程序启动的时间)
    s = time.time() - start_time
    # 模拟IO等待,例如网络请求、文件读写等
    # 这里我们用 sleep 来模拟:
    # 两次随机等待以体现不同的并发运行点
    await asyncio.sleep(0.1)  # 模拟前半段IO
    await asyncio.sleep(0.2)  # 模拟后半段IO
    e = time.time() - start_time
    execution_times[name] = (s, e)

async def main():
    start_time = time.time()
    # 创建多个协程任务
    tasks = [io_bound_task(f"Task-{i}", start_time) for i in range(5)]
    # 并发运行所有任务
    await asyncio.gather(*tasks)

asyncio.run(main())

# 所有协程执行完毕后绘图
fig, ax = plt.subplots(figsize=(10, 6))
ax.set_title("Asyncio Coroutines Execution Timeline")
ax.set_xlabel("Time (seconds)")
ax.set_ylabel("Coroutines")

# 我们对5个协程进行绘制,每个协程水平排开
task_names = sorted(execution_times.keys())
height = 0.8
yticks = []
ylabels = []

for idx, tname in enumerate(task_names):
    start, end = execution_times[tname]
    duration = end - start
    y_pos = idx * 2  # 给每个任务留点间隔
    ax.broken_barh([(start, duration)], (y_pos, height), facecolors='tab:blue')
    yticks.append(y_pos + height/2)
    ylabels.append(tname)

ax.set_yticks(yticks)
ax.set_yticklabels(ylabels)
ax.grid(True)

plt.tight_layout()
plt.show()

运行该代码后,你将看到一张图表,X轴为执行时间,Y轴为协程任务名称。每条水平线段代表一个协程在这段时间内执行。从图中你会看到多条线段在时间上重叠,说明在同一时间段内,多个协程任务都在进行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

掉光头发的土豆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值