Python篇-多进程与协程的理解与使用
TZ : 朴实的劳作也能硕果累累
一 : 科普一分钟
- 尽管
进程
间是独立存在的,不能相互访问彼此的数据,但是在python
中却存在进程
间的通信方法,来帮助我们可以利用多核CPU
也能共享
数据. - 对于
多线程
其实也是存在一些缺点的,不是任何场景我们都用多线程
来完成并发
处理任务,因为CPU操作线程
,所以线程多了,对于计算机的资源消耗是十分严重的,多线程
适合IO
操作密集的任务,那么怎么办呢,协程
的出现帮我们解决了这个问题 ,协程
是比线程更小的一个单位,但是它的作用却不容忽视.
二 : 多进程
1. 多进程简单了解 :
进程之间是独立的,是操作系统自己来维护和管理的,python
通过C接口起了一个进程,多进程可以充分的利用多核CPU
2. 多进程的创建 :
import multiprocessing
import time
# 子进程执行方法
def run(person):
time.sleep(2)
print(person)
#循环创建 10个子进程
for i in range(10):
#创建子进程实例
p = multiprocessing.Process(target=run,args=('雪芙 %s' %i ,))
#运行子进程
p.start()
3. 多进程间的通信 :
进程间独立,如果想相互访问,就必须有一个中间翻译,下面提供了几种进程间通信的方法
- 进程Queue
from multiprocessing import Process,Queue
# import queue
#子进程,内存独立,相当于数据的传递
def f(subQueue):
subQueue.put("雪芙")
if __name__ == '__main__':
#进程Queue
q = Queue()
#创建进程
p = Process(target=f,args=(q,))
#执行进程
p.start()
print(q.get())
解析 :
Queue
通信,相当于父进程赋值了一个Queue
给子进程,子进程在这个Queue
放好数据后,序列化一个中间翻译,然后在反序列化返回给父进程,
因为进程之间内存独立,不能传递对象
传递的其实就是序列化
的数据
- Pipe
多进程还有一种数据传递方式叫管道
原理和Queue
相同
# Author:TianTianBaby
from multiprocessing import Process,Pipe
#子进程执行方法
def f(Subconn):
Subconn.send("吃了吗")
print("来自父亲的问候 : ",Subconn.recv())
Subconn.close()
if __name__ == '__main__':
#创建管道两端
parent_conn,child_conn = Pipe()
#创建子进程
p = Process(target=f,args=(child_conn,))
p.start()
print("来自儿子的问候 :",parent_conn.recv())
parent_conn.send("你好啊")
p.join()
4. 进程锁
虽然内存独立,但是即使是打印也会造成打印数据错误,为了防止进程间抢屏幕打印输出,加了进程锁
from multiprocessing import Process,Lock
#子进程执行方法
def f(lock,num):
lock.acquire()
print("tztztz",num)
lock.release()
if __name__ == '__main__':
lock = Lock()
#循环创建100个子进程
for num in range(100):
Process(target=f,args=(lock,num)).start()
5. 进程池
创建一个子进程
相当于copy一份父进程
内存数据,为了防止频繁创建,导致内存不足,所以有了进程池
作为限制.
# Author:TianTianBaby
from multiprocessing import Process,Pool
import time,os
def son(i):
time.sleep(2)
return i+100
def Back(arg):
print('你好完成')
#允许进程池同时放入5个进程
pool = Pool(processes=3)
for i in range(10):
#并行 callback 回调(主进程调用的)
pool.apply_async(func=son,args=(i,),callback=Back)
#并行
# pool.apply_async(func=son, args=(i,))
#串行
# pool.apply(func=son,args=(i,))
print('end')
#先关闭进程池再join
pool.close()
#进程池中进程执行完毕再关闭,如果注释,那么程序直接关闭
pool.join()
三 : 协程
1. 协程的简单了解 :
协程
又称微线程
,coroutne
,协程
是一种用户态的轻量级线程
通俗点讲就是周末我在家里休息,假如我先洗漱,再煮饭,再下载电影看会很慢,用了协程
的效果就好比,我在下载电影的时候去点火煮饭,此时我马上洗漱,等我洗漱好了,饭也好了,吃完饭了,电影下好了,我可以看了.
2. 协程的创建和使用 :
gevent
是一个三方库,可以轻松通过gevent
实现并发同步或者异步编程.
# Author:TianTianBaby
import gevent
#函数1
def first():
print('运行 1')
gevent.sleep(2)
print('回到1')#精确的文本内容切换到 ..
#函数2
def second():
print('运行2')
gevent.sleep(1)
print('回到2')
#函数3
def third():
print("运行3")
gevent.sleep(0)
print("回到3")
#创建并添加写成任务
gevent.joinall([gevent.spawn(first), #生成
gevent.spawn(second),
gevent.spawn(third)])
解析:尝试运行发现,运行时间为Sleep
最长的时间,也就是说协程
能绕过IO,进行执行,极大的提高了效率.
IO(从硬盘上读一块数据,从网络读数据,从内存里读一块数据) 操作不占用CPU,计算占用CPU
3. 协程简单爬网页 :
# Author:TianTianBaby
from urllib import request
import ssl
import gevent
from gevent import monkey
#把当前程序的所有的IO操作 单独做上标记
monkey.patch_all()
ssl._create_default_https_context = ssl._create_unverified_context
def f(url):
print('GET:%s' %url)
resp = request.urlopen(url)
print(resp)
data = resp.read()
f = open("pa.html","wb")
f.write(data)
f.close()
print("%d bytes received from %s" %(len(data),url))
gevent.joinall([gevent.spawn(f,'http://www.jianshu.com/'),
gevent.spawn(f,'http://www.iconfont.cn/'),
gevent.spawn(f,'http://www.cocoachina.com/'),
])
4. 协程实现socketServer:
通过协程,我们可以写出一个socketServer
,真正socketServer
的底层是用多线程来实现,我们用写成写出的效率很高,而且非常节省内存
import sys
import socket
import time
import gevent
from gevent import socket,monkey
monkey.patch_all()
def server(port):
s = socket.socket()
s.bind(('localhost',port))
s.listen(500)
while True:
cli,addr = s.accept()
#交给协程处理
gevent.spawn(handle_request,cli)
def handle_request(conn):
try:
while True:
data = conn.recv(1024)
print("recv:",data)
conn.send(data)
if not data:
conn.shutdown(socket.SHUT_WR)
except Exception as ex:
print(ex)
finally:
conn.close()
if __name__ == '__main__':
server(9001)
四 : 总结
-
协程的优点
1 : 线程在单线程下切换,减少资源消耗
2 : 无需原子操作控制流,简化编程模型
3 : 高并发,高扩展,低成本. -
无论是
多进程
,多线程
还是协程
在不同的场景用不同的模型才能高效的完成任务.