• Python层面中的高并发


    一、传输模型

    (一).基本模型

    (二).层次划分

    七层模型与四层模型

    作为Python开发,都是在应用层的HTTP协议之上进行开发的。HTTP协议是基于TCP之上的,也就是Python开发需要关心的是传输层。

     

    二、TCP连接

    (一).建立连接(三次握手)

    第一次,只是客户端告诉服务端。

    第二次,客户端才知道服务端收到了。

    第三次,服务端才知道客户端收到了。

    (二).传输数据

    客户端向服务端请求,服务端向客户端响应。

    一发一收,一收一发。

    (三).断开连接(四次挥手)

    因为服务端可能还有数据要发给客户端,所以在断开时就多了一次挥手。

     

    三、IP地址与端口

    (一).IP

    127.0.0.1(localhost):本地回环

    0.0.0.0:任意IP都可以访问

    (二).端口

    端口是用来区分我们的应用程序的

    端口的范围:0-65535,其中0-1023多为知名端口,1024-65535多为动态端口

     

    四、套接字编程

    (一).套接字基本概念

    (二).三种套接字

    (三).套接字编程的常用方法

    绑定套接字:bind()

    服务端监听套接字:listen()

    客户端连接服务端套接字:connect()

    对等连接套接字:accept()

    发送数据套接字:send()

    接收数据套接字:recv()

    关闭连接套接字:close()

    (四).示例

    (1).服务端代码

    # 服务端
    
    import socket
    from datetime import datetime
    
    server = socket.socket()  # 实例化
    server.bind(("127.0.0.1", 3333))  # 绑定
    server.listen(16)  # 监听
    print(server)
    
    while 1:
        conn, addr = server.accept()  # 生成对等连接套接字
        print(f"time:{datetime.now()}")
        msg = conn.recv(1024)  # 接收来自客户端的数据
        print(f"got a message from client:{msg}")
        conn.send(msg)  # 把数据发送回去给客户端
        conn.close()  # 把对等连接套接字关了。服务不要关,不然监听不到了
    View Code

    (2).客户端代码

    # 客户端
    
    import socket
    
    while 1:
        client = socket.socket()
        client.connect(("127.0.0.1", 3333))#连接
        sentence = input("enter your message:")
        msg = bytes(sentence, "utf-8")
        client.send(msg)
        response = client.recv(1024)
        print(f"have received response from server:{response}")
        client.close()
    View Code

    (3).先启动服务端

    不启动服务器,客户端怎么连过去?比如某服务器崩了,还能访问吗?

     

    五、非阻塞套接字

    第四部分中的示例是阻塞套接字,一次只能服务一个客户端,在accept()和recv()这两处会发生阻塞。

    (一).accept()阻塞

    在没有新的套接字来之前,不能处理已经建立连接的套接字的请求。

    (二).recv()阻塞

    在没有接受到客户端请求数据之前,不能与其他客户端建立连接。

    (三).普通服务器的IO模型

    (四).非阻塞IO模型

     

    六、IO多路复用

    第五部分的非阻塞套接字,有着非常严重的资源浪费,CPU吃满,无用处理很多。

    那么就需要考虑用更合适的技术来解决这个问题,这里使用Linux上的epoll,来解决这个问题。

    epoll是目前Linux上,效率最高的IO多路复用技术!

    (一).IO多路复用技术

    把socket交给操作系统去监控。

    (二).epoll是惰性的事件回调

    惰性事件回调是由用户进程自己调用的操作系统只起到通知的作用

     

    七、进程

    (一).计算机执行指令示意图

    (二).轮询调度实现并发执行

    并发:看上去一起执行,同时在发生

    并行:真正一起执行,同时在进行

    调度算法:时间片轮转;优先级调度

    前提:一个CPU

    (三).并行需要的核心条件

    并行真正的核心条件是有多个CPU

    (四).多进程实现并行

    (1).进程的概念

    计算机程序是存储在磁盘上的可执行二进制(或其他类型)文件。

    只有把它们加载到内存中,并被操作系统调用,它们才会拥有其自己的生命周期。

    进程则是表示的一个正在执行的程序。

    每个进程都拥有自己的地址空间、内存、数据栈,以及其他用于跟踪执行的辅助数据。

    操作系统负责其上所有进程的执行。操作系统会为这些进程合理地分配执行时间。

    (2).Python进程的使用流程

    (3).多进程并行的必要条件

    总进程数量不多于CPU核心数量!

    运行的程序都是轮询调度产生的并行假象。但是在Python层面的确获得了并行!

     

    八、线程

    (一).线程的概念

    线程被称作轻量级进程。与进程类似,不过它们是在同一个进程下执行的。并且它们会共享相同的上下文。

    当其他线程运行时,它可以被抢占(中断)和临时挂起(也成为睡眠),也被成为:让步。

    线程的轮询调度机制类似于进程的轮询调度。只不过这个调度不是由操作系统来负责,而是由Python解释器来负责。

    (二).Python线程的使用流程

    (三).GIL锁

    全局解释器锁

    Python在设计的时候,还没有多核处理器的概念。因此,为了设计方便与线程安全,直接设计了一个锁。这个锁要求,任何进程中,一次只能有一个线程在执行。

    因此,并不能为多个线程分配多个CPU。所以Python中的线程只能实现并发,而不能实现真正的并行。

    但是Python3中的GIL锁有一个很棒的设计,在遇到阻塞(不是耗时)的时候,会自动切换线程。因此我们可以利用这种机制来有效的避开阻塞,充分利用CPU

    Django、Flask、Web2py,都是使用多线程来做。

     

    九、并发通信

    (一).进程间的通信

    进程是互不干扰的独立内存空间

    进程间通信的解决方案:

    1.管理器负责与公共进程通信2.代理负责操作共享的空间

    (二).线程间的通信

    线程属于同一个进程,是共享内存空间的。会发生资源竞争的问题,需要用互斥锁来解决这个问题。

    互斥锁:控制共享资源的访问

    (三).线程与进程安全的队列

    队列的基本概念:

    一个入口,一个出口,先入先出,First input first output(FIFO)

     

    十、进程池与线程池

    (一).池的概念

    主线程: 相当于生产者,只管向线程池提交任务。并不关心线程池是如何执行任务的。因此,并不关心是哪一个线程执行的这个任务。

    线程池: 相当于消费者,负责接收任务,并将任务分配到一个空闲的线程中去执行。

    十一、greenlet

    (一).什么是greenlet

    虽然CPython(标准Python)能够通过生成器来实现协程,但使用起来还并不是很方便。

    与此同时,Python的一个衍生版 Stackless Python 实现了原生的协程,它更利于使用。

    于是,大家开始将 Stackless 中关于协程的代码单独拿出来做成了CPython的扩展包。

    这就是greenlet的由来,因此 greenlet 是底层实现了原生协程的C扩展库

    (二).安装包

    需要额外安装依赖包:sudo pip3 install greenlet

    (三).greenlet的价值

    (1).高性能的原生协程

    (2).语义更加明确的显式切换

    (3).直接将函数包装成协程,保持原有代码风格

    十二、gevent协程

    (一).什么事gevent协程

    虽然,我们有了 基于 epoll 的回调式编程模式,但是却难以使用。

    即使我们可以通过配合 生成器协程 进行复杂的封装,以简化编程难度。

    但是仍然有一个大的问题: 封装难度大,现有代码几乎完全要重写。

    gevent,通过封装了 libev(基于epoll) 和 greenlet 两个库。

    帮我们做好封装,允许我们以类似于线程的方式使用协程。

    以至于我们几乎不用重写原来的代码就能充分利用 epoll 和 协程 威力。

    (二).安装包

    sudo pip3 install gevent

    (三).gevent的价值

    遇到阻塞就切换到另一个协程继续执行 !

    (1).使用基于epoll的libev来避开阻塞

    (2).使用基于gevent的高效协程来切换执行

    (3).只在遇到阻塞的时候切换,没有轮询的开销,也没有线程的开销

  • 相关阅读:
    ant design pro梳理
    JSON.stringify()
    数组小细节
    js this细节
    策略模式解决if-else过多
    使用useState的赋值函数异步更新问题
    Hook
    React Api
    Intent
    树的非递归遍历
  • 原文地址:https://www.cnblogs.com/quanquan616/p/10022625.html
Copyright © 2020-2023  润新知