目录
0. 前言
1. 进程学习总结
1.1 进程的理解
1.2 进程的三种状态
1.3 multiprogressing模块
1.3.1 Process类
1.3.2 current_process
1.3.3 active_children
1.3.4 Queue客户
1.3.5 Pool类
1.3.6 Manager().Queue()
2. 线程学习总结
2.1 线程的理解
2.2 threading模块
2.2.1 Thread类
2.2.2 Lock类
2.2.3 enumerate
2.2.4 current_thread
2.2.5 active_count
2.3 线程池实现: multiprogressing.dummy模块
3. 协程学习总结
3.1 协程理解
3.2 协程详解及实现
3.2.1 yield实现
3.2.2 greenlet模块
3.2.3 gevent模块
3.3 协程池实现: gevent.pool模块
4. 结束语
0. 前言
在日常工作中经常会碰到并发执行的问题,免不了要和各类python模块打交道,在此记录下自己的理解,方便以后重拾。
1. 进程学习总结
1.1 进程的理解
- 进程是操作系统进行资源分配和调度的基本单位;
- 一个运行起来的软件/程序叫一个进程,进程只提供运行资源,真正干活的是线程;
- 不同进程之间不共享任何状态,每个进程都有自己独立的内存空间,进程间的通讯需要通过操作系统执行信号的传递(queue).
1.2 进程的三种状态
图
就绪态:运行的条件都已经满足,正在等cpu调度执行;
执行态:cpu正在执行其功能;
等待态度:等待某些条件满足,例如一个程序sleep了,此时就处于等待态;
1.3 multiprocessing模块
图
1.3.1 Process类
参数:target/name/ages/kwargs/group;
is_alive(): 判断进程是否活着;
start(): 开始进程;
join(): 进程等待;
daemon: 属性,守护进程;
terminate(): 立即终止进程,只在子进程外使用;
1.3.2 current_process()
获取进程编号: current_process().pid[当前] os.getpid()[当前] os.getppid()[父进程];
杀死进程: os.kill(is.getpid(), signal.SIGKILL/9);
1.3.3 active_children()
获取当前活动的子进程;
1.3.4 Queue类
进程间通讯(先进先出):
参数: 上限数据个数,不写/负数则无上限[multiprocessing.Queue(3)];
put()/get(): 放入/获取[有阻塞功能;参数:内容,是否阻塞,阻塞时间]:
put(‘消息’, True, 2) / put(‘消息’, False);
get(True, 2)/get(False);
put_nowait()/get_nowait(): 放入/获取[不会阻塞,会报错]:
put_nowait(‘消息’) 等价于 put(‘消息’, False);
get_nowait() 等价于 get(False);
full(): 是否满了;
empty(): 是否空了;
qsize(): 队列中数据个数;
1.3.5 Pool类
参数: 上限进程个数;
apply()/apply_sync(): 同步/异步执行;
close(): 关闭Pool,不再接受新功能,进程池关闭;
terminate(): 不管任务是否完成,立即终止;
join(): 进程池等待,必须用在close/terminate之后;
1.3.6 Manager().Queue()
进程池中的进程间通讯,用法同Queue();
2. 线程学习总结
2.1 线程理解
- 进程的一个实体,是cpu调度和分派的基本单位;
- 同一个进程内的不同线程,共享同一个进程内的内存空间;
- 线程之间切换开销小,通讯效率高,可以共享内存空间;
- GIL(全局解释器锁): 程序执行活动中,一次只能执行一个线程;
好处: 直接杜绝了多个线程同时访问进程内存空间的安全问题;
坏处: python中的多线程并不是真正的多线程,一次只能执行一个线程,不能充分利用cpu多核资源;- 缺点: 同一时间内存一次只能运行一个线程,可以做到并发,不能做到并行;
- 使用场景: IO密集任务(网络IO、磁盘IO、数据库IO等);
2.2 threading 模块
图
2.2.1 Thread类
参数: target/name/args/kwargs/daemon/group;
start(): 线程开始执行[调用start()创建新线程,同时底层调用run()完成逻辑];
setDaemon(True): 守护主线程(主线程为主),主线程执行完,子线程立即结束[子线程start()之前设置];
join(): 线程等待(子线程为主),子线程执行完才执行主线程[子线程start()之后设置];
run(): 自定义线程类中,需要重写run()方法run()方法结束该线程就结束;
2.2.2 Lock类
创建锁: mutex = threadind.Lock()
上锁: mutex.acquire()
解锁: mutex.release()
2.2.3 enumerate()
当前活动线程列表[没启动的线程不算];
2.2.4 current_thread()
当前线程对象;
2.2.5 active_count()
活动线程数量;
2.3 线程池实现: multiprocessing.dummy模块
from multiprocessing.dummy import Pool
# 1.初始化线程池对象
pool = Pool()
# 2.通过pool.map()依次将list里每个元素传递给func处理,并通过多线程----异步执行;
# 参数: func(必须) list(必须)
return_list = pool.map(func, list)
# 通过pool.map_async()依次讲list里每个元素传递给func处理,并通过多线程异步执行;
# 将每个函数返回值放入列表,再传给callback执行;
pool.map_async(func, list, callback)
# 2.如果func不需要传参,可以使用apply(逐条处理)和apply_async(异步执行);
# 参数: func(必须) args(可选)
pool.apply(func, args)
# 将args参数传递给func,并接收返回值交给callback执行;
pool.apply_async(func, args, callback)
# 关闭线程池,线程等待
pool.close()
pool.join()
3. 协程学习总结
3.1 协程理解
- 微线程: 本质就是一个函数,不由操作系统调度(没有cpu切换开销);
- 在单个线程上切换执行多个函数任务,切换调度由程序员决定,不涉及操作系统管理,也不需要处理锁;
- 缺点: 单县城执行,不能处理密集cpu任务,本地磁盘IO密集任务;
- 使用场景: 网络密集IO任务;
3.2 协程详解及实现
图
3.2.1 yield实现
- 多个功能通过yield交替执行,模拟多线程;
- 缺点:
死循环,没法执行次数;
依旧单线程,只是模拟多任务;
使用方式如下:
# 创建生成器/协程
generator = func()
# 执行生成器/协程
next(generator)
3.2.2 greenlet模块
- 封装了yield,需要手动交替调用;
- 不能存在循环应用(官方文档明确说明);
使用方式如下:
# 创建协程
g1 = greenlet.greenlet(func)
# 协程开始
g1.switch()
3.2.3 gevent模块
- 封装了yield,自动交替调用;
- 不识别input(),涉及到尽量使用线程/进程;
使用方式如下:
# 打补丁
from gevent import monkey
monkey.patch_all()
# 创建协程
g = gevent.spawn(func)
# 协程等待
g.join()
# 2、3步合并写法
gevent.joinall([
gevent.spawn(func, *args, **kwargs),
gevent.spawn(func, *args, **kwargs),
])
3.3 协程池实现: gevent.pool模块
用法和线程池一样,但是导包不同,协程池也不需要close();
from gevent.pool import Pool
# 1.导入协程patch_all()补丁,执行该补丁,则当前解释器环境的任务,全部按异步方式执行
from gevent import monkey
monkey.patch_all()
# 2.实例化协程池对象
pool = Pool(10)
# 3.异步执行任务,和线程池一样
pool.apply_async(send_request_func, args=(url,), callback=use_result)
# 4.线程等待,不需要close()
pool.join()