进程
处理高并发
https://blog.csdn.net/weixin_37988176/article/details/109376554
Python怎么处理高并发?
使用协程, 事件循环, 高效IO模型(比如多路复用,比如epoll), 三者缺一不可。
进程
程序: 应用软件,一堆代码文件.
进程: 一个正在执行的程序/文件,抽象的概念.
数据隔离 开启/调度/销毁开销大 能利用多核, 是资源分配的最小单位,适合处理高计算型的
1、 单个cpu:
多道技术:
空间上的复用:(内存可以加载很多个不同的任务)
时间上的复用:
1, 遇到进程中的IO时,cpu就切换
IO(input output):用户输入input,f.read()
目的:提高效率
2, 一个进程长时间被cpu处理时,操作系统就强硬的将cpu调出去处理下一个进程.
目的:雨露均沾
2、 串行:cpu将一些进程一个接一个的执行
并发:一个cpu在同一个时刻处理不同的进程,任务
并行:真正意义的一对一服务
阻塞:IO成为阻塞
非阻塞:进程没有IO,就非阻塞
同步:两个任务一个接一个的进行
异步:两个任务能够同时进行
一个进程三个状态
运行,阻塞,就绪
查看子进程和父进程
子进程: pid
父进程: ppid
1.查看进程号:
1).终端查看 tasklist
2).本文件也可以看
import time
import os
a = 1
b = 2
print('子进程:',os.getpid())
print('父进程:',os.getppid())
time.sleep(20)
print(a+b)
多进程应用场景: 各种循环处理,计数等
多线程场景: 文件处理,网络爬虫等
概念:
进程 数据隔离 开启/调度/销毁开销大 能利用多核, 是资源分配的最小单位,适合处理高计算型的。
线程 数据共享 开销小 能利用多核(除了cpython解释器),是CPU调度的最小单位.,每一个进程中至少有一个线程。
协成 一个线程中的多个任务之间可以自由切换工作的,遇不遇到IO都可以随时切换
1. 不能利用多核
2.用户级的概念,操作系统不可见
3.协程不存在数据不安全问题
2.创建进程的两种方式
windows环境下想开启子进程一定要 name == 'mian
p = Process(target=task,kwargs={'n':1}) # p就是一个进程对象
创建一个进程对象,将进程的代码放进进程对象中.
p.start() 会给操作系统发送一个请求,
操作系统得到请求命令,会在内存开辟一个空间,操作系统会将主进程所有的代码数据copy一份到子进程,
.这个过程会有时间消耗.
print('主进程开始.....') 主进程开始 和子进程开始,谁先到内存被cpu执行,谁先运行
1)multiprocess模块
a = 1
b = 2
def aa(c):
print('%s is running'%c)
if __name__ == '__main__':
# p = Process(target=aa,args=(1,))
p = Process(target=aa,kwargs={'c':1})
p.start()
print('主进程') #1 is running
from multiprocessing import Process
class Myprocess(Process):
# 先要执行父类的__init__
def __init__(self,n):
super(Myprocess, self).__init__()
self.n = n
def run(self):
# 子进程的代码都要写在这里
a = 1
b = 2
print('%s is running'%self.n)
if __name__ == '__main__':
p = Myprocess('666')
p.start()
print('主线程')
3.验证进程间的内存隔离(改了就是没隔离)
from multiprocessing import Process
import time
x = 100
def task():
time.sleep(2)
global x
x = 3
print('子进程',x)
if __name__ == '__main__':
p = Process(target=task,)
p.start()
time.sleep(5)
print('主:',x) #结果:子进程 3 主: 100
4.主进程在子进程结束后运行
1)用time.sleep() 2)p.join()
from multiprocessing import Process
import time
x = 100
def task(x):
print('%s is begin'%x)
time.sleep(3)
print('%s is over'%x)
def main():
print('主:')
if __name__ == '__main__':
p = Process(target=task,args=(1,))
p.start()
p.join()
main()
5.一个主进程开启多个子进程( for循环)
十个子进程请求几乎是同同时发起,谁先到达操作系统,操作系统给谁开辟空间,让cpu执行.
from multiprocessing import Process
import time
x = 100
def task(x):
print('%s is begin'%x)
time.sleep(3)
print('%s is over'%x)
def main():
print('主:')
if __name__ == '__main__':
for i in range(1,11):
p = Process(target=task,args=(1,))
p.start()
p.join()
main()
6.这种方法执行效率高
1)
from multiprocessing import Process
import time
x = 100
def task(x):
print('%s is begin'%x)
time.sleep(3)
print('%s is over'%x)
def main():
print('主:')
if __name__ == '__main__':
p1 = Process(target=task,args=('p',))
p2 = Process(target=task,args=('p2',))
p3 = Process(target=task,args=('p3',))
start_time = time.time()
p1.start()
p2.start()
p3.start()
p1.join()
p2.join()
p3.join()
print(time.time()-start_time)
main() #结果: 3.2413246631622314秒(并发)
2)这种方法执行效率低
from multiprocessing import Process
import time
x = 100
def task(x):
print('%s is begin'%x)
time.sleep(3)
print('%s is over'%x)
def main():
print('主:')
if __name__ == '__main__':
p1 = Process(target=task,args=('p',))
p2 = Process(target=task,args=('p2',))
p3 = Process(target=task,args=('p3',))
start_time = time.time()
p1.start()
p1.join()
p2.start()
p2.join()
p3.start()
p3.join()
print(time.time()-start_time)
main() #结果为:9.32131531515秒 (串行)
7.参数
- P1.name 起名字
- p1.start() 开启进程
- p1.terminate() 杀死进程
- p1.is_alive() True or false 判断进程的死活
- p1.join() 通知p,子程序结束后再执行主程序
- p1.pid 获取子程序
8.僵尸进程 与 孤儿进程
linux 系统:
你的进程空间在内存中真正的消失才算是进程结束.
1) 僵尸进程: 死而不腐, p1 p2 p3 在代码结束时都没有完全死掉,
他们会形成一个僵尸进程:僵尸进程只会保存pid,运行时间,状态.
2) 僵尸进程的回收,是由主进程发起的
3) 孤儿进程?
你的主进程意外挂了,剩下 p1 p2 p3 就形成 孤儿进程
所以你的孤儿都会交给'民政局'处理. init 是 Linux 所有进程的主进程.
4) 僵尸进程有害的.
僵尸进程他的回收取决于主进程,如果主进程产生了大量的子进程,但是不着急回收这些变成了的僵尸进程.
孤儿进程 无害 都会被民政局 init 回收.
Linux系统:具体回收方法: waitpid()
windows系统:
p1.join()源码中 有waitpid()方法
9.守护进程
子进程 守护 主进程
就一个参数 p1.daemon=True #你的p1进程就设置成了守护进程 主进程结束子进程也会跟着结束(不会等子进程)
def fun11():
print '子进程'
time.sleep(5)
t = Process(target=fun11,)
t.Daemon=True
t.start()
time.sleep(3)
print '主进程程' #3秒之后就结束
10.进程锁
from multiprocessing import Process,Lock
import time
import json
def search_ticket(name):
# 查票
with open('ticket') as f:
ticket_dict = json.load(f)
time.sleep(0.1)
print('%s: 当前余票%s张'%(name,ticket_dict['count']))
def buy_ticket(name,lock):
# lock.acquire()
with open('ticket') as f:
ticket_dict = json.load(f)
time.sleep(0.1)
if ticket_dict['count']>0:
print('%s 查询余票%s'%(name,ticket_dict['count']))
print('%s 购票成功'%name)
ticket_dict['count'] -=1
with open('ticket','w') as f :
json.dump(ticket_dict,f)
# lock.release()
# def user_system(name,lock):
# search_ticket(name)
# lock.acquire()
# buy_ticket(name,lock)
# lock.release()
def user_system(name,lock):
search_ticket(name)
with lock:
buy_ticket(name,lock)
if __name__ == '__main__':
lock = Lock()
for i in range(5):
p=Process(target=user_system,args=('x'+str(i),lock))
p.start()
11.生产者 消费者
import time
import random
from multiprocessing import Process, Queue
def consumer(q, name):
while True:
task = q.get()
if task is None:
break
time.sleep(random.random())
print('%s吃了%s' % (name, task))
def produce(q, n):
for i in range(n):
time.sleep(random.uniform(1, 2))
print('生产了泔水%s' % i)
q.put('泔水%s' % i)
if __name__ == '__main__':
q = Queue()
pro_l = []
for i in range(3):
p = Process(target=produce, args=(q, 5))
p.start()
pro_l.append(p)
p1 = Process(target=consumer, args=(q, 'alex'))
p2 = Process(target=consumer, args=(q, 'wusir'))
p1.start()
p2.start()
for p in pro_l:
p.join()
q.put(None)
q.put(None)
12.进程队列 进程间的通信 IPC 先进先出
在多进程中所有的判断空 判断满 判断个数 的方法都不准确
q=Queue() q.put() q.get()
q.empty() # 判断队列是否为空
q.full() # 判断队列是否为满
q .qsize() # 队列中数据的个数
q.get_nowait() (数据不会丢)
q.put_nowait()(数据容易丢)
def pro(q):
q.put(1)
# q.put(2)
print('pro:')
print('-->',q.get())
def con(q):
# q.get()
# q.get()
print('con:%s'%q.get())
q.put(3)
if __name__ == '__main__':
q = Queue()
print('q.get :%s' % q.get())
p = Process(target=pro,args=(q,))
p.start()
p1 = Process(target=con, args=(q,))
p1.start()
数据共享
from multiprocessing import Manager
def fun(dic,lock):
with lock:
dic['count'] -=1
# print(999)
if __name__ == '__main__':
m = Manager()
dic = m.dict({"count":100})
pro_l = []
lock = Lock()
for i in range(10):
p = Process(target=fun,args=(dic,lock))
p.start()
pro_l.append(p)
for j in pro_l:
j.join()
print(dic)
开启多进程处理多任务是不是真的高效?
没有阻塞的话,那么起多进程效率反而降低
没有IO都是计算
一个进程的开销
进程的开启
进程的销毁
进程的调度
进程池 异步提交
使用进程池,只需要开四个进程的时间开启销毁一个进程的时间开销小
使用多进程,有1000个任务,就得开1000个进程,开启和销毁的时间比较长,开销大
使用get来获取apply_aync的结果,如果是apply,则没有get方法,因为apply是同步执行,立刻获取结果,也根本无需get
进程池
apply_async(fun,args=())
一般的进程,线程还得写上target,args()
进程池的异步提交
apply提交任务,async异步
p.close()p.join()执行的是函数里边的结果,不写close join ,直接pring(ret.get())是执行return的结果
例:
from multiprocessing import Pool
import os
def fnc(i):
i*i
# print(os.cpu_count())
# print(i,os.getpid())
if __name__ == '__main__':
#使用进程池的时间
s = time.time()
p = Pool() #默认就是CPU的个数
for i in range(100):
p.apply_async(fnc,(i,)) #apply 提交任务,async 异步
p.close() #关闭进程池,不允许再往这个池子添加任务了
p.join() #阻塞,直到已经被提交到进程池的任务全部结束
print('111',time.time()-s) #0.13223505020141602
# 不使用进程池的时间
d = time.time()
p_l = []
for j in range(1000):
p1 = Process(target=fnc,args=(j,))
p1.start()
p_l.append(p1)
for n in p_l:
n.join()
print(time.time()-d) #3.176100254058838
进程池中异步提交获取返回值 .get()方法
from multiprocessing import Pool
import os
def fnc(i):
time.sleep(1)
return i*i
if __name__ == '__main__':
#
p = Pool() #默认就是CPU的个数
ret_l = []
for i in range(100):
ret = p.apply_async(fnc,(i,)) #apply 提交任务,async 异步
# print(ret.get()) #结果是一个一个的出,看似是异步其实是同步
ret_l.append(ret)
for ret in ret_l:
print(ret.get()) #结果是4个4个出结果,谁先回来先取谁
# 第二种 加上close 和 join
p = Pool() # 默认就是CPU的个数
ret_l = []
for i in range(100):
ret = p.apply_async(fnc, (i,)) # apply 提交任务,async 异步
ret_l.append(ret)
p.close()
p.join()
for ret in ret_l:
print(ret.get()) # 结果是等所有的结果都回来了,再一次性取出
进程池 同步提交apply() 几乎不用
def foo(i):
# time.sleep(random.random())
print(i,os.getpid())
if __name__ == '__main__':
s = time.time()
p = Pool()
for i in range(20):
p.apply(foo,(i,))
print(time.time()-s)
进程池 map方法
map就是一种简便的apply_async的方式,并且内置了close和join的功能
map(fun,’必须是可迭代的’)
from multiprocessing import Pool
import time
def func(i,):
i*i
time.sleep(1)
return 'i'*i
if __name__ == '__main__':
p = Pool(4)
ret_l = p.map(func,range(50)) #map就是一种简便的apply_async的方式,并且内置了close和join的功能
for ret in ret_l:
print(ret)
进程池 回调函数 callback
import os
from urllib import request
from multiprocessing import Pool
def aa(content):
print(os.getpid())
print('len' ,len(content))
def get_url(i):
ret=request.urlopen(i)
content=ret.read().decode('utf-8')
return content
if __name__ == '__main__':
print(os.getpid())
url_list = [
'http://www.cnblogs.com/Eva-J/articles/8253549.html', # 1
'http://www.cnblogs.com/Eva-J/articles/8306047.html', # 0.05
'http://www.baidu.com',
'http://www.sogou.com',
'https://www.cnblogs.com/Eva-J/p/7277026.html'
]
p=Pool()
# ret_l=[]
for i in range(5):
ret=p.apply_async(get_url,(i,),callback=aa)
# ret_l.append(ret)
# for i in ret_l:
p.close()
p.join()
# res=i.get()
# aa(res)
线程
参数
获取id: p.ident
获取名字: p.name
currentThread模块也可以获取id 名字
enumerate(): 当前所有活着的线程(开启的线程)对象组成的列表
Active_count(): 统计当前活着的线程对象的个数(len(enumerate()))
主进程(住线程)会等待子进程(子线程)结束之后才结束
特点
数据共享 开销小 能利用多核(除了cpython解释器),是CPU调度的最小单位.,每一个进程中至少有一个线程
线的数据共享
from threading import Thread
n =100
def func():
global n
n -=1
t_l = []
for i in range(100):
t = Thread(target=func,)
t.start()
t_l.append(t)
for t in t_l:
t.join()
print(22,n)
守护线程 setdaemon(True)
<span style='color:blue'>守护进程 会等待主进程的代码结束而结束
守护线程 会等待主线程的结束而结束
如果主线程还开启了其他子线程,那么主线程死了,守护线程会守护 其他子线程(非守护线程)到最后
主进程必须后结束,回收子进程的资源
线程是属于进程的,主线程如果结束了,那么整个进程就结束了</span>
#守护线程
from threading import Thread
import time
def fun1():
while True:
print '子线程'
time.sleep(5)
t = Thread(target=fun1,)
t.setDaemon(True)
t.start()
time.sleep(3)
print '主线程' #3秒之后就结束
#守护线程 还有其他子线程
def fun1():
while True:
print '子线程'
time.sleep(50)
# print os.getpid()
def foo():
time.sleep(10)
print 'ziianc'
t = Thread(target=fun1,)
t.setDaemon(True)
t.start()
Thread(target=foo).start()
time.sleep(3)
print '主线程' #会等10秒结束
GIL锁(全局解释器)
GIL 全局解释器锁
锁的是线程 让同一个进程中的多个线程同一时刻只能有一个线程被CPU调度
互斥锁
在同一个线程只能被acquire一次
递归锁;RLock
在一个线程内可以被acquire多次而不被锁住,(必须acquire多少次,release多少次 (科学家吃面))
死锁现象(用递归锁解决)
同一个线程中 出现了两把锁 同时锁两个资源去进行某些操作(使用操作不当)
Cpython解释器:高计算型 开多进程 高IO型 开多线程
死锁的例子:
from threading import Thread,Lock
mian_lock = Lock()
forck_lock = Lock()
def eat(name):
mian_lock.acquire()
print('%s拿到面了'%name)
forck_lock.acquire()
print('%s拿到叉子了'%name)
time.sleep(0.1)
print('%s迟到面了'%name)
forck_lock.release()
print('%s放下叉子'%name)
mian_lock.release()
print('%s放下面了'%name)
def eat2(name):
forck_lock.acquire()
print('%s拿到叉子了'%name)
mian_lock.acquire()
print('%s拿到面了'%name)
time.sleep(0.1)
mian_lock.release()
print('%s放下面'%name)
forck_lock.release()
print('%s放下叉子了'%name)
Thread(target=eat,args=('11',)).start()
Thread(target=eat2,args=('22',)).start()
Thread(target=eat,args=('33',)).start()
Thread(target=eat2,args=('44',)).start()
结果:哼在那儿了
用递归锁RLock() 解决 死锁现象:
from threading import Thread,Lock,RLock
mian_lock =forck_lock = RLock()
def eat(name):
mian_lock.acquire()
print('%s拿到面了'%name)
forck_lock.acquire()
print('%s拿到叉子了'%name)
time.sleep(0.1)
print('%s迟到面了'%name)
forck_lock.release()
print('%s放下叉子'%name)
mian_lock.release()
print('%s放下面了'%name)
def eat2(name):
forck_lock.acquire()
print('%s拿到叉子了'%name)
mian_lock.acquire()
print('%s拿到面了'%name)
time.sleep(0.1)
mian_lock.release()
print('%s放下面'%name)
forck_lock.release()
print('%s放下叉子了'%name)
Thread(target=eat,args=('11',)).start()
Thread(target=eat2,args=('22',)).start()
Thread(target=eat,args=('33',)).start()
Thread(target=eat2,args=('44',)).start()
结果:全吃到面了
使用互斥锁,一般情况下,能用一把锁解决的事,就不用 两把锁:
from threading import Thread,Lock,RLock
lock = Lock()
def eat(name):
lock.acquire()
print('%s拿到面了'%name)
print('%s拿到叉子了'%name)
time.sleep(0.1)
print('%s迟到面了'%name)
print('%s放下叉子'%name)
print('%s放下面了'%name)
lock.release()
def eat2(name):
lock.acquire()
print('%s拿到叉子了'%name)
print('%s拿到面了'%name)
time.sleep(0.1)
print('%s放下面'%name)
print('%s放下叉子了'%name)
lock.release()
Thread(target=eat,args=('11',)).start()
Thread(target=eat2,args=('22',)).start()
Thread(target=eat,args=('33',)).start()
Thread(target=eat2,args=('44',)).start()
结果:也是全吃到面了
线程队列
-
(IPC机制 所有队列都自带锁,安全) fifo 先进先出 lifo 先进后出
Import queue
q=queue.Queue()
q.put(1)
print(q.get())
import queue
q=queue.LifoQueue()
队列和栈的区别
先进先出fifo ,是未来做生产者消费者模型或者对外提供服务的最好的工具,标识着先来请求我的服务的人先得到我的服务
栈 = 后进先出的队列 lifo :后来 请求我的服务的人先服务他,有点不公平,所有栈不是用来做生产者消费者模型或者 对外服务的
栈更多用在算法里
2)优先级队列 PriorityQueue
以元祖的形式,先按第一个值,数值越小,优先级越高,要是第一个值一样,按第二个值的ascii码 决定谁先谁后
应用场景:比如买票,充会员的和普通票,同时进入队列,会优先处理充会员的
import queue
q=queue.PriorityQueue()
pq.put((2,'alex'))
pq.put((1,'wusir'))
pq.put((1,'yuanhao'))
pq.put((3,'taibai'))
print(pq.get())
print(pq.get())
print(pq.get())
print(pq.get())
结果:
(1, 'wusir')
(1, 'yuanhao')
(2, 'alex')
(3, 'taibai')
写一个三级菜单
menu = {'北京':
{
'海淀':{
'五道口':{
'goole':{},
'soule':{}
},
'中关村':{
'爱奇艺':{},
'汽车之家':{}
},
'上地':{
'百度':{}
},
},
'昌平':
{
'沙河':{'松兰堡':'圆梦家园'},
'高教园':{},
}
},
'上海':{
'迪士尼':{}
}
}
代码如下:
l = [menu]
while 1:
for i in l[-1]:print(22,i)
k = input('>>>').strip()
if k in l[-1].keys() and l[-1][k]:
l.append(l[-1][k])
print(111, l)
elif k =='b':l.pop()
elif k=='q':break
线程池concurrent.futures
用map(fun,可迭代)
concurrent.futures模块提供了高度封装的异步调用接口
ThreadPoolExecutor:线程池,提供异步调用
ProcessPoolExecutor: 进程池,提供异步调用
shutdown(wait=True) 相当于进程池的pool.close()+pool.join()操作
使用submit 提交任务
例:
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
s1 = time.time()
def fun1(i):
time.sleep(1)
print('is son thread %s'% i)
tp = ThreadPoolExecutor(40)
for i in range(20):
tp.submit(fun1,i)
tp.shutdown() #不写shutdown, 会先执行下面的代码,写了的话,会最后执行下面代码
print('全部执行完成')
print(time.time()-s1)
2.as_completed用法, 使用多线程的 先返回的任务先处理的方法,比如,多线程请求20个函数式计算任务,但是只需要先返回的10个任务即可,剩下的10个任务 忽略,这样能保证 最大程度最大概率的 前10个任务返回总体时间很短,从而 优化接口执行时间。
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor ,as_completed
def get_ali_fun(times):
time.sleep(times)
return times
with ThreadPoolExecutor(5) as f:
ab = [3,2,6]
all_task = [f.submit(get_ali_fun,i) for i in ab]
result_list = []
for future in as_completed(all_task):
data = future.result()
result_list.append(data)
print('获取数据,耗时{}s'.format(data))
if len(result_list) == 2:
print('先返回的数据已经满足条件,则 剩下的1个线程结果不在处理。。。')
break
print('继续处理下面程序')
as_completed() 方法是一个生成器,先完成的任务会先通知主线程。
** map()方法**
def fun1(i):
time.sleep(1)
print('is son thread %s'% i)
return 111
tp = ThreadPoolExecutor(40)
tp.map(fun1,range(20))
** map方法 获取返回值**
def fun1(i):
time.sleep(1)
# print('is son thread %s'% i)
return 111
tp = ThreadPoolExecutor(4)
ret_l = tp.map(fun1,range(20))
for i in ret_l:
print(i) #四个四个的出
使用submit提交任务并获取值, 用 result
def fun1(i):
time.sleep(1)
return 111
tp = ThreadPoolExecutor(4)
ret_l = []
for i in range(20):
ret = tp.submit(fun1,i)
ret_l.append(ret)
for ret in ret_l:
print(ret.result())
回调函数
def back(ret):
print(ret.result())
def son_func(i):
time.sleep(random.random())
return i**i
tp = ThreadPoolExecutor(4)
for i in range(20):
ret = tp.submit(son_func,i)
ret.add_done_callback(back)
协成
特点
协程的本质是一条线程
1.不能利用多核
2.用户级的概念,操作系统不可见
3.协程不存在数据不安全问题
线程中的多个任务,其中的每个任务都可以成为一个协成
能够放多个任务在一个线程中
1)定义:多个任务同时在一个线程中,一个线程中的多个任务之间可以自由切换工作的,遇不遇到IO都可以随时切换
网络:有一个机制,叫IO多路复用,是操作系统提供的,我们能够从程序级别感知到的IO操作是有限的
协成在cpython解释器下,只能感知到网络和时间,跟网络和时间相关的 用协成 ,其他的用线程
用没用过并发编程?
在爬虫 和框架里用过
使用yield实现一个协成
生成器函数 本身就是一个协成
def consumer(): #消费者
while 1: #必须生产者先 生产,消费者在进行消费
good = yield
# time.sleep(1)
print(good)
def produce(): #生产者
c = consumer()
next(c)
for i in range(8):
# time.sleep(1)
c.send('土豆%s'%i) #生产了一个数据
produce()
结果:
'''
土豆0
土豆1
土豆2
土豆3
土豆4
土豆5
土豆6
土豆7
'''
上面这个例子, 并没有规避掉 IO ,只是做到了切换而已,加上time.sleep(1) ,结果是2秒出一个结果,本身还是同步的程序,还是按照顺序执的
asyncio 使用关键字yield 实现了规避io的操作,yield会拖慢执行速度的
greenlet 拓展模块 进行函数中代码的来回切换
使用协成,是为了规避IO
程序:遇到io就切换
greenlet 跟yield 一样,只做切换,并没有规避io, g.Switch() 切换
from greenlet import greenlet
def eat():
print('start eating')
time.sleep(1)
g2.switch()
print('eating finished')
def sleep():
print('start sleeping')
time.sleep(1)
print('sleeping finished')
g1.switch()
g1 = greenlet(eat) #创建了一个协成对象
g2 = greenlet(sleep) #创建了一个协成对象
g1.switch()
**gevent **
gevent 内部使用了greenlet的切换机制,实现了遇到io就自动切换程序,并规避io
import gevent
def eat():
print('start eating')
gevent.sleep(1)
print('eating finished')
def sleep():
print('start sleeping')
gevent.sleep(1)
print('sleeping finished')
gevent.spawn(eat)
gevent.spawn(sleep)
# time.sleep(1) #使用time.sleep(1),并不能进行程序的切换,协成不认识time这个io 操作,而是使用gevent.sleep(1) 进行切换
gevent.sleep(1) #结果是 start eating start sleeping,后边不执行了,
结果:
start eating
start sleeping
# 原因是,主程序遇到io,切换到eat,eat遇到io ,切换到sleep,等再切回主程序时,sleep(1)已经结束了,
# 所有后面的代码就不执行了
使用join方法
# 因为无法计算程序遇到的io是多久,所有使用join(),它可以刚好阻塞到协成结束
import gevent
def eat():
print('start eating')
gevent.sleep(1)
print('eating finished')
def sleep():
print('start sleeping')
gevent.sleep(1)
print('sleeping finished')
g1 = gevent.spawn(eat)
g2 = gevent.spawn(sleep)
g1.join()
g2.join()
结果:
'''start eating
start sleeping
eating finished
sleeping finished
'''
法二:用join
from gevent import monkey
monkey.patch_all()
import time
import gevent
def eat():
print('start eating')
time.sleep(1)
print('eat finished')
def sleep():
print('start sleeping')
time.sleep(1)
print('sleeping finished')
# g = gevent.spawn(eat)
# g1 = gevent.spawn(sleep)
# g.join()
# g1.join()
g_l = []
for i in range(5):
g = gevent.spawn(eat)
g1 = gevent.spawn(sleep)
g_l.append(g)
g_l.append(g1)
gevent.joinall(g_l)
我就是想使用time模块,也是可以的
from greven import monkey
monkey.patch_all() #我要检测接下来所有导入的模块当中io事件,并给他打个包,这样gevent就能认识这个io事件了
import time
import gevent
def eat():
print('start eating')
time.sleep(1)
print('eating finished')
def sleep():
print('start sleeping')
time.sleep(1)
print('sleeping finished')
g1 = gevent.spawn(eat)
g2 = gevent.spawn(sleep)
g1.join()
g2.join() #结果是一样的
使用协成写一个socket Server
就是再一个线程的多个任务中,实现并发操作
服务端
from gevent import monkey
monkey.patch_all()
import socket
import gevent
def talk(conn):
while True:
msg = conn.recv(1024).decode()
ret_msg = msg.upper().encode()
conn.send(ret_msg)
sk = socket.socket()
sk.bind(('127.0.0.1',9000))
sk.listen()
while True:
conn,_ = sk.accept()
g = gevent.spawn(talk,conn)
客户端
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
while True:
sk.send(b'hello')
msg = sk.recv(1024)
print(msg)
4)两个协程模块
gevent - 基于greenlet - 使用更方便 性能相对低
asyncio - 基于yield - 性能更好
总结
多进程应用场景: 各种循环处理,计数等
多线程场景: 文件处理,网络爬虫等
并发阶段
进程 :数据隔离 开启/调度/销毁开销大 能利用多核, 是资源分配的最小单位,适合处理高计算型的
多进程 存在数据不安全(在共同操作文件\数据库\消息中间件(管道))
正常的线程 : 数据共享 开启\销毁\调度开销比进程小很多 能利用多核(除了cpython解释器),是CPU调度的最小单位.,每一个进程中至少有一个线程 适合处理高io型的
线程是进程内的执行单位,适合处理程序中高并发(计算\io)的问题
多线程 存在数据不安全(在共同操作文件\数据库\全局变量)
协成 : 一个线程中的多个任务之间可以自由切换工作的,遇不遇到IO都可以随时切换
1. 不能利用多核
2.用户级的概念,操作系统不可见
3.协程不存在数据不安全问题
Cpython中的线程
GIL全局解释器锁 : 同一个进程内的多个线程,同一时刻,只能有一个线程被CPU调度,不能利用多核
仍然数据不安全 仍然需要加锁
为什么要有GIL锁?
CPython在执行多线程的时候并不是线程安全的,所以为了程序的稳定性,加一把全局解释锁,能够确保任何时候都只有一个Python线程执行。
如果没有GIL,则python解释器的垃圾回收线程和任务线程可以并行,在任务线程I/O时,某些变量值引用计数为0,很可能会被回收, 导致数据不安全。
你知道哪些锁
互斥锁 : 在同一个线程中只能连续acquire一次,多线程时,保证修改共享数据时时有序修改,不会产生数据修改混乱
递归锁 : 在同一个线程中可以多次acquire而不被锁住(但是acquire多少次就要有对应的多少次release)
死锁现象 :
递归锁和互斥锁使用不当都有可能发生死锁现象
死锁现象发生的条件 总是因为在一个线程中有两把锁
问题一:什么时候会是释放Gil锁
答: 1 遇到像 io 操作这种会有时间空闲情况造成cpu闲置的情况会释放Gil
2 会有一个专门ticks进行计数,一旦ticks数值达到100就会释放
问题二:互斥锁和GIL锁的区别
Gil锁:保证同一时刻只有一个线程使用cup
互斥锁:多线程时,保证修改共享数据时有序修改,不会产生数据修改 混乱
问题三.解决Gil琐的方法
1.更换解释器 比如使用jpython(java实现的python解释器)
2:使用多进程完成多任务的处理
3.递归琐
队列
进程队列(IPC机制): 先进先出 \ 数据安全 \multiprocessing.Queue
通信,发送数据
线程队列 : queueu模块 维护顺序\数据安全的
先进先出的队列 Queue
优先级队列 PriorityQueue
栈(后进先出) LifoQueue
解耦 : 把程序中的功能都分开 使得功能之间的依赖关系变得明确
生产者消费者模型
生产数据 和消费数据 两个功能的解耦
能够由用户调节生产者 消费者的数据 使得程序的效率达到最高
维护了数据的顺序
在数据过剩的情况下,能够先将数据缓存下来,而不是堆在网络入口的地方让用户的信息过长时间的等待
池 concurrent.futrues
进程池 ProcessPoolExcutor 无论有多少个任务 进程的个数都是有限的
推荐的进程数 (cpu个数的1-2倍)
线程池 ThreadPoolExcutor 无论有多少个任务 线程的个数都是有限的
协程
在一条线程上 能够在多个任务之间进行切换
gevent模块 实现的协程
开多少个协程 和程序中的IO操作有关系
- 为什么要有GIL锁?
因为CPython自带的垃圾回收机制,在执行多线程的时候并不是线程安全的,所以加了GIL锁
如果没有GIL 锁,则Python解释器的垃圾回收线程和任务线程就可以并行,在任务线程I/O时,某些变量值的引用技术为0,很可能会被回收,导致数据不安全
同步与异步
比如说你要去一个餐厅吃饭,你点完菜以后假设服务员告诉你,你点的菜,要两个小时才能做完,这个时候你可以有两个选择
•一直在餐厅等着饭菜上桌
•你可以回家等着,这个时候你就可以把你的电话留给服务员,告诉服务员等什么时候你的饭菜上桌了,在给你打电话
所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么成功都成功,失败都失败,两个任务的状态可以保持一致。
所谓异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。
阻塞与非阻塞
继续上面的例子
•不管你的在餐厅等着还是回家等着,这个期间你的都不能干别的事,那么该机制就是阻塞的,表现在程序中,也就是该程序一直阻塞在该函数调用处不能继续往下执行。
•你回家以后就可以去做别的事了,一遍做别的事,一般去等待服务员的电话,这样的状态就是非阻塞的,因为你(等待者)没有阻塞在这个消息通知上,而是一边做自己的事情一边等待。
阻塞和非阻塞这两个概念与程序(线程)等待消息通知(无所谓同步或者异步)时的状态有关。也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的
同步/异步与阻塞/非阻塞
同步阻塞形式
效率最低。拿上面的例子来说,就是你专心的在餐馆等着,什么别的事都不做。
异步阻塞形式
在家里等待的过程中,你一直盯着手机,不去做其它的事情,那么很显然,你被阻塞在了这个等待的操作上面;
异步操作是可以被阻塞住的,只不过它不是在处理消息时阻塞,而是在等待消息通知时被阻塞。
同步非阻塞形式
实际上是效率低下的。
想象一下你如果害怕服务员忘记给你打电话通知你,你过一会就要去餐厅看一下你的饭菜好了没有,没好 ,在回家等待,过一会再去看一眼,没好再回家等着,那么效率可想而知是低下的。
异步非阻塞形式
比如说你回家以后就直接看电视了,把手机放在一边,等什么时候电话响了,你在去接电话.这就是异步非阻塞形式,大家想一下这样是不是效率是最高的
那么同步一定是阻塞的吗?异步一定是非阻塞的吗?
同步与异步针对的是函数/任务的调用方式:同步就是当一个进程发起一个函数(任务)调用的时候,一直等到函数(任务)完成,而进程继续处于激活(非阻塞)状态。而异步情况下是当一个进程发起一个函数(任务)调用的时候,不会等函数返回,而是继续往下执行,当函数返回的时候通过状态、通知、事件等方式通知进程任务完成。
阻塞与非阻塞 针对的是进程或线程:阻塞是当请求不能满足的时候就将进程挂起,而非阻塞则不会阻塞当前进程
生产者消费者模型
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过消息队列(缓冲区)来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给消息队列,消费者不找生产者要数据,而是直接从消息队列里取,消息队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个消息队列就是用来给生产者和消费者解耦的。------------->这里又有一个问题,什么叫做解耦?
解耦:假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合)。将来如果消费者的代码发生变化,可能会影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。生产者直接调用消费者的某个方法,还有另一个弊端。由于函数调用是同步的(或者叫阻塞的),在消费者的方法没有返回之前,生产者只好一直等在那边。万一消费者处理数据很慢,生产者就会白白糟蹋大好时光。缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来了。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。等生产者的制造速度慢下来,消费者再慢慢处理掉。