Python-并发编程

1. 简介

  • 基础概念
    • 并行:利用多核CPU同时运行;
    • 并发:伪并行,同一时刻一个线程运行时,其它线程挂起;
    • 同步:提交一任务,等待返回值,再提交一个任务
    • 异步:提交一个任务,不等待返回,再提交一个任务
    • 阻塞:程序运行时,遇到IO程序挂起;
    • 非阻塞:程序顺序执行,不会卡住;
  • 任务类型
    类型IO时间CPU占用率例子
    CPU密集型压缩解压、加密解密、正则匹配
    IO密集型文件读写、DB读写、网络请求
  • 方法选择
    方法优缺点场景
    多进程 Process
    包 multiprocessing
    优点:可以利用多核CPU并行运算
    缺点:占用资源最多、可启动数目比线程少
    CPU密集型
    多线程 Thread
    包 threading
    VS 进程-优点:占用资源少
    VS 进程-缺点:只能并发执行,不能利用多CPU
    VS 协程-缺点:启动数目有限,占用内存资源,有切换开销
    IO密集型
    少量任务
    多协程 Coroutine
    包 asyncio
    优点:内存开销最少、启动协程数量最多
    缺点:支持的库有限制(aiohttp vs requests)、代码实现复杂
    IO密集型
    大量任务

2. 全局解释器锁GIL

  • Python速度慢原因
    • 动态解释型语言,边解释边执行;
    • 全局解释器锁GIL,无法利用多核CPU实现并发。
  • 全局解释器锁GIL:多线程之间的切换会导致引用计数器计数混乱、数据不完整、状态异常等问题。因此,提出GIL(Global Interpreter Lock)使得同一时刻也只有一个线程在执行。
    Python-GIL
  • GIL限制下加速方法
    • 多线程:IO密集型任务,实现CPU和IO并行加速;CPU密集型任务,由于频繁的线程切换会拖慢速度。
    • 多进程:利用多核CPU实现并行计算。

3. 多线程

  • 创建
    import threading
    def my_func(a, b):
        pass
    # 创建线程对象
    t = threading.Thread(target=my_func, args=(100, 200))
    t.start()	# 启动线程
    t.join()	# 等待结束
    
  • 生产者消费者架构
    • 例子:信件与邮递员
    • 定义:生产者把数据放入缓冲区,消费者从缓冲区取出数据进行处理。
      Python-生产者消费者
  • 多线程数据通信
    import queue
    q = queue.Queue()		# 创建队列缓冲区
    q.put(item)				# 生产者把数据放入缓冲区(阻塞的,满时卡住)
    item = q.get()			# 消费者从缓冲区取出数据(阻塞的,空时卡住)
    q.qsize()				# 查看缓冲区元素个数
    q.empty()				# 判断缓冲区是否为空
    q.full()				# 判断缓冲区是否已满
    
  • 线程安全:多线程切换时,能够正确地处理多个线程之间的共享数据。
    import threading
    lock = threading.Lock()
    
    # 解决方法1:
    lock.acquire()
    # do something
    lock.release()
    
    # 解决方法2:
    with lock:
        # do something
    
  • 线程池
    • 线程生命周期:新建线程系统需要分配资源、终止线程系统需要回收资源。线程池可以重用线程,从而减去新建/终止的开销。
      Python-线程生命周期
    • 线程池优点
      • 提升性能:减去了大量新建、终止线程的开销,重用了线程资源;
      • 防御功能:能有效避免系统因为创建线程过多,而导致系统负荷过大相应变慢等问题
      • 代码优势:使用线程池的语法比自己新建线程执行线程更加简洁
      • 适用场景:适合需要大量线程完成任务、但实际任务处理时间较短
    • 实现
      from concurrent.futures import ThreadPoolExecutor, as_completed
      # 第一种:
      with ThreadPoolExecutor() as pool:
          results = pool.map(craw, urls)	# map的结果和入参是顺序对应的
          for result in results:
              print(result)
      # 第二种:
      with ThreadPoolExecutor() as pool:
         futures = [ pool.submit(craw, url) for url in urls ]
          for future in futures:								# 顺序返回
              print(future.result())
          for future in as_completed(futures):	# 谁先执行完,先返回谁
              print(future.result())
      

4. 多进程

  • 原理:多进程在多个CPU上并行执行,适用于CPU密集型计算。
  • 对比梳理
    Python-多进程多线程

5. 异步IO

  • 协程:使用超级循环在单线程内实现并发。
    Python-协程
  • 实现:request 不支持异步,需要使用aiohttp
    import asyncio
    loop = asyncio.get_event_loop()	# 获取事件循环
    async def myfunc(url):          # 定义协程
        await get_url(url)			# 对应IO,执行到此处不阻塞
    tasks = [loop.create_task(myfunc(url)) for url in urls]# 创建任务列表
    loop.run_until_complete(asyncio.wait(tasks))		   # 执行等待完成
    
  • 信号量:设置并发量Semaphore,控制并发数量。
    sem = asyncio.Semaphore(10)
    
    # 方法1
    async with sem:
    # work with shared resource
    
    # 方法2
    await sem.acquire()
    try:
        # work with shared resource
    finally:
        sem.release()
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

代码大玩家

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

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

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

打赏作者

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

抵扣说明:

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

余额充值