并发编程 协程

浏览器/爬虫都是socket 客户端

如何提高并发?  多线程  :IO  多进程:计算

一.IO多路复用

作用:检测多个socket 是否发生变化(是否连接成功/是否获取数据) (可读/可写)

regests 模块

ret=requests.get('https://www.baidu.com/s?wd=alex')
#DNS解析,根据域名解析出IP 后面的为搜索关键字

 

socket

 1 #socket 发送请求
 2 import  socket
 3 cilent=socket.socket()
 4 cilent.connect(("www.baidui.com",80))
 5 cilent.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n')
 6 data=cilent.recv(8096)
 7 lst=[]
 8 while 1:
 9     if not data:
10         break
11     lst.append(data)
12 list=b"".join(lst)
13 list.decode("utf8")

b"".join(lst)  使列表结成字符串

2.多线程解决并发 

 1 import  socket
 2 import requests
 3 def func(key):
 4     cilent=socket.socket()
 5     cilent.connect(("www.baidui.com",80))
 6     cilent.sendall(b'GET /s?wd=%s HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n' % key)
 7     data=cilent.recv(8096)
 8     lst=[]
 9     while 1:
10         if not data:
11             break
12         lst.append(data)
13     list=b"".join(lst)
14     list.decode("utf8")
15 import  threading
16 l=["zq","wa","wd"]
17 
18 for item  in  l:
19     t1=threading.Thread(target=func,args=(item))
20     t1.start()

缺点: 这个需要等待,因为有阻塞,会在客户端connect,recv 时刻需要等待客户端 ,怎样才能不等待呢???

IO多路复用+socket 实现并发请求 (一个线程100个请求)

这里用到   client.setblocking(False) 

1 client = socket.socket()
2 client.setblocking(False) # 将原来阻塞的位置变成非阻塞(报错)
3 # 百度创建连接: 阻塞
4 
5 try:#需要 避免
6     client.connect(('www.baidu.com',80)) # 执行了但报错了
7 except BlockingIOError as e:
8     pass

 

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

IO多路复用+socket 实现并发请求

 1 import socket
 2 import select
 3 
 4 client1 = socket.socket()
 5 client1.setblocking(False) # 百度创建连接: 非阻塞
 6 
 7 try:
 8     client1.connect(('www.baidu.com',80))
 9 except BlockingIOError as e:
10     pass
11 
12 client2 = socket.socket()
13 client2.setblocking(False) # 百度创建连接: 非阻塞
14 try:
15     client2.connect(('www.sogou.com',80))
16 except BlockingIOError as e:
17     pass
18 
19 client3 = socket.socket()
20 client3.setblocking(False) # 百度创建连接: 非阻塞
21 try:
22     client3.connect(('www.oldboyedu.com',80))
23 except BlockingIOError as e:
24     pass
25 
26 socket_list = [client1,client2,client3]#创建连接列表,有三个
27 conn_list = [client1,client2,client3]#再创建
28 
29 while True:
30     rlist,wlist,elist = select.select(socket_list,conn_list,[],0.005)#rlist
31     # wlist中表示是否已经和socket建立连接对象,有返回值,可写 返回cilent
32     #rlist中表示是否已经和socket有返回数据,有返回值,可读 返回cilent
33     #[] 中 将错误信息返回空列表
34     #0.005 最大0.005秒检测错误
35     for sk in wlist:
36         if sk == client1:
37             sk.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n')
38         elif sk==client2:
39             sk.sendall(b'GET /web?query=fdf HTTP/1.0\r\nhost:www.sogou.com\r\n\r\n')
40         else:
41             sk.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.oldboyedu.com\r\n\r\n')
42         conn_list.remove(sk)
43     for sk in rlist:
44         chunk_list = []
45         while True:
46             try:
47                 chunk = sk.recv(8096)
48                 if not chunk:#
49                     break
50                 chunk_list.append(chunk)
51             except BlockingIOError as e:
52                 break
53         body = b''.join(chunk_list)
54         # print(body.decode('utf-8'))
55         print('------------>',body)
56         sk.close()
57         socket_list.remove(sk)
58     if not socket_list:#这里指当列表中的cilent被取到时,把他们移出作为判断
59         #直到把最后都取出
60         break

