IO模型详解
-
阻塞IO(blocking IO)
在阻塞IO模型中,当一个进程需要从某个设备(例如硬盘)中读取数据时,它必须等待这个设备的数据准备好,然后才能继续执行。这个等待的过程就是阻塞,因为它阻止了进程的进一步执行。这种模型在早期的系统中非常常见,因为它相对简单,但是它的问题在于效率不高,因为CPU在等待IO操作完成的过程中可能会闲置。
-
非阻塞IO(non-blocking IO)
在非阻塞IO模型中,当一个进程请求IO操作时,它不会阻塞,而是立即返回。但是,这个操作可能还没有完成。因此,进程需要定期检查IO操作的进度,看是否已经准备好。如果还没有准备好,进程就去做其他的事情。这种模型可以让CPU更有效地利用起来,因为即使IO操作没有完成,进程也可以做其他的事情。但是,它也有缺点,因为它需要进程不断地检查IO操作的进度,这可能会增加CPU的负载。
-
多路复用IO(IO multiplexing)
多路复用IO模型是一种更高级的IO模型。在多路复用IO模型中,进程可以在等待某个IO操作时,同时等待多个其他的IO操作。如果有一个IO操作准备好了,进程就处理这个操作,然后继续等待其他的操作。这种模型可以有效地利用CPU,因为它允许多个IO操作同时进行。多路复用IO模型通常使用操作系统提供的一些特殊机制来实现,例如poll或select。
-
异步IO(Asynchronous I/O)
异步IO模型是一种最先进的IO模型。在异步IO模型中,当一个进程请求一个IO操作时,它不会等待这个操作完成。相反,它只是简单地告诉操作系统:“我想做一个IO操作,你帮我完成它,然后通知我。”然后,进程就可以做其他的事情了。当操作系统完成IO操作时,它会通知进程。这种模型的最大优点是效率非常高,因为进程不需要等待IO操作完成就可以做其他的事情。然而,实现异步IO模型通常需要更复杂的编程技巧和操作系统支持。
-
asyncio简单介绍
以下是 asyncio
模块中一些常用的函数和参数的详解:
asyncio.run(main)
: 这个函数是用来创建主事件循环并运行主程序的主要函数。参数main
是一个协程对象,表示主程序。当调用asyncio.run(main)
时,它会创建一个新的事件循环,然后运行main
协程,直到main
协程完成。在main
协程结束后,事件循环会关闭。asyncio.get_event_loop()
: 这个函数用于获取当前的主事件循环。如果当前线程没有事件循环,它会创建一个新的事件循环并返回。如果有多个线程同时调用这个函数,每个线程都会得到一个独立的事件循环。asyncio.set_event_loop(loop)
: 这个函数用于设置当前线程的事件循环为loop
。如果当前线程没有事件循环,或者loop
是当前线程的事件循环,这个函数没有任何效果。否则,它会抛出RuntimeError
。asyncio.sleep(delay)
: 这个函数用于挂起当前协程一段时间,类似于休眠。在休眠期间,这个协程会让出 CPU 给其他协程使用。它的参数delay
是休眠的时间(秒)。当休眠结束时,它会返回一个新的Future
对象,可以用来获取休眠的结果。asyncio.run_until_complete(coro)
接受一个协程(coroutine)对象作为参数,并运行这个协程直到它完成。这个函数会阻塞当前线程,直到协程运行完毕。
#示例1
import threading
import asyncio
@asyncio.coroutine
def hello():
print('Hello world! (%s)' % threading.currentThread())
yield from asyncio.sleep(3)
print('Hello again! (%s)' % threading.currentThread())
loop = asyncio.get_event_loop()
tasks = [hello(), hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
"""
Hello world! (<_MainThread(MainThread, started 16812)>)
Hello world! (<_MainThread(MainThread, started 16812)>)
(暂停大约3秒)
Hello again! (<_MainThread(MainThread, started 16812)>)
Hello again! (<_MainThread(MainThread, started 16812)>)
"""
#示例2
import threading
import asyncio
async def hello():
print('Hello world! (%s)' % threading.currentThread())
await asyncio.sleep(3)
print('Hello again! (%s)' % threading.currentThread())
async def main():
task1 = asyncio.create_task(hello())
task2 = asyncio.create_task(hello())
# 等待所有任务完成
await task1
await task2
# 所有任务完成,输出结果
print("All operations completed")
# 运行主程序协程
asyncio.run(main())
"""
Hello world! (<_MainThread(MainThread, started 16812)>)
Hello world! (<_MainThread(MainThread, started 16812)>)
(暂停大约3秒)
Hello again! (<_MainThread(MainThread, started 16812)>)
Hello again! (<_MainThread(MainThread, started 16812)>)
"""
-
对比asyncio库和threading库
import threading
import asyncio
import time
#基于协程的异步编程实现
@asyncio.coroutine
def hello():
print('Hello world! (%s)' % threading.currentThread())
yield from asyncio.sleep(3)
print('Hello again! (%s)' % threading.currentThread())
loop = asyncio.get_event_loop()
tasks = [hello(), hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
#多线程实现
def hello():
print('Hello world! (%s)' % threading.currentThread())
time.sleep(3)
print('Hello again! (%s)' % threading.currentThread())
if __name__ == '__main__':
task1 = threading.Thread(target=hello)
task2 = threading.Thread(target=hello)
task1.start()
task2.start()
time.sleep(3) # Wait for threads to finish
task1.join()
task2.join()
- 第一段代码使用了
asyncio
库,这是一个用于处理 I/O 操作的库,它支持基于协程的异步编程。协程是一种可以被暂停和恢复的函数,非常适合处理异步操作。在asyncio
中,通过async
和await
关键词可以定义协程和暂停/恢复协程的执行。asyncio.coroutine
装饰器在 Python 3.4 中已被弃用,但在 Python 3.5 中,使用async
和await
代替。这段代码中的asyncio.coroutine
和@asyncio.coroutine
是等价的。
这段代码中,主线程会创建两个任务(协程),每个任务执行函数 hello
,然后主线程等待这两个任务完成。这里使用了 asyncio.wait
来等待所有任务完成。
- 第二段代码使用了
threading
库,这是 Python 标准库中用于处理线程的库。线程是操作系统分配的最小的执行单元,每个线程都有自己的栈和局部变量。这段代码中,主线程会创建两个线程,每个线程执行函数hello
。然后主线程等待这两个线程完成。
这两种并发模型的主要区别在于:
asyncio
是单线程的,它主要用于 I/O 密集型任务,适合处理大量并发的小任务。threading
是多线程的,它主要用于 CPU 密集型任务,适合处理大量并发的计算任务。
在性能上,这两种模型各有优缺点。多线程由于可以同时执行多个任务,因此在处理大量并发的计算任务时具有优势。但是创建线程的开销比较大,线程间的数据共享和通信也比较复杂。而异步IO由于其非阻塞的特性,可以更高效地利用CPU,因此在处理大量并发的I/O任务时具有优势。但是当需要等待某个任务完成时(例如等待磁盘I/O完成),异步IO可能会造成其他任务被挂起,从而降低整体的并发度。
补充:
I/O密集型任务是指那些需要大量输入输出操作的程序,例如网络服务器、数据库系统和文件传输等。这些应用程序通常需要高速的磁盘、网络和其他输入输出设备来完成任务,而CPU的运算能力相对较少。
CPU密集型任务是指那些需要大量CPU运算能力的程序,通常涉及到大量的数据处理和计算操作,例如科学计算、图像处理和加密解密等。这些应用程序通常需要高速的CPU和大量的内存来完成任务。
想了解更多关于asyncio模块知识可以看一下以下这篇文章: