首页 > 编程语言 >并发编程之IO模型

并发编程之IO模型

时间:2024-03-03 11:47:43浏览次数:32  
标签:socket 编程 list 阻塞 并发 conn IO select

引言

Python的I/O模型分为同步(sync)异步(async)两种:

  • 同步I/O模型是指,当一个线程在等待I/O操作完成时,它不能执行其他任务,需要一直等待I/O操作完成,直到接收到I/O操作的完成通知后才继续执行。
  • 异步I/O模型是指,当一个线程发起一个I/O操作后,不会等待I/O操作完成,而是直接执行其他任务,当I/O操作完成后,再通过回调或事件通知来处理I/O操作的结果。

如果再对IO模型细分可以分为五种模型:

  • 同步阻塞IO(Blocking IO):即传统的IO模型。
  • 同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。
  • IO多路复用(IO MulTIplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。
  • 异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO。
  • signal driven IO:信号驱动IO , 在实际中并不常用,所以只剩下四种IO Model。

IO 模型详解

1、同步阻塞IO(Blocking IO)

Python 中的同步阻塞 I/O 是一种 I/O 操作,在这种情况下,程序的执行会被阻塞,直到 I/O 操作完成。换句话说,程序将等待 I/O 操作完成,才能继续执行下一个任务。这种类型的 I/O 被称为“阻塞”,因为它阻塞了程序的执行,并且被称为“同步”,因为它以同步的方式发生,程序等待 I/O 操作完成后再继续。

例如,当使用 Python 中的 read 方法从文件读取时,程序将等待整个文件读取完毕,才能继续执行下一个任务。这是同步阻塞 I/O 的一个例子。

当 I/O 操作相对较短且程序可以等待其完成后才继续执行下一个任务时,通常会使用同步阻塞 I/O。然而,当 I/O 操作比较长时,程序可能会被阻塞很长一段时间,从而导致性能下降。在这种情况下,通常更好使用异步 I/O。

linux中,默认情况下所有的socket都是阻塞IO,一个典型的读操作流程大概是这样:

image

其实还是没有解决IO问题 该等的地方还是得等 没有规避 只不过多个人等待彼此互不干扰。示例如下:

服务端:

import socket

server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)

while True:
    conn, addr = server.accept()
    while True:
        try:
            data = conn.recv(1024)
            if len(data) == 0:break
            print(data)
            conn.send(data.upper())
        except ConnectionResetError as e:
            break
    conn.close()

客户端:

import socket

client = socket.socket()
client.connect(('127.0.0.1',8081))

while True:
    client.send(b'hello world')
    data = client.recv(1024)
    print(data)

2、同步非阻塞IO(Non-blocking IO)

Python 中的同步非阻塞 I/O 是一种程序不必等待 I/O 操作完成就可以继续执行下一个任务的 I/O 操作。相反,程序在 I/O 操作正在进行时继续执行下一个任务。这种类型的 I/O 被称为“非阻塞”,因为它不会阻塞程序的执行,并且被称为“同步”,因为它仍以同步的方式操作,程序会定期检查 I/O 操作的状态。

例如,在 Python 中实现同步非阻塞 I/O,可以使用 select 模块,该模块提供了对操作系统底层 I/O 多路复用功能的访问,允许您同时监视多个 I/O 操作,而不会阻塞。

当 I/O 操作预计需要很长时间才能完成,且程序需要在此期间继续处理其他任务时,通常使用同步非阻塞 I/O。这可以提高程序的性能和响应性。

python下,可以通过设置socket使其变为non-blocking(server.setblocking(False))。当对一个non-blocking socket执行读操作时,流程是这个样子:

image

示例如下:

服务端:

import socket
import time

server = socket.socket()
server.bind(('127.0.0.1', 8081))
server.listen(5)
server.setblocking(False)
# 将所有的网络阻塞变为非阻塞
r_list = []
del_list = []
while True:
    try:
        conn, addr = server.accept()
        r_list.append(conn)
    except BlockingIOError:
        for conn in r_list:
            try:
                data = conn.recv(1024)  # 没有消息 报错
                if len(data) == 0:  # 客户端断开链接
                    conn.close()  # 关闭conn
                    # 将无用的conn从r_list删除
                    del_list.append(conn)
                    continue
                conn.send(data.upper())
            except BlockingIOError:
                continue
            except ConnectionResetError:
                conn.close()
                del_list.append(conn)
        # 挥手无用的链接
        for conn in del_list:
            r_list.remove(conn)
        del_list.clear()

