# Python并发编程概述
# 参考
# 概览
- 进程和线程的概念 - 什么是进程 / 什么是线程 / 多线程的应用场景
- 使用进程 -
fork函数 /multiprocessing模块 / 进程池 / 进程间通信 - 使用线程 -
_thread模块/threading模块 /Thread类 /RLock类 /Condition类 / 线程池 - 使用协程- 单线程+异步IO/
yield/asyncio/gevent
# 概述
多线程、多进程和协程并发编程是用于实现并发执行的三种技术。
多线程是指在一个程序中同时运行多个线程,每个线程都有自己的执行流程和执行上下文。通常用于实现程序中不同部分之间的并行执行,以提高程序的执行效率。
多进程是指在一个程序中同时运行多个进程,每个进程都有自己独立的执行流程和执行上下文。通常用于实现程序中不同部分之间的并行执行,以提高程序的执行效率。
协程是一种轻量级的并发编程技术,它通过在单个线程中同时运行多个协程来实现并发执行。通常用于实现程序中不同部分之间的协作执行,以提高程序的执行效率。
# 多任务执行概念
什么叫 多任务 ?简单地说,就是操作系统可以同时运行多个任务。打个比方,一边在用浏览器上网,一边在听音乐,一边在用Word赶作业,这就是多任务,至少同时有3个任务正在运行。
还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。
现在,多核CPU已经非常普及了,但是即使过去的单核CPU,也可以执行多任务。由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?
答案就是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。
真正的并行执行多任务只能在 多核CPU 上实现,但是,由于任务数量远远多于 CPU 的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。
线程 thread 是 操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
- 并发性:是指两个或多个事件在同一时间间隔内发生。
- 并行性:是指两个或多个事件在同一时刻发生。
# 线程,进程和协程
进程是任务是计算机进行资源分配和调度的基本单位,拥有独立数据空间,进程间不共享数据。
线程是进程内的子任务,是执行程序的最小单位,进程内线程间共享资源(上下文)。
每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序(进程)中,由应用程序提供多个线程执行控制。
每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。
指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。
- 线程可以被抢占(中断)。
- 在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) -- 这就是线程的退让。
线程可以分为:
- 内核线程: 由操作系统内核创建和撤销。
- 用户线程: 不需要内核支持而在用户程序中实现的线程。
协程是由程序员调度,将一个线程分解为多个"微线程",实质上是单线程+异步IO的编程模型。
- 子程序切换不是线程切换而是程序自身控制,因此没有线程切换的开销
- 不需要多线程的锁机制-只存在一个线程
# 两种并发形式:threading 和 asyncio
Python中有基于Thread和基于Task的两种并发切换方式,分别对应Python 中并发的两种形式——threading 和 asyncio-基于线程编程(抢占) 和基于事件驱动编程(协作)。

对于 threading,操作系统知道每个线程的所有信息,因此它会做主在适当的时候做线程切换。很显然,这样的好处是代码容易书写,因为程序员不需要做任何切换操作的处理;但是切换线程的操作,也有可能出现在一个语句执行的过程中(比如 x += 1),这样就容易出现 race condition 的情况。
而对于 asyncio,主程序想要切换任务时,必须得到此任务可以被切换的通知,这样一来也就可以避免刚刚提到的 race condition 的情况。
# 使用场景
以下情况需要使用多进程:
- 程序执行计算密集型任务(如:字节码操作、数据处理、科学计算)。
- 程序的输入可以并行的分成块,并且可以将运算结果合并。
- 程序在内存使用方面没有任何限制且不强依赖于I/O操作(如:读写文件、套接字等)。
以下情况需要使用多线程:
- 程序需要维护许多共享的状态(尤其是可变状态),Python中的列表、字典、集合都是线程安全的,所以使用线程而不是进程维护共享状态的代价相对较小。
- 程序是 I/O bound,但是 I/O 操作很快,没有太多并行计算的需求且不需占用太多的内存。
以下情况需要使用协程:
- 程序是 I/O bound,但是 I/O 操作很慢。