进程和线程的比较
进程的开销比线程的开销大很多
进程之间的数据是隔离的,但是,线程之间的数据不隔离
多个进程间的线程数据不共享----->让进程通信(IPC)---->进程下的线程也通信了---->队列
GIL全局解释器锁(重要理论)
虽然一个进程中开了多个线程,但在同一时刻只有一个线程在解释器中运行,全局解释器 锁(GIL)来保证
背景信息:
1、Python代码运行在解释器上嘛,有解释器来执行或者解释
2、 Python解释器的种类:CPython、IPython、PyPy、Jython、IronPython
官方下载 交互式 提高速度 Java字节码
当前市场使用的最多(95%)的解释器就是CPython解释器
GIL全局解释器锁是存在于CPython中
同一时刻只有一个线程在执行,GIL全局解释器锁是为了避免多个线程抢夺资源的情况
在设计之初,在解释器上添加了一把锁 GIL
哪个线程想执行,必须拿到这把锁,等释放掉,别的线程才能拿
问题:
- python有GIL锁的原因,同一个进程下多个线程,实际上同一时刻只有一个线程在执行
- 只有在python上开进程用的多,其他语言一般不开多进程,只开多线程就够了
- cpython解释器开多线程不能利用多核优势,只有开多进程才能利用多核优势,其他语 言不存在这个问题
- 8核cpu电脑,充分利用起我这个8核
- 如果不存在GIL锁,一个进程下,开启8个线程,它就能够充分利用cpu资源,跑满cpu
- cpython解释器中好多代码,模块都是基于GIL锁机制写起来的,改不了了----》开启多 进程---》每个进程下开启的线程,可以被多个cpu调度执行
- cpython解释器:io密集型使用多线程,计算密集型使用多进程
互斥锁
"""在多线程的情况下,同时执行一个数据,会发生数据错乱的问题"""
pythonn = 10 from threading import Lock import time def task(lock): lock.acquire() global n temp = n time.sleep(0.5) n = temp - 1 lock.release() from threading import Thread if __name__ == '__main__': tt = [] lock=Lock() for i in range(10): t = Thread(target=task, args=(lock, )) t.start() tt.append(t) for j in tt: j.join() print("主", n)
拿时间换空间,空间换时间 时间复杂度
面试题:既然有了GIL锁,为什么还要互斥锁? (多线程下)
第一个线程来了,拿到a=0,开始执行a=a+1,这个时候结果a就是1了
第一个线程得到的结果1还没有赋值回去给a,这个时候,第二个线程来了,拿到的a是 0,继续执行, a=a+1结果还是1
加了互斥锁,就能够解决多线程下操作同一个数据,发生错乱的问题
线程执行过快,还未赋值,下一个线程就上来了,所以加个互斥锁
GIL锁同时只能执行 一 个线程
线程队列
队列可以解决数据隔离问题(进程)
队列可以保持数据的安全(线程)
线程队列:1. 先进先出 2. 后进先出 3. 优先级的队列
python"""进程""" from multiprocessing import Queue """线程""" import queue queue.Queue()
queue.Queue的缺点是它的实现涉及多个锁和条件变量,因此可能会影响性能和内存效率
python"""先进先出""" import queue q=queue.Queue() # 无限大、 q.put('first') q.put('second') print(q.get()) print(q.get())
python"""后进先出""" import queue # Lifo:last in first out q=queue.LifoQueue() q.put('first') q.put('second') print(q.get()) print(q.get())
python"""优先级队列""" import queue q=queue.PriorityQueue() q.put((20,'a')) # put进入一个元组,元组的第一个元素是优先级,数字越小优先级越高 q.put((10,'b')) q.put((30,'c')) print(q.get()) # 数字越小优先级越高,优先级高的优先出队 print(q.get()) print(q.get())
进程池和线程池的使用(concurrent模块)
池:池子、容器类型,可以盛放多个元素
进程池:提前定义好一个池子,然后,往这个池子里面添加进程,以后,只需要往这个进 程池里面丢任务就行了,然后,有这个进程池里面的任意一个进程来执行任务
线程池:由任意一个线程来执行任务
开进程池
pythondef task(n, m): return n+m def task1(): return {'username':'kevin', 'password':123} from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor def callback(res): print(res) # Future at 0x1ed5a5e5610 state=finished returned int> print(res.result()) # 3 def callback1(res): print(res) # Future at 0x1ed5a5e5610 state=finished returned int> print(res.result()) # {'username': 'kevin', 'password': 123} print(res.result().get('username')) if __name__ == '__main__': pool=ProcessPoolExecutor(3) # 定义一个进程池,里面有3个进程 # pool=ThreadPoolExecutor(3) 改一下就是线程 pool.submit(task, m=1, n=2).add_done_callback(callback) ## 2. 往池子里面丢任务 pool.submit(task1).add_done_callback(callback1)
线程池
回调函数,等执行回调用这个函数
拿结果:print(res.result())
进程池中先主后子,如果想子进程都执行完再执行主进程:
pythonpool.shutdown() # join + close print(123)
协程理论
进程:资源分配的基本单位
线程: 执行的最小单位
协程:是程序员自己想出来的,不存在于操作系统中
并发:切换+保存状态
协程就是单线程下的并发 # 遇到I/O时
协程是最节省资源的,进程是最消耗资源的,其次是线程
监测有没有遇到IO,本质上就是最大限度的利用CPU资源
import gevent 模块 先安装,不是内置:pip install gevent
猴子补丁:就可以把gevent.sleep(2) 写成time.sleep(2)
pythonfrom gevent import monkey; monkey.path_all()
pythongevent.joinall([g1,g2]) # 相当于g1.join() g2.join()