Python-并发编程

Python-并发编程,第1张

Python-并发编程

文章目录
  • 1. 简介
  • 2. 全局解释器锁GIL
  • 3. 多线程
  • 4. 多进程
  • 5. 异步IO

1. 简介
  • 基础概念:
    • 并行:利用多核CPU同时运行;
    • 并发:伪并行,同一时刻一个线程运行时,其它线程挂起;
    • 同步:提交一任务,等待返回值,再提交一个任务
    • 异步:提交一个任务,不等待返回,再提交一个任务
    • 阻塞:程序运行时,遇到IO程序挂起;
    • 非阻塞:程序顺序执行,不会卡住;
  • 任务类型: 类型IO时间CPU占用率例子CPU密集型短高压缩解压、加密解密、正则匹配IO密集型长低文件读写、DB读写、网络请求
  • 方法选择: 方法优缺点场景多进程 Process
    包 multiprocessing优点:可以利用多核CPU并行运算
    缺点:占用资源最多、可启动数目比线程少CPU密集型多线程 Thread
    包 threadingVS 进程-优点:占用资源少
    VS 进程-缺点:只能并发执行,不能利用多CPU
    VS 协程-缺点:启动数目有限,占用内存资源,有切换开销IO密集型
    少量任务多协程 Coroutine
    包 asyncio优点:内存开销最少、启动协程数量最多
    缺点:支持的库有限制(aiohttp vs requests)、代码实现复杂IO密集型
    大量任务
2. 全局解释器锁GIL
  • Python速度慢原因:
    • 动态解释型语言,边解释边执行;
    • 全局解释器锁GIL,无法利用多核CPU实现并发。
  • 全局解释器锁GIL:多线程之间的切换会导致引用计数器计数混乱、数据不完整、状态异常等问题。因此,提出GIL(Global Interpreter Lock)使得同一时刻也只有一个线程在执行。
  • 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()	# 等待结束
    
  • 生产者消费者架构:
    • 例子:信件与邮递员
    • 定义:生产者把数据放入缓冲区,消费者从缓冲区取出数据进行处理。
  • 多线程数据通信:
    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
    
  • 线程池:
    • 线程生命周期:新建线程系统需要分配资源、终止线程系统需要回收资源。线程池可以重用线程,从而减去新建/终止的开销。
    • 线程池优点:
      • 提升性能:减去了大量新建、终止线程的开销,重用了线程资源;
      • 防御功能:能有效避免系统因为创建线程过多,而导致系统负荷过大相应变慢等问题
      • 代码优势:使用线程池的语法比自己新建线程执行线程更加简洁
      • 适用场景:适合需要大量线程完成任务、但实际任务处理时间较短
    • 实现:
      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密集型计算。
  • 对比梳理:
5. 异步IO
  • 协程:使用超级循环在单线程内实现并发。
  • 实现: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()
    

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5563225.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-14
下一篇 2022-12-14

发表评论

登录后才能评论

评论列表(0条)

保存