Python 并发简介(多线程、多进程)

Python 并发简介

请添加图片描述

  • 多线程:threading,利用CPU和IO可以同时执行的原理,让CPU不会干巴巴的等待IO完成。

  • 多进程:multiprocessing,利用多核CPU的能力,真正并行执行任务。

  • 异步IO:asyncio,在单线程利用CPU和IO同时执行的原理,实现函数异步执行。

  • 使用 Lock对资源加锁,防止冲突访问

  • 使用Queue实现不同线程、进程之间的数据通信,实现生产者-消费者模式

  • 使用线程池Pool、进程池Pool,简化线程、进程的任务提交、等待结束、获取结果

多线程、多进程、多协程对比

  • 多进程Process(multiprocessing):

    优点:可以利用多核CPU并行计算

    缺点:占用资源最多、可启动数目比线程少

    适用于:CPU密集型计算

  • 多线程Thread(threading):
    优点:相比进程,更轻量级、占用资源少
    缺点: 1. 相比进程:多线程只能并发执行,不能利用多CPU(GIL)(python多线程中只能使用单CPU)

     	2. 相比协程:启动数目有限制,占用内存资源,有线程切换开销
    

    适用于:IO密集型计算、同时运行的任务数目要求不多

  • 多协程Corotine(asyncio):

    优点:内存开销最少、启动协程数量最多

    缺点:支持的库有限制、代码实现复杂

    适用于:IO密集型计算、需要超多任务运行、但有现成的库支持的场景

IO(读写内存、发送、网络等)

关系:进程 > 线程 > 协程,一个进程中可以启动多个线程,一个线程中可以启动多个协程。

GIL

python速度慢的原因

  1. 动态类型语言,边解释边执行
  2. GIL,无法利用多核CPU并发执行

GIL

同步线程的一种机制,使得任何时刻仅有一个线程在执行。在多核心处理器上,使用 GIL 的解释器也只允许同一时间执行一个线程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jTXRZMgJ-1655734406988)(file:///D:\app\QQ\My_FILE\676221979\Image\Group2\N([J\N([JRTM]T]3G8P}_B5`S[18.png)]![img]

为什么有GIL这个东西?

简而言之:python设计初期,为了规避并发问题引入了GIL,现在想去除却去不掉

怎么规避GIL带来的限制?

  1. 多线程threading机制依然是有用的,用于IO密集型计算

    因为在IO期间,线程会释放GIL,实现CPU和IO并行,因此多线程用于IO密集型计算依然可以大幅提升速度,

    但是多线程用于CPU密集型计算时,只会更加拖慢速度。

  2. 使用multiprocessing的多进程机制实现并行计算、利用多核CPU优势

    为了应对GIL的问题,python提供了multiprocessing

多线程编程

应用于IO密集型计算,比如几乎所有的网络后台服务、网络爬虫

引入模块

  • from threading import Thread
    

新建、启动、等待结束

  • t=Thread(target=func, args=(100, ))
    t.start()
    t.join()
    

数据通信

  • import queue
    q = queue.Queue()
    q.put(item)
    item = q.get()
    

线程安全加锁

  • from threading import Lock
    lock = Lock()
    with lock:
    

    RLock和Lock的区别:

    RLock支持嵌套锁,Lock只支持锁一次解一次,不支持嵌套锁。RLock又叫递归锁,Lock又叫互斥锁。如果Lock锁上没有解锁,再锁上一次,会造成死锁。死锁另外一种情况是由于竞争资源或由于彼此通信而造成阻塞的现象。

    eg:两个线程函数func1和func2,分别被lock1和lock2锁上,在func1里(lock1未解锁)再用lock2上锁,在func2里在用lock1上锁.

信号量限制并发

  • from threading import Semaphore
    semaphre = Semaphore(10)
    with semaphre:
    

线程池

  1. 线程池的原理请添加图片描述

  2. 使用线程池的好处

    1. 提升性能:因为减去了大量新建、终止线程的开销,重用了线程资源
    2. 适用场景:适合处理突发性大量请求或者需要大量线程完成任务、但实际任务处理时间短
    3. 防御功能:能有效避免系统因为创建线程过多,而导致系统符合过大相应变慢等问题
    4. 代码优势:使用线程池的语法比自己新建线程、执行线程更加简洁
  3. ThreadPoolExecutor的使用语法

    1. from concurrent.futures import ThreadPoolExecutor
      with ThreadPoolExecutor() as pool:
          results = pool.map(func, args)
          # args 对应着results
      
    2. future = pool.submit(func, 1)
      # 一个一个执行,一个arg对应一future
      futures = [pool.submit(func, arg)  for arg in args]
      # 两种遍历方式
      # 1
      for future in futures:
      	print(future.result())
      # 2
      for future in as_completed(futures):
          print(future.result())
      
  4. 使用线程池改造爬虫

使用线程池在Web服务中实现加速

多进程编程

如果遇到了 CPU密集型计算,线程自动切换反而变成了负担,多线程反而会降低执行速度,multiprocessing模块就是python为了解决GIL缺陷引入的模块,原理就是用多进程在多CPU上并发执行。

语法多线程多进程
引入模块from threading import Threadfrom multiprcessing import Prcoess
新建
启动
等待结束
t = Thread(target=func, args=(100,))
t.start
t.join()
p = Process(target=func, args=('bob',))
p.start
p.join()
数据通信import queue
q = queue.Queue()
q.put(item)
item = q.get()
from multiprcessing import Queue
q = Queue()
q.put([42, None, 'hello']])
item = q.get()
安全加锁from threading import Lock
lock = Lock()
with lock:
pass
from multiprocessing import Lock
lock = Lock()
with lock:
pass
池化技术from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor() as pool:
# 方法1
results = pool.map(func, args)
# args 对应着results
# 方法2
future = pool.submit(func, 1)
result = future.result()
from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor() as pool:
# 方法1
results = pool.map(func, args)
# args 对应着results
# 方法2
future = pool.submit(func, 1)
result = future.result()

进程间通信

进程间相互独立

  1. ​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RS9b1qGP-1655820951762)(C:\Users\han\Desktop$QFO8S6I}%)]65KLU}{H]$WY.jpg)
  2. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CzofiY84-1655820951763)(C:\Users\han\Desktop\U_E)]14A88XMZ]C0}%Y_HL0P.jpg)
  3. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dyujjB7J-1655820951763)(C:\Users\han\Desktop~NA

Python 并发简介

请添加图片描述

  • 多线程:threading,利用CPU和IO可以同时执行的原理,让CPU不会干巴巴的等待IO完成。

  • 多进程:multiprocessing,利用多核CPU的能力,真正并行执行任务。

  • 异步IO:asyncio,在单线程利用CPU和IO同时执行的原理,实现函数异步执行。

  • 使用 Lock对资源加锁,防止冲突访问

  • 使用Queue实现不同线程、进程之间的数据通信,实现生产者-消费者模式

  • 使用线程池Pool、进程池Pool,简化线程、进程的任务提交、等待结束、获取结果

多线程、多进程、多协程对比

  • 多进程Process(multiprocessing):

    优点:可以利用多核CPU并行计算

    缺点:占用资源最多、可启动数目比线程少

    适用于:CPU密集型计算

  • 多线程Thread(threading):
    优点:相比进程,更轻量级、占用资源少
    缺点: 1. 相比进程:多线程只能并发执行,不能利用多CPU(GIL)(python多线程中只能使用单CPU)

     	2. 相比协程:启动数目有限制,占用内存资源,有线程切换开销
    

    适用于:IO密集型计算、同时运行的任务数目要求不多

  • 多协程Corotine(asyncio):

    优点:内存开销最少、启动协程数量最多

    缺点:支持的库有限制、代码实现复杂

    适用于:IO密集型计算、需要超多任务运行、但有现成的库支持的场景

IO(读写内存、发送、网络等)

关系:进程 > 线程 > 协程,一个进程中可以启动多个线程,一个线程中可以启动多个协程。

GIL

python速度慢的原因

  1. 动态类型语言,边解释边执行
  2. GIL,无法利用多核CPU并发执行

GIL

同步线程的一种机制,使得任何时刻仅有一个线程在执行。在多核心处理器上,使用 GIL 的解释器也只允许同一时间执行一个线程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jTXRZMgJ-1655734406988)(file:///D:\app\QQ\My_FILE\676221979\Image\Group2\N([J\N([JRTM]T]3G8P}_B5`S[18.png)]![img]

为什么有GIL这个东西?

简而言之:python设计初期,为了规避并发问题引入了GIL,现在想去除却去不掉

怎么规避GIL带来的限制?

  1. 多线程threading机制依然是有用的,用于IO密集型计算

    因为在IO期间,线程会释放GIL,实现CPU和IO并行,因此多线程用于IO密集型计算依然可以大幅提升速度,

    但是多线程用于CPU密集型计算时,只会更加拖慢速度。

  2. 使用multiprocessing的多进程机制实现并行计算、利用多核CPU优势

    为了应对GIL的问题,python提供了multiprocessing

多线程编程

应用于IO密集型计算,比如几乎所有的网络后台服务、网络爬虫

引入模块

  • from threading import Thread
    

新建、启动、等待结束

  • t=Thread(target=func, args=(100, ))
    t.start()
    t.join()
    

数据通信

  • import queue
    q = queue.Queue()
    q.put(item)
    item = q.get()
    

线程安全加锁

  • from threading import Lock
    lock = Lock()
    with lock:
    

    RLock和Lock的区别:

    RLock支持嵌套锁,Lock只支持锁一次解一次,不支持嵌套锁。RLock又叫递归锁,Lock又叫互斥锁。如果Lock锁上没有解锁,再锁上一次,会造成死锁。死锁另外一种情况是由于竞争资源或由于彼此通信而造成阻塞的现象。

    eg:两个线程函数func1和func2,分别被lock1和lock2锁上,在func1里(lock1未解锁)再用lock2上锁,在func2里在用lock1上锁.

信号量限制并发

  • from threading import Semaphore
    semaphre = Semaphore(10)
    with semaphre:
    

线程池

  1. 线程池的原理请添加图片描述

  2. 使用线程池的好处

    1. 提升性能:因为减去了大量新建、终止线程的开销,重用了线程资源
    2. 适用场景:适合处理突发性大量请求或者需要大量线程完成任务、但实际任务处理时间短
    3. 防御功能:能有效避免系统因为创建线程过多,而导致系统符合过大相应变慢等问题
    4. 代码优势:使用线程池的语法比自己新建线程、执行线程更加简洁
  3. ThreadPoolExecutor的使用语法

    1. from concurrent.futures import ThreadPoolExecutor
      with ThreadPoolExecutor() as pool:
          results = pool.map(func, args)
          # args 对应着results
      
    2. future = pool.submit(func, 1)
      # 一个一个执行,一个arg对应一future
      futures = [pool.submit(func, arg)  for arg in args]
      # 两种遍历方式
      # 1
      for future in futures:
      	print(future.result())
      # 2
      for future in as_completed(futures):
          print(future.result())
      
  4. 使用线程池改造爬虫

使用线程池在Web服务中实现加速

多进程编程

如果遇到了 CPU密集型计算,线程自动切换反而变成了负担,多线程反而会降低执行速度,multiprocessing模块就是python为了解决GIL缺陷引入的模块,原理就是用多进程在多CPU上并发执行。

语法多线程多进程
引入模块from threading import Threadfrom multiprcessing import Prcoess
新建
启动
等待结束
t = Thread(target=func, args=(100,))
t.start
t.join()
p = Process(target=func, args=('bob',))
p.start
p.join()
数据通信import queue
q = queue.Queue()
q.put(item)
item = q.get()
from multiprcessing import Queue
q = Queue()
q.put([42, None, 'hello']])
item = q.get()
安全加锁from threading import Lock
lock = Lock()
with lock:
pass
from multiprocessing import Lock
lock = Lock()
with lock:
pass
池化技术from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor() as pool:
# 方法1
results = pool.map(func, args)
# args 对应着results
# 方法2
future = pool.submit(func, 1)
result = future.result()
from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor() as pool:
# 方法1
results = pool.map(func, args)
# args 对应着results
# 方法2
future = pool.submit(func, 1)
result = future.result()

进程间通信

进程间相互独立

  1. 请添加图片描述

  2. 请添加图片描述

  3. 请添加图片描述

  4. 请添加图片描述

线程池里,multiporcessing.RLock() 不可使用

使用: manager = multiprocessing.Manager()

lock = manager.RLock()

RLock 可以多次使用多次释放、递归锁

Lock


版权声明:本文为qingguideng原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qingguideng/article/details/125399819