用面向对象做IO多路复用

import socket
import select

class Req(object):
    def __init__(self,sk,func):
        self.sock = sk
        self.func = func

    def fileno(self):
        return self.sock.fileno()


class Nb(object):

    def __init__(self):
        self.conn_list = []
        self.socket_list = []

    def add(self,url,func):
        client = socket.socket()
        client.setblocking(False)  # 非阻塞
        try:
            client.connect((url, 80))
        except BlockingIOError as e:
            pass
        obj = Req(client,func)
        self.conn_list.append(obj)
        self.socket_list.append(obj)

    def run(self):

        while True:
            rlist,wlist,elist = select.select(self.socket_list,self.conn_list,[],0.005)
            # wlist中表示已经连接成功的req对象
            for sk in wlist:
                # 发生变换的req对象
                sk.sock.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n')
                self.conn_list.remove(sk)
            for sk in rlist:
                chunk_list = []
                while True:
                    try:
                        chunk = sk.sock.recv(8096)
                        if not chunk:
                            break
                        chunk_list.append(chunk)
                    except BlockingIOError as e:
                        break
                body = b''.join(chunk_list)
                # print(body.decode('utf-8'))
                sk.func(body)
                sk.sock.close()
                self.socket_list.remove(sk)
            if not self.socket_list:
                break

def baidu_repsonse(body):
    print('百度下载结果:',body)

def sogou_repsonse(body):
    print('搜狗下载结果:', body)

def oldboyedu_repsonse(body):
    print('老男孩下载结果:', body)

t1 = Nb()
t1.add('www.baidu.com',baidu_repsonse)
t1.add('www.sogou.com',sogou_repsonse)
t1.add('www.oldboyedu.com',oldboyedu_repsonse)
t1.run()
View Code

3.协程

 概念:

进程,线程 操作系统中存在

协程,是由程序员创造出来的,不是一个单独存在的东西

协程:是为线程,对一个线程进行分片,使得线程在代码块之间来回切换执行,而不是在原来逐行执行

 1 import  greenlet
 2 def func1():
 3     print(11)
 4     f2.switch()
 5     print(22)
 6     f2.switch()
 7 def func2():
 8     print(33)
 9     f1.switch()
10     print(44)
11 f1=greenlet.greenlet(func1)
12 f2=greenlet.greenlet(func2)
13 f1.switch()
协程示例

但是单独的协程没有什么作用

协程只是单纯的分片,但是用在线程上就可以节省宝贵的时间,

但是无法判断是否是IO进行分片,这时候就需要  pip3 install gevent 

 1 from gevent import monkey
 2 monkey.patch_all()#以后代码中遇到IO都会自动执行greenlet的switch进行切换
 3 import requests
 4 import gevent
 5 def f1(url):
 6     ret=requests.get(url)
 7     print(url,ret.content)
 8 def f2(url):
 9     ret=requests.get(url)
10     print(url,ret.content)
11 def f3(url):
12     ret=requests.get(url)
13     print(url,ret.content)
14 gevent.joinall([
15     gevent.spawn(f1,'https://www.python.org/'),#协程1
16     gevent.spawn(f2, 'https://www.yahoo.com/'),
17     gevent.spawn(f3, 'https://www.github.com/'),
18 ])
monkey.patch_all()

 

总结:
1. 什么是协程?
协程也可以称为“微线程”,就是开发者控制线程执行流程,控制先执行某段代码然后再切换到另外函执行代码...来回切换。

2. 协程可以提高并发吗?
协程自己本身无法实现并发(甚至性能会降低)。
协程+IO切换性能提高。

3. 进程、线程、协程的区别?

