• python -- 异步编程


    我们在生产中,常用的处理任务模型有三种:
      单线程
      多线程
      异步(单线程内,串行,特点是遇到阻塞(或IO之类的)就切换到其他任务)

    其中一般如果都符合要求,那么异步是最好的选择。
      单线程:遇到阻塞整个程序都等待
      多线程:以空间换取时间,且有时候伴随着数据安全问题(通常加锁来处理)
      异步:在单个线程内,且是串行执行,但是一旦遇到阻塞(IO之类的),就会切换到线程内的其他任务(把IO操作交给操作系统处理)

    当我们面对如下的环境时,事件驱动模型(异步模型)通常是一个好的选择(and):
      1、程序中有许多任务
      2、任务之间高度独立(因此它们不需要互相通信,或者等待彼此)
      3、在等待事件到来时,某些任务会阻塞。

    常用的异步IO模型:select、poll、Epoll    (windows下只支持select)
    nginx就是Epoll模型实现的。单线程,多进程(为了利用多核,单线程只能跑在单个cup核心上)

    前面我们说了多线程与多进程,总结其特点就是:

      单线程串行执行,遇到IO就阻塞,效率低。

      多线程并发执行,遇到IO就切换(用空间换取执行时间),效率上去了,但是耗费资源,操作复杂。

    针对以上问题,出现了一种新的替代品,协程。

    协程  

      协程又名微线程,纤程。协程是一种用户态的轻量级线程(协程由用户切换,线程由cpu时间片控制切换)协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。跟yield过程差不多


    协程的定义标准:
      1、必须在一个单线程里面实现并发
      2、修改共享数据不需要加锁(因为协程是串行的)
      3、用户程序里自己保存多个控制流的上下文栈
      4、一个协程遇到IO操作(阻塞也一样)就自动切换到其他协程

    协程的好处:
      1、无需线程上下文切换的开销
      2、无需原子操作锁定及同步的开销
           "原子操作(atomic operation)是不需要synchronized",所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序是不可以被打乱,或者切割掉只执行部分。视作整体是原子性的核心。

      3、方便切换控制流,简化编程模型
      4、高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。

    缺点:
      1、无法利用多核资源:协程的本质是个单线程,它不能同时将单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
      2、进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

    协程例子

      1、用yield实现协程操作的例子:

     1 import time
     2 
     3 def consumer(name):
     4     print("33[32;1m ---> starting eating baozi... 33[0m")
     5     while True:
     6         new_baozi = yield
     7         print("[%s] is eating baozi %s " %(name,new_baozi))
     8         # time.sleep(1)  #在yield里面并没有实现阻塞切换
     9 
    10 def producer():
    11     c = con1.__next__()
    12     c = con2.__next__()
    13     n = 0
    14     while n < 5:
    15         n += 1
    16         print("33[32;1m [producer]33[0m is making baozi %s " % n)
    17         con1.send(n)
    18         con2.send(n)     #用send可以想yield发送数据
    19 
    20 if __name__ == '__main__':
    21     con1 = consumer('c1')
    22     con2 = consumer('c2')
    23     p = producer()

      执行结果

     ---> starting eating baozi... 
     ---> starting eating baozi... 
     [producer] is making baozi 1 
    [c1] is eating baozi 1 
    [c2] is eating baozi 1 
     [producer] is making baozi 2 
    [c1] is eating baozi 2 
    [c2] is eating baozi 2 
     [producer] is making baozi 3 
    [c1] is eating baozi 3 
    [c2] is eating baozi 3 
     [producer] is making baozi 4 
    [c1] is eating baozi 4 
    [c2] is eating baozi 4 
     [producer] is making baozi 5 
    [c1] is eating baozi 5 
    [c2] is eating baozi 5 

    2、使用gevent实现协程

     1 #!/usr/bin/env python
     2 # -*- coding: utf-8 -*-
     3 
     4 import gevent
     5 import time
     6 
     7 def func1():
     8     print("33[32;1m func1函数的第1部分... 33[0m")
     9     gevent.sleep(2)
    10     # time.sleep(2)   #用time.sleep(2)是不行的
    11     print("33[32;1m func1函数的第2部分... 33[0m")
    12 
    13 def func2():
    14     print("33[31;1m func2函数的第1部分... 33[0m")
    15     gevent.sleep(1)
    16     # time.sleep(1)
    17     print("33[31;1m func2函数的第2部分... 33[0m")
    18 
    19 def func3():
    20     print("33[34;1m func3函数的第1部分... 33[0m")
    21     gevent.sleep(3)
    22     # time.sleep(3)
    23     print("33[34;1m func3函数的第2部分... 33[0m")
    24 
    25 
    26 if __name__ == '__main__':
    27     gevent.joinall([
    28         gevent.spawn(func1),
    29         gevent.spawn(func2),
    30         gevent.spawn(func3),
    31     ])

      执行结果

     func1函数的第1部分... 
     func2函数的第1部分... 
     func3函数的第1部分... 
     func2函数的第2部分... 
     func1函数的第2部分... 
     func3函数的第2部分... 

    3、协程结合urllib模块爬网站

     1 #!/usr/bin/env python
     2 # -*- coding: utf-8 -*-
     3 
     4 from gevent import monkey
     5 monkey.patch_all()  #遇到阻塞就切换全靠它(作用:把要用到的接口全部变为非阻塞模式)
     6 
     7 import gevent
     8 from urllib.request import urlopen
     9 
    10 def f(url):
    11     print("GET: %s " %url)
    12     resp = urlopen(url)
    13     data = resp.read()
    14     print("%s bytes received from %s " %(len(data),url))
    15 
    16 if __name__ == '__main__':
    17     gevent.joinall([
    18         gevent.spawn(f, 'http://www.cdu.edu.cn/'),
    19         gevent.spawn(f,'https://www.python.org/'),
    20         gevent.spawn(f,'https://www.jd.com/'),
    21         gevent.spawn(f,'https://www.vip.com/')
    22     ])

      执行结果

    GET: http://www.cdu.edu.cn/ 
    GET: https://www.python.org/ 
    GET: https://www.jd.com/ 
    GET: https://www.vip.com/ 
    137584 bytes received from https://www.jd.com/ 
    69500 bytes received from https://www.vip.com/ 
    47984 bytes received from http://www.cdu.edu.cn/ 
    47695 bytes received from https://www.python.org/ 
    
    Process finished with exit code 0

    4、利用协程实现高并发服务器

     1 #!/usr/bin/env python
     2 # -*- coding: utf-8 -*-
     3 
     4 import gevent
     5 import socket
     6 from gevent import monkey; monkey.patch_all()
     7 
     8 def server(port):
     9     s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    10     s.bind(('0.0.0.0',port))
    11     s.listen(5000)
    12 
    13     while True:
    14         # print("33[32;1m server is waiting... 33[0m")
    15         print(" server is waiting... ")
    16         conn, addr = s.accept()
    17         gevent.spawn(handle_request,conn)
    18 
    19 def handle_request(conn):
    20     try:
    21         while True:
    22             data = conn.recv(1024)
    23             print("recvied:",data.decode('utf8'))
    24             conn.send(data)
    25             if not data:
    26                 conn.shutdown(socket.SHUT_WR)
    27 
    28     except Exception as ex:
    29         print(ex)
    30 
    31     finally:
    32         conn.close()
    33 
    34 
    35 if __name__ == '__main__':
    36     server(9999)
    server
     1 #!/usr/bin/env python
     2 # -*- coding: utf-8 -*-
     3 
     4 import socket
     5 import threading
     6 
     7 def run(n):
     8     '''这里是启动多线程,然后每个线程死循环发包给服务器,测试协程服务器的高并发和稳定性'''
     9     while True:
    10         # msg = input(">>:").strip()
    11         # if len(msg) == 0:continue
    12         # if msg == 'q':break
    13         msg = 'hello %s' %n
    14         sk.send(bytes(msg,'utf8'))
    15         data = sk.recv(1024)
    16         print("Received:",data.decode('utf8'))
    17 
    18     sk.close()
    19 
    20 if __name__ == '__main__':
    21     IP_Port = ('127.0.0.1', 9999)
    22     sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    23     sk.connect(IP_Port)
    24 
    25     thread_list = []
    26     for i in range(2000):
    27         t = threading.Thread(target=run,args=[i,])
    28         t.start()
    29         thread_list.append(t)
    30 
    31     for thread in thread_list:
    32         thread.join()
    client
  • 相关阅读:
    微服务常见安全认证方案Session token cookie跨域
    谈谈基于OAuth 2.0的第三方认证 [上篇]
    Kerberos安全体系详解---Kerberos的简单实现
    kerberos认证原理---讲的非常细致,易懂
    重放攻击(Replay Attacks)
    HTTP
    Cookie/Session机制详解
    cookie和session的区别与联系
    基于Token的WEB后台认证机制
    如何用phpmyadmin导入大容量.sql文件,直接使用cmd命令进行导入
  • 原文地址:https://www.cnblogs.com/xtsec/p/6985839.html
Copyright © 2020-2023  润新知