一、引言
昨天讲了多任务和多进程,今天咱们接着将线程和协程.
二、多线程
1.线程的概念
线程是程序执行的最小单位, 实际上进程只负责分配资源 , 而利用这些资源执行程序的是线程 , 也就说进程是线程的容器 , 一个进程中最少有一个线程来负责执行程序 。同时线程自己不拥有系统资源,只需要一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。这就像通过一个QQ软件(一个进程)打开两个窗口(两个线程)跟两个人聊天一样 , 实现多任务的同时也节省了资源。
2.多线程完成多任务
① 导入线程模块 import threading ② 通过线程类创建线程对象 线程对象 = threading.Thread(target=任务名) ② 启动线程执行任务 线程对象.start()| 参数名 | 说明 |
|---|---|
| target | 执行的目标任务名,这里指的是函数名(方法名) |
| name | 线程名,一般不用设置 |
| group | 线程组,目前只能使用None |
2.1线程创建与启动代码
import time import threading def music(): for i in range(3): print('听音乐...') time.sleep(0.2) def coding(): for i in range(3): print('敲代码...') time.sleep(0.2) if __name__ == '__main__': music_thread = threading.Thread(target=music) coding_thread = threading.Thread(target=coding) music_thread.start() coding_thread.start()2.2线程执行带有参数的任务
| 参数名 | 说明 |
|---|---|
| args | 以元组的方式给执行任务传参 |
| kwargs | 以字典方式给执行任务传参 |
import time import threading def music(num): for i in range(num): print('听音乐...') time.sleep(0.2) def coding(count): for i in range(count): print('敲代码...') time.sleep(0.2) if __name__ == '__main__': music_thread = threading.Thread(target=music, args=(3, )) coding_thread = threading.Thread(target=coding, kwargs={'count': 3}) music_thread.start() coding_thread.start()3.主线程和子线程的结束顺序
import time import threading def work(): for i in range(10): print('work...') time.sleep(0.2) if __name__ == '__main__': # 创建子进程 work_thread = threading.Thread(target=work) # 启动线程 work_thread.start() # 延时1s time.sleep(1) print('主线程执行完毕')设置守护线程方式一:
import time import threading def work(): for i in range(10): print('work...') time.sleep(0.2) if __name__ == '__main__': # 创建子线程并设置守护主线程 work_thread = threading.Thread(target=work, daemon=True) # 启动线程 work_thread.start() # 延时1s time.sleep(1) print('主线程执行完毕')设置守护线程方式二:
import time import threading def work(): for i in range(10): print('work...') time.sleep(0.2) if __name__ == '__main__': # 创建子线程 work_thread = threading.Thread(target=work) # 设置守护主线程 work_thread.setDaemon(True) # 启动线程 work_thread.start() # 延时1s time.sleep(1) print('主线程执行完毕')4.线程之间的执行顺序
线程之间的执行是无序的.
5.线程执行顺序验证
获取当前线程信息
# 通过current_thread方法获取线程对象 current_thread = threading.current_thread() # 通过current_thread对象可以知道线程的相关信息,例如被创建的顺序 print(current_thread)线程间的执行顺序
import threading import time def get_info(): # 可以暂时先不加,查看效果 time.sleep(0.5) current_thread = threading.current_thread() print(current_thread) if __name__ == '__main__': # 创建子线程 for i in range(10): sub_thread = threading.Thread(target=get_info) sub_thread.start()线程之间执行是无序的,是由CPU调度决定某个线程先执行的。
6.线程间共享全局变量
这点很好理解,前面我们讲了进程不共享全局变量的原因是不同进程都把最开始的全局变量复制了一份给自己,就好比孙悟空的分身,虽然是由孙悟空这个全局变量分出的,但每一个分身都可以看作是一个个体,对本体造不成任何影响.但是线程本质是同一个进程里的东西,就好比分身的四肢,四肢变化了,对本体肯定是有影响的,不知道这样形容大家有没有觉得更形象一点.
7.进程和线程的对比
7.1关系对比
① 线程是依附在进程里面的,没有进程就没有线程。
② 一个进程默认提供一条线程,进程可以创建多个线程。
7.2区别对比
① 进程之间不共享全局变量
② 线程之间共享全局变量
③ 创建进程的资源开销要比创建线程的资源开销要大
④ 进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
7.3优缺点对比
进程优缺点:
优点:可以用多核
缺点:资源开销大
线程优缺点
优点:资源开销小
缺点:不能使用多核
三、多协程
1.python中的生成器
根据程序设计者制定的规则循环生成数据,当条件不成立时则生成数据结束
数据不是一次性全部生成出来,而是使用一个,再生成一个,可以节约大量的内存。
创建生成器的方式
① 生成器推导式
生成器推导式与列表推导式类似,只不过生成器推导式使用小括号。
# 创建生成器 my_generator = (i * 2 for i in range(5)) print(my_generator) # next获取生成器下一个值 # value = next(my_generator) # print(value) # 遍历生成器 for value in my_generator: print(value)生成器的相关函数:
next 函数获取生成器中的下一个值
for 循环遍历生成器中的每一个值
② yield 关键字
yield 关键字生成器的特征:在def函数中具有yield关键字
def generator(n): for i in range(n): print('开始生成...') yield i print('完成一次...') g = generator(5) print(next(g)) print(next(g)) print(next(g)) print(next(g)) print(next(g)) -----> 正常 print(next(g)) -----> 报错注意点:
① 代码执行到 yield 会暂停,然后把结果返回出去,下次启动生成器会在暂停的位置继续往下执行
② 生成器如果把数据生成完成,再次获取生成器中的下一个数据会抛出一个StopIteration 异常,表示停止迭代异常
③ while 循环内部没有处理异常操作,需要手动添加处理异常操作
④ for 循环内部自动处理了停止迭代异常,使用起来更加方便,推荐大家使用。
yield关键字和return关键字
如果不太好理解yield,可以先把yield当作return的同胞兄弟来看,他们都在函数中使用,并履行着返回某种结果的职责。
这两者的区别是:
有return的函数直接返回所有结果,程序终止不再运行,并销毁局部变量;
def example(): x = 1 return x example = example() print(example)而有yield的函数则返回一个可迭代的 generator(生成器)对象,你可以使用for循环或者调用next()方法遍历生成器对象来提取结果。
def example(): x = 1 y = 10 while x < y: yield x x += 1 example = example() print(example)2.Python中的协程
Python 的协程实现确实是从生成器发展而来的。
| 特性 | 生成器 (Generator) | 协程 (Coroutine) |
|---|---|---|
| 主要目的 | 生成值序列 | 执行异步任务 |
| 控制流 | 单向:调用者 → 生成器 | 双向:调用者 ↔ 协程 |
| 数据流向 | 数据向外流动(产出值) | 数据双向流动(发送和接收) |
| 调度 | 由调用者驱动 | 由事件循环调度 |
| 关键字 | yield | async,await |
| 类型 | Generator | Coroutine |
Python 协程是由async def定义的异步函数,它是一种实现了Awaitable协议的状态保持执行单元。其执行由事件循环调度,通过await表达式主动挂起,将控制权交还调度器,实现协作式并发。
协程三要素:
- 函数前加 async
- 等待处加 await
- 启动用 asyncio.run
使用三步骤:
- 定义 async 函数
- 用 create_task 创建任务
- 用 await 等待结果
3.进程、线程、协程最简单记忆方法
- 协程:单线程魔术师,手里抛接多个球(I/O等待时换件事做)
- 线程:多个魔术师,但只有一个能表演(GIL限制)
- 进程:多个魔术师,各自独立表演 (完全独立)
四、结语
多任务变成这块就告一段落了,后面会继续讲正则表达式的相关概念.