python_多进程 / queue、pipes、manager / 进程锁、进程池 / 协程、Gevent、爬虫、socket / IO多路复用 / IO模式 / select
一、多进程:
(上面说了多线程操作,多线程操作适合于IO密集型的操作,不适合cpu密集型的操作)
(IO密集型的操作:io操作不占用cpu,你从硬盘上读一块数据、你从网络上读一块数据或者你从内存里读一块数据)
(CPU密集型的操作:计算占用cpu,例如:1+1;各种计算…)
因为python的多线程,要不断的实现“上下文的切换”;如果你的程序计算多的(cpu密集型的),不建议使用,可能还没串型的块;如果你的程序IO操作多一点,可以使用多线程…
python的 “进程” 和 “线程” ,都是起的os的原生进程和线程,这些原生进程和线程都是os自己维护的;(若你的CPU是8核的,你起8个进程,这8个进程实打在这8个核上的;而且每一个进程都会有一个线程,所以“多线程”的目的又达到了;but,这些“多线程”的资源或者内存,不是共享的;而且你的进程若是很大,这样你的内存和其他资源会被拖到很累…)
so:多进程,是想利用“多核”;
1.multiprocessing:
import multiprocessing
def run(name):
print("%s is runing....."%name)
if __name__ == "__main__":#如果是,通过"手动"的自己触发,就执行...
man = multiprocessing.Process(target=run,args=("蔡徐坤",))
man.start()
2.进程里面起一个线程:(threading.get_ident();获取当前线程的ID)
import multiprocessing,threading
def thread_id():
print(threading.get_ident())#获取当前线程的id
def run(name):
print("%s is runing....."%name)
t = threading.Thread(target=thread_id(),)#进程里面起线程...
t.start()
if __name__ == "__main__":#如果是,通过"手动"的自己触发,就执行...
for i in range(10):
man = multiprocessing.Process(target=run,args=("蔡徐坤%s代"%i,))
man.start()
3.获取进程id 和 父进程id:(os.getpid 、os.getppid)
#Author:Jony c
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import multiprocessing,threading,os
def info(name):#每一个进程都是父进程启动的
print(name)
print("main process:",os.getppid())#父进程id
print("sub process:",os.getpid())#子进程id
def run(name):
info("第%s个进程"%name)#每个进程的ID 和 父进程ID
print("%s is runing..."%name)
if __name__ == "__main__":
info("have not raise proces")
for i in range(5):
p = multiprocessing.Process(target=run,args=(i,))
p.start()
conclude:
thread_getident() / os.getpid / os.getppid ;
进程 和 线程 使用一致(if _ _ name _ _ == “_ _ main _ _”) ;
4.Queue(进程队列):
(线程queue.Queue / 进程 Queue,需要传给“子进程”)
#Author:Jony c
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import multiprocessing,threading,os
from multiprocessing import Queue#进程Queue
def run(q_test):
q_test.put("蔡徐坤")
if __name__ == "__main__":
q = Queue()#进程Queue
p = multiprocessing.Process(target=run,args=(q,))#因为进程是天生的内存不共享,得把这个传给子进程...
p.start()
print(q.get())在这里插入代码片
(这样的效果,类似于线程的“共享”一个queue;但是进程Queue,不是一个Queue,而是把父进程的Queue拷贝了一份,给了子进程,父进程之所以可以访问的到,是因为pickle的序列化和反序列化…,把pickle当做第三方翻译,在两个内存里“翻译”…只是实现了数据的传递)
5.pipe(管道):(parent_pipe,child_pipe = Pipe())
#Author:Jony c
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import multiprocessing,threading,os
from multiprocessing import Pipe#管道
def run(q_test):
q_test.send("蔡徐坤")#子进程send
print(q_test.recv())#儿子收
if __name__ == "__main__":
parent_pipe,child_pipe = Pipe()#管道实例,会返回2个实例
p = multiprocessing.Process(target=run,args=(child_pipe,))#把管道的一头给子进程,
p.start()
print(parent_pipe.recv())#父进程,recv
parent_pipe.send("打篮球")#父亲send
6.Manager:
#Author:Jony c
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import multiprocessing,threading,os
from multiprocessing import Manager
def run(D,L):
D["第一个"] =1
D["第二个"] =2
D["第三个"] =3
L.append(os.getpid())
if __name__ == "__main__":
m = Manager()#实例化
d = m.dict()#特殊语法,生成字典
l = m.list()#特殊语法,生成列表
p = multiprocessing.Process(target=run,args=(d,l))
p.start()
p.join()#得等子进程结束
print(d,l)
conclude: Queue(pickle序列化)、Pipe(parent_pipe,child_pipe) 、Manager(dict,list)
7.进程锁:
(虽然进程的内存是独立的,但是屏幕是共用的,就像你要打数据,打个"hello world";还没等到一个数据打完,另一个进程就抢进来了;进程们抢着打印数据…所以需要一个进程锁…)
#Author:Jony c
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import multiprocessing,threading,os
from multiprocessing import Manager,Lock
def run(lock):
lock.acquire()
print("进程锁")
lock.release()
if __name__ == "__main__":
lock = Lock()
p = multiprocessing.Process(target=run,args=(lock,))
p.start()
p.join()#得等子进程结束
7.进程池:
(如果父进程1个G,起100进程的话,就是101G;瞬间内存被撑爆了…OS被拉崩了…)
(进程池:同一时间,可以运行多少个“进程”…)
apply:串型
#Author:Jony c
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import multiprocessing,threading,os
from multiprocessing import Pool
def run(name):
print(name)
if __name__ == "__main__":#多进程必须加的一句话...
pool = Pool(5)#实例化
for i in range(10):
p = pool.apply(func=run,args=(i,))#起十个进程
pool.close()#必须是先关闭
pool.join()#进程池中执行完在关闭,如果不注释程序直接关闭...
tips:(如果是手动执行就是“_ _ main _ _”;如果是模块调用,就执行不了…)
if __name__ == "__main__":#多进程必须加的一句话...
apply_async():并行
#Author:Jony c
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import multiprocessing,threading,os
from multiprocessing import Pool
def run(name):
print(name)
if __name__ == "__main__":#多进程必须加的一句话...
pool = Pool(5)#实例化
for i in range(10):
#p = pool.apply(func=run,args=(i,))#起十个进程
p = pool.apply_async(func=run,args=(i,))#起十个进程,并行
pool.close()#必须是先关闭
pool.join()#进程池中执行完在关闭,如果不注释程序直接关闭...
callback():(回调函数,每执行完run之后,自行调用…)
#Author:Jony c
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import multiprocessing,threading,os
from multiprocessing import Pool
def run(name):
print(name)
def Bar(arg):#执行完每一个run之后,在执行Bar
print("写日志...from",os.getpid())#子进程id
if __name__ == "__main__":#多进程必须加的一句话...
print(os.getpid())#父进程id11
pool = Pool(5)#实例化
for i in range(10):
#p = pool.apply(func=run,args=(i,))#起十个进程
p = pool.apply_async(func=run,args=(i,),callback = Bar )#起十个进程
pool.close()#必须是先关闭
pool.join()#进程池中执行完在关闭,如果不注释程序直接关闭...
conclude:
1.pool.apply / pool.apply_async;
2.p.close…后面必须跟p.join();
3.callback = bar(回调函数)
二、协程:
1.协程:又称为微线程;是在单线程下工作的;不需要原子操作的“锁定”以及同步的开销;
2.一个cpu支持上万个“协程”都不是问题;
协程:之所以快,是遇到所有的IO操作都“切换”;
(带个问题:什么时候切换回来呢???)
Greenlet:
#Author:Jony c
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#greenlet:封装好了的协程(手动切换);g
from greenlet import greenlet
import time
def test1():
print("12")
g2.switch()#站点,然后切换test >>> 56
print("34")
g2.switch()#站点,然后切换到78
def test2():
print("56")
g1.switch()#站点,然后切换到34
print("78")
g1 = greenlet(test1)#启动一个协程
g2 = greenlet(test2)#启动第二个协程
g1.switch()#手动切换
Gevent:
(“自动切换”);主要用到的模式是Greenlet;
#Author:Jony c
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import gevent
def test1():#1.先执行test1
print("12")#2.到这一步
gevent.sleep(2)#模仿IO操作,3.自动跳转到test2....#7.跳会上次“站点”(标记的地方);这里会睡2秒
print('34')#8.输出
def test2():#4.执行test2
print("56")#5.输出
gevent.sleep(1)#6.跳转到test1.....#9.执行test2回到上一次“站点”;然后睡1s
print("78")#10.最后一步,输出
gevent.joinall([
gevent.spawn(test1),#生成,
gevent.spawn(test2),
])
#Author:Jony c
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import gevent
def test1():#1.先执行test1
print("12")#2.到这一步
gevent.sleep(5)#模仿IO操作,3.自动跳转到test2....#7.跳会上次“站点”(标记的地方);这里会睡2秒
print('34')#8.输出
def test2():#4.执行test2
print("56")#5.输出
gevent.sleep(1)#6.跳转到test1.....#9.执行test2回到上一次“站点”;然后睡1s
print("78")#10.最后一步,输出
def test3():
print("910")
gevent.sleep(0)
print("1112")
gevent.joinall([
gevent.spawn(test1),#生成,spawn
gevent.spawn(test2),
gevent.spawn(test3),
])
conclude:
1.协程是单线程下工作;之所以快,是因为"IO"操作,都跳开了;
2.greenlet…g1.switch(手动挡);
3.genent(自动切换);gevent.joinall([gevent.spawn()…gevent.spawn()])…gevent.sleep()…;
4.带个问题:“什么时候”切回来???
协程之爬虫:
(实现一个简单的网页爬取…)
#Author:Jony c
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from urllib import request
#爬虫,爬一个最简单的网页
import gevent
from gevent import monkey
#打个补丁:
#用协程大范围的爬
monkey.patch_all()#把当前程序的的,所有的IO操作给我单独的做上标记...
def f(url):
print("GET:%s"%url)
resp =request.urlopen(url)#实例化
date = resp.read()
f = open("url,html","wb")
f.write(date)
f.close()
print("%d bytes received from %s."%(len(date),url))
gevent.joinall([
gevent.spawn(f,""),
gevent.spawn(f,""),
gevent.spawn(f,""),
])
#urllib 和 gevent ,之间是
conclude:
1.urllib / resp = urllib.request.urlopen(网页地址) / date = resp.read();
2.urllib 和 gevent 之间没有之间关系 ;而且gevent 检测不到 urllib 的 IO操作;
3.所以from gevent import monkey;monkey.patch_all();
事件驱动与异步IO:
(事件驱动模型:每收到一个请求,放入一个事件列表,让主进程通过"非阻塞IO"方式来处理请求)
例如:UI编程中,如何获取鼠标点击???
假如采取以下操作,创建一个"线程",循环检测这个“鼠标点击”事件;
1.但是我这鼠标两年点一次,导致cpu大大浪费;
2.扫描到了“鼠标点击”,若处理事件要处理5年,一直“堵塞”,后面你干的任何事都检测不到了;
3.要是循环检测的设备很多,带来的响应时间的问题;
大部分UI编程,都是事件驱动模型
conclude:
1.收到一个请求 > 2.放在“事件队列”里 > 3.主进程"非阻塞IO"操作执行;4.所以你的事件响应的速度,完全取决于你的“主进程”处理事件的速度;
番外篇:
1.操作系统是虚拟的存储器,对于32位操作系统,它的寻址空间是4G(2的32次方);
2.操作系统将虚拟空间分为2部分,一部分是“内核”空间,一部分是“用户空间”;
3.文件描述符,一个非负整数,实际上,是一个索引值;
当进程启动一个文件(打开一个/创建一个新的文件)的时候,内核会给进程一个“索引值(文件描述符);
进程拿着这个索引值给OS,内核会根据这个索引值,在所维护的“文件列表”里,把这个文件对象(文件句柄)取出来;
4.缓存IO:数据会被copy在内核的缓冲区中,再被内核copy到应用程序的“内存空间里”;
IO模式:阻塞IO、非阻塞IO、IO多路复用、异步IO
阻塞IO:
两个阶段,都是阻塞的;非阻塞IO:
用户read,数据没准备好,return一个error;(不会把你卡在这)…(一百年以后)数据准备好了,用户read,内核copy给你应用程序的“内存”…(没准备好的时候,return给你的error,你可以去干别的事…这个层面上快了一点…)
IO多路复用:(select、poll、epoll)(也称为事件驱动IO)
(原理是:select、poll、epoll所负责的socket,不断询问,当"数据准备"好了,就通知“进程”…)
(当用户调用select,那么整个进程会被block,kernel回“监视”select所负责的socket)
异步IO:
用户read之后,“立马”去干别的事了,等到kernel数准备好了,copy到内存里了,发一个signal给用户,你read一下就行了…对比这些IO模式:
select 所检测的文件描述符受到限制,Linux上一般是1024;有一个活跃了,告诉你;但是,不会告诉你,到底是哪一个活跃了;
poll,做了升级,检测的文件描述符不受限制;
epoll,是现在最流行的,实现了,告诉你6w个链接里面,具体是哪两个活跃了;水平触发,数据准备好了,放在内核,通知你,下次的时候,接着通知你;边缘触发,快递到了,你没有取的话,下次就不会在告诉你了
python select:
#Author:Jony c
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket,select,sys,queue
server = socket.socket()
server.bind(("localhost",9999))
server.listen(5)
server.setblocking(False)#不阻塞
msg_dic = {}
inputs = [server,]#有多少个链接...放进去
#放了一个要检测的conn,inputs = [server,conn]...
#实际上是,你不知道是serevr 还是 conn 在活跃...
#server活跃,说明有新链接进来
outputs = []
#你往output里面放啥,下一次就出啥...
while True:
readable,writeable,excptional = select.select(inputs,outputs,inputs)
#input要检测的,outputs是检测那些断了,第二个inputs是检测那些有问题的
print(readable,writeable,excptional)
for r in readable:
if r is server:#来了一个新链接
conn,addr = server.accept()#新的链接建立了
print(conn,addr)
inputs.append(conn)#还得让检测列表里,下一次循环的时候,如果客服端发数据了,下一次循环接收
msg_dic[conn] = queue.Queue()#初试化一个队列,后面存要返回的数据...
else:#conn发数据过来了
data = r.recv(1024)
print("数据",data)
msg_dic[r].put(data)#队列里放数据
outputs.append(r)#放入要返回的连接...
for w in writeable:#要返回的客服端你连接列表
data_to_client = msg_dic[w].get()
w.send(data_to_client)#返回原来的数据
outputs.remove(w)#确保下次循环的时候,不返回就得连接
for j in excptional:
if j in outputs:
outputs.remove(j)
inputs.remove(j)
del msg_dic[j]
多并发:
import selectors
import socket
sel = selectors.DefaultSelector()
def accept(sock, mask):
conn, addr = sock.accept() # Should be ready
print('accepted', conn, 'from', addr,mask)
conn.setblocking(False)
sel.register(conn, selectors.EVENT_READ, read) #新连接注册read回调函数
def read(conn, mask):
data = conn.recv(1024) # Should be ready
if data:
print('echoing', repr(data), 'to', conn)
conn.send(data) # Hope it won't block
else:
print('closing', conn)
sel.unregister(conn)
conn.close()
sock = socket.socket()
sock.bind(('localhost', 9999))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept)
while True:
events = sel.select() #默认阻塞,有活动连接就返回活动的连接列表
for key, mask in events:
callback = key.data #accept
callback(key.fileobj, mask) #key.fileobj= 文件句柄
起11000个client
__author__ = "Alex Li"
import socket
import sys
messages = [ b'This is the message. ',
b'It will be sent ',
b'in parts.',
]
server_address = ('192.168.16.130', 9999)
# Create a TCP/IP socket
socks = [ socket.socket(socket.AF_INET, socket.SOCK_STREAM) for i in range(11000)]
print(socks)
# Connect the socket to the port where the server is listening
print('connecting to %s port %s' % server_address)
for s in socks:
s.connect(server_address)
for message in messages:
# Send messages on both sockets
for s in socks:
print('%s: sending "%s"' % (s.getsockname(), message) )
s.send(message)
# Read responses on both sockets
for s in socks:
data = s.recv(1024)
print( '%s: received "%s"' % (s.getsockname(), data) )
if not data:
print( 'closing socket', s.getsockname() )