客户端:

import socket

client = socket.socket()
client.connect(('127.0.0.1',8081))

while True:
    client.send(b'hello world')
    data = client.recv(1024)
    print(data)

3、IO多路复用(IO MulTIplexing)

"IO 多路复用" 是一种用于监听多个网络连接的技术,以提高网络程序的性能和效率。常用的 IO 多路复用技术有 select、poll、epoll

在 Python 中,可以使用 select 模块来实现 IO 多路复用。select 模块提供了三个函数:select、poll、epoll,可以在不同的平台上使用不同的函数来实现 IO 多路复用。它们都是用来监听多个文件描述符(socket)的读写情况,以实现对多个socket的高效管理。

  • select:select 是最早的 I/O 多路复用 API 之一,并在大多数 Unix 类系统上广泛支持。select 可以监视大量的文件描述符,但它有许多限制,例如具有固定的最大文件描述符数量(1024),以及当正在监视的文件描述符列表更改时受到竞争条件的影响。
  • poll:poll 是为了解决 select 中的一些限制而引入的,在大多数 Unix 类系统上广泛支持。poll 可以监视比 select 更多的文件描述符(无限制),并且它还提供了关于每个文件描述符状态的更多信息。
  • epoll:epoll 被引入为 poll 和 select 的更有效替代品,并可在 Linux 系统上使用。epoll 具有许多比 poll 和 select 更有效的性能优势,例如更快更可扩展的设计,以及能够以较低开销监视大量文件描述符的能力。

select、poll、epoll之间的区别:

select poll epoll
操作方式 遍历 遍历 回调
底层实现 数组 链表 哈希表
IO效率 每次调用都进行线性遍历,时间复杂度为O(n) 每次调用都进行线性遍历,时间复杂度为O(n) 事件通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到rdllist里面。时间复杂度为O(1)
最大连接数 1024(x86)或2048(x64) 无上限 无上限
fd拷贝 每次调用select,都需要把fd集合从用户态拷贝到内核态 每次调用poll,都需要把fd集合从用户态拷贝到内核态 调用epoll_ct时拷贝进内核并保存,之后每次epoll_wait不拷贝

它的流程如图:

image

  • 管的对象只有一个的时候 其实IO多路复用连阻塞IO都比不上!!!但是IO多路复用可以一次性监管很多个对象
  • 监管机制是操作系统本身就有的 如果你想要用该监管机制(select)需要,
  • 你导入对应的select模块

示例如下:

import socket
import select

server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)
server.setblocking(False)
read_list = [server]

while True:
    r_list, w_list, x_list = select.select(read_list, [], [])
    """
    帮你监管
    一旦有人来了 立刻给你返回对应的监管对象
    """
    # print(res)  # ([<socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080)>], [], [])
    # print(server)
    # print(r_list)
    for i in r_list:  #
        """针对不同的对象做不同的处理"""
        if i is server:
            conn, addr = i.accept()
            # 也应该添加到监管的队列中
            read_list.append(conn)
        else:
            res = i.recv(1024)
            if len(res) == 0:
                i.close()
                # 将无效的监管对象 移除
                read_list.remove(i)
                continue
            print(res)
            i.send(b'hello python')

 # 客户端
import socket

client = socket.socket()
client.connect(('127.0.0.1',8080))

while True:

    client.send(b'hello world')
    data = client.recv(1024)
    print(data)

4、异步IO(Asynchronous IO)

异步IO模型是所有模型中效率最高的 也是使用最广泛的 。先看一下它的流程:

image

"""
异步IO模型是所有模型中效率最高的 也是使用最广泛的
相关的模块和框架
	模块: asyncio模块
	异步框架:sanic tronado twisted
		速度快!!!
"""
import threading
import asyncio

@asyncio.coroutine
def hello():
    print('hello world %s'%threading.current_thread())
    yield from asyncio.sleep(1)  # 换成真正的IO操作
    print('hello world %s' % threading.current_thread())

