python进程、线程、协程

通过学习bi站 蚂蚁学Python 老师视频总结文档,仅用于学习。。。

python进程、线程、协程

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

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

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

方法:

  • 使用Lock对资源加锁,防止冲突访问(锁起来就可以实现有序访问)

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

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

  • 使用subprocess 启动外部程序的进程,并进行输入输出交互

1. CPU密集型计算、IO密集型计算

1.1 CPU密集型(CPU-bound)

CPU密集型也叫计算密集型,是指在I/O在很短的时间就可以完成,CPU需要大量的计算和处理,特点是CPU占用率相当高

例如:

  • 压缩和解压缩
  • 加密解密
  • 正则表达式搜索

1.2 IO密集型(I/O-bound)

IO密集型是指是,系统运作大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,CPU占用率仍然较低

例如:

  • 文件处理程序(大量读写程序)
  • 网络爬虫程序(大量网络下载,网络下载占用很长时间)
  • 读写数据库程序(网络的读取,网络开销比较多)

2. 多进程、多线程、多协程的对比

2.1 多进程(multiprocessing)

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

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

  • 适用于:CPU密集型计算

  • 一个进程可以启动N个线程

2.2 多线程(threading)

  • 优点:相比进程,更轻量级、占用资源少

  • 缺点:

    • 相比进程,多线程只能并发执行,并不能利用多CPU(GIL)
    • 相比协程,启动数目有限制,占用内存资源,有线程切换开销
  • 适用于:IO 密集型计算、同时运行的任务数目要求不多

  • 一个线程可以启动N个协程

2.3 多协程(Coroutine -> asyncio)

  • 优点:内存开销最少、启动协程数量最多
  • 缺点:支持的库有限制、代码实现复杂
  • 适用于:IO密集型计算、需要超多任务运行、但有现成库支持的场景

3. Python 的GIL

python慢的原因

  • 动态类语言,边解释边执行
  • GIL的存在导致python无法利用多核CPU并发执行

3.1 GIL是什么

全局解释器锁(Global Interpreter Lock)

是计算机程序设计语言解释器用于 同步线程的一种机制,它使得任何时刻仅有一个线程在执行

即便在多核处理器上,使用GIL的解释器也只允许同一时间执行一个线程。

python进程、线程、协程_第1张图片

即使电脑有多核CPU单个时刻也只能使用一个,相比较于并发加速的C++/Java慢很多

3.2 GIL 存在的原因

Python设计初期,为了规避并发问题引入了GIL,现在想去除却去除不掉了

引入GIL的原因:

  • 为了解决线程之间数据完整性状态同步问题

    • Python中对象的管理,是使用引用计数器进行的,引用数为0则释放对象

    • 例:线程A和线程B都引用的对象obj,obj.ref_num = 2,假如线程A和线程B都想撤销对obj的引用

python进程、线程、协程_第2张图片

  • GIL的好处简化了python对共享资源的管理

3.3 规避GIL带来的限制

1、多线程 threading 机制 还是有用的,用于IO密集型计算

  • 因为在 I/O (read、write、send、recv、etc.)期间,线程会释放 GIL , 实现CPU和IO并行,因此

    多线程用于IO密集型计算,依然可以大幅度提升速度

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

2、使用 multiprocessing 的多进程机制实现并行计算、利用多核CPU优势(为了应对GIL的问题,Python提供了multiprocessing)

3.4 并发与并行

并发:

​ 指的是任务数多余cpu核数,通过操作系统的各种任务调度算法,实现用多个任务“一起”执 行(实际上总有一些任务不在执行,因为切换任务的速度相当快,看上去一起执行而已)

并行:

​ 指的是任务数小于等于cpu核数,即任务真的是一起执行的。

4. 线程

4.1 创建多线程的方法

1、准备一个函数

def my_func(a,b)
	do_sum(a,b)

2、怎样创建一个线程

import threading
t = threading.Thread(target=my_func, args=(100,200))  # target 表示要执行的任务(传入函数名),args表示传入函数的参数

3、启动线程

t.start()

4、等待结束

t.join()

4.2 单线程与多线程对比

import blog_spider
import threading
import time

# 单线程
def single_thread():
    print("single_thread begin")
    for url in blog_spider.urls:
        blog_spider.craw(url)
    print("single_thread end")

# 多线程
def multi_thread():
    print("multi_thread begin")
    threads = []
    for url in blog_spider.urls:
        threads.append(
            threading.Thread(target=blog_spider.craw, args=(url,))
        )

    for thread in threads:
        thread.start()  # 开启线程

    for thread in threads:
        thread.join()  # 等待线程结束

    print("multi_thread end")

if __name__ == '__main__':
    start = time.time()  # 开始时间
    single_thread()
    end = time.time()   # 结束时间
    print("single thread cost:", end-start, "seconds")  # single thread cost: 8.59098196029663 seconds

    start = time.time()
    multi_thread()
    end = time.time()
    print("multi thread cost:", end - start, "seconds")  # multi thread cost: 0.82523512840271 seconds

4.3 多线程生产消费模式

4.5 线程池

4.5.1 线程池的原理

线程的生命周期:

python进程、线程、协程_第3张图片

新建线程系统需要分配资源、终止线程系统需要回收资源,如果可以重用线程,则可以减去新建/终止的开销

python进程、线程、协程_第4张图片

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

你可能感兴趣的:(python,python,多线程)