4. 单线程提供并发:
- 协程+IO切换:gevent
- 基于事件循环的异步非阻塞框架:Twisted
手动实现协程:yield关键字生成器 
 1             def f1():
 2                 print(11)
 3                 yield
 4                 print(22)
 5                 yield
 6                 print(33)
 7 
 8             def f2():
 9                 print(55)
10                 yield
11                 print(66)
12                 yield
13                 print(77)
14 
15             v1 = f1()
16             v2 = f2()
17 
18             next(v1) # v1.send(None)
19             next(v2) # v1.send(None)
20             next(v1) # v1.send(None)
21             next(v2) # v1.send(None)
22             next(v1) # v1.send(None)
23             next(v2) # v1.send(None)
View Code

总体总结

 1 '''
 2 1.本质上socket
 3 2.如何提高并发 ?
 4 多进程:计算 多线程:io
 5 一.
 6 1.通过客户端协议也可以浏览网页
 7 2.协议 遵循
 8 3.间接可以用requests 模块  或者用 socket 模块来引用
 9 4.cilent.setblocking(False)将原来阻塞的位置变成非阻塞(报错)
10 报错捕获一下  try  except BlockingIOError as e:
11 作用:单线程遇到io 不等待
12 需要检测是否连接
13 ***  io多路复用作用: 检测socket是否已将发生变化
14 (是否已经连接成功/是否已经获取数据) (可度/可写)
15 ***
16 5. select.select()[] >>报错写入  0.005 >内部最多0.005检测错误
17 6.已经实现的模块 Twisted  基于事件循环实现的异步非阻塞框架
18 还用封装实现 (没听好)
19 7.总结;
20 1.socket默认阻塞,阻塞体现在
21  connect recv
22 2. 如何让socket变成非阻塞
23  setblocking(False)
24 3. io多路复用
25  检测多个socket是否发生变化
26 4. 系统检测socket是否发生变化,三种模式
27 select "最多1024个socket;循环检测
28  poil: (主动查询)不限制监听sockrt个数;循环检测(慢)水平触发
29 epoil : (举手,自己完成主动通知) 回调方式 边缘触发
30 Python模块:
31 select.select 
32 select.epoll 
33 
34 5.提高并发方案:
35 多进程
36 多线程
37 异步非阻塞模块(Twisted)  scrapy框架(单线程完成并发)
38 5.提高并发方案:
39 控制好线程个数
40 生产者消费者模型
41 6.什么是 异步非阻塞?
42 -非阻塞 ,不等待
43 比如创建socket对某个地址进行connect、获取接收数据recv时默认都会等待(连接成功或接收到数据),才执行后续操作。
44 如果设置setblocking(False),以上两个过程就不再等待,但是会报BlockingIOError的错误,只要捕获即可。
45 异步:
46 返回一个函数继续调用,
47 通知,执行完成后自动执行回调函数局或自动执行某种操作
48 7.什么是同步阻塞?
49 阻塞 ,等
50 同步,按照顺序逐步执行
51 
52 不如创建socket对某个地址
53 
54 
55 三.
56 协程,是由程序员创造出来的不是真实存在的东西
57 由用户控制
58 协程,就是微线程,对一个线程进程分片,使得线程在代码块之间来回切换执行,
59 而不是在原来逐行执行
60 单纯的协程无用
61 协程的存在意义:类似于单线程实现并发:遇到IO + 协程的切换可以提高性能
62 3.1
63 协程+IO切换>>站起来了  pip3 install gevent
64 from gevent import monkey
65 monkey.path_all() #默认将代码遇到的IO
66 总结:
67 1.什么是协程?
68 2.协程可以提高并发么?
69 3.进程,线程,协程的区别面试题?
70 4.单线程提供并发
71 -协程+IO切换 :gevent
72 -基于事件循环的异步非阻塞框架: Twisted
73 
74 也可以通过yield
75 
76 以后gevent  Twisted 写代码
77 三种模式:
78 select
79 poil
80 epoil
View Code

 

posted @ 2018-09-13 19:25  逆欢  阅读(132)  评论(0编辑  收藏  举报