python_day10_多进程 / queue、pipes、manager / 进程锁、进程池 / 协程、Gevent、爬虫、socket / IO多路复用 / IO模式 / select

python_多进程 / queue、pipes、manager / 进程锁、进程池 / 协程、Gevent、爬虫、socket / IO多路复用 / IO模式 / select

在这里得感谢,老师Alex金角大王(路飞学城IT)
Python(给兄弟们挂个🔗)

python边写边更…

一、多进程:

(上面说了多线程操作,多线程操作适合于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,"https://blog.csdn.net/weixin_44941385?spm=1011.2124.3001.5343"),
    gevent.spawn(f,"https://blog.csdn.net/Temmie1024?spm=1001.2014.3001.5509"),
    gevent.spawn(f,"https://blog.csdn.net/Temmie1024/article/details/115286184?spm=1001.2014.3001.5501"),

 ])
#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() )


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值