loop = asyncio.get_event_loop()
tasks = [hello(),hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

进程、线程与协程的关系与区别

进程、线程和协程是操作系统中用来管理程序执行的三种不同的技术。

  • 进程:进程是操作系统中最基本的资源分配单元,是程序的实体。它是系统进行资源分配和调度的独立单位。每个进程都有独立的内存空间,因此在一个进程中出现故障不会影响其他进程。
  • 线程:线程是进程的一个执行单元,是操作系统分配资源的最小单元。多线程可以共享进程的资源,因此线程间的通信和协作比进程间更加方便。不幸的是,线程之间存在竞争关系,并且当一个线程出现故障时,整个进程都会受到影响。
  • 协程:协程是一种在单线程中执行多任务的机制,是线程的一种特殊形式。它不同于多线程,因为它不会独立分配资源,而是在一个线程中共享资源。因此,协程的实现比线程更加轻量,可以提高程序的效率。不过,协程的实现也比线程更加复杂,因为需要编写更多的代码来协调任务的。

详细解释:
链接:https://juejin.cn/post/7198505494251601981

标签:socket,编程,list,阻塞,并发,conn,IO,select
From: https://www.cnblogs.com/xiao01/p/18049751

相关文章

  • 并发编程补充:基于多线程实现并发的套接字通信
    服务端:fromsocketimport*fromthreadingimportThreaddefcommunicate(conn):whileTrue:try:data=conn.recv(1024)ifnotdata:breakconn.send(data.upper())exceptConnectionResetError:......
  • 并发编程补充:基于多进程实现并发的套接字通信
    服务端:frommultiprocessingimportProcessfromsocketimport*deftalk(conn):whileTrue:try:data=conn.recv(1024)ifnotdata:breakconn.send(data.upper())exceptConnectionResetError:......
  • 并发编程之定时器
    定时器定时器,指定n秒后执行某操作简易版:fromthreadingimportTimerdeftask(name):print('hello%s'%name)t=Timer(5,task,args=('xiao',))t.start()#helloxiao应用版:##验证码定时器fromthreadingimportTimerimportrandomclassCode:......
  • 并发编程之条件Condition
    条件Condition(了解)使得线程等待,只有满足某条件时,才释放n个线程importthreadingdefrun(n):con.acquire()con.wait()print("runthethread:%s"%n)con.release()if__name__=='__main__':con=threading.Condition()foriinra......
  • 「java.util.concurrent并发包」之 Unsafe
    一unsafe介绍Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。但由于Unsafe类使Java语言拥有了类似C......
  • Semantic Kernel 学习笔记:通过 Kernel Memory 初步体验 Retrieval Augmented Generati
    学习材料:QuickintrotoKernelMemory:install,uploadadoc,askaquestion创建控制台项目dotnetnewconsoledotnetaddpackageMicrosoft.KernelMemory.Core创建IKernelMemory实例varmemory=newKernelMemoryBuilder().WithOpenAIDefaults(OPENAI_API_KEY......
  • Windows配置R语言、RStudio开发环境
      本文介绍R语言及其集成开发环境RStudio的下载、安装方法。  R语言是一个属于GNU操作系统的开源软件,在数据统计与分析、可视化等方面具有优秀的表现;而RStudio则是R语言的集成开发环境(IDE),可以帮助我们更好地编辑、调试R语言的代码。这二者的关系有点类似于Python与Spyder的关......
  • CF1931G One-Dimensional Puzzle 题解
    CF1931GOne-DimensionalPuzzle题解题意传送门思路考虑一下怎么入手,发现一个拼图只能接一些拼图(废话但是有用),所以我们可以简单地画出一个链接关系的图,\(u\tov\)表示编号为\(u\)的拼图后面能够接编号为\(v\)的拼图。然后我们发现问题转换为:......
  • AT_joig2021_d 展覧会 2 (Exhibition 2) 题解
    题目传送门前置知识二分答案解法最小值最大,考虑二分答案。关于check函数的书写,比luoguP1182数列分段SectionII多了个对位置的判定,注意对当前是第一次展出时进行特判。代码#include<bits/stdc++.h>usingnamespacestd;#definelllonglong#defineullunsigne......
  • bullet3-collision例子
    bullet3的碰撞检测例子在examples/collision,CollisionTutorialBullet2类中,先是plCreateCollisionWorld,然后plCreateSphereShape,创建一些几何shape,然后调plCollide执行两个shape之间的碰撞检测,返回contact点个数及坐标,调plWorldCollide执行world中所有shape的碰撞检测。plXXX(.........