
本文系统介绍了计算机基础知识和协程编程。首先讲解了计算机硬件组成CPU、存储器、输入输出设备和软件分类系统软件、应用软件以及计算机语言发展历程机器语言→汇编语言→高级语言。重点阐述了协程的概念与实现协程是用户态的轻量级线程通过事件循环实现任务调度利用await关键字挂起IO操作并切换任务提高CPU利用率。文章通过代码示例展示了协程函数的定义、任务创建(asyncio.create_task)、并发执行(asyncio.gather)等核心用法最后对比了传统同步下载和协程异步下载图片的差异突显协程在IO密集型任务中的性能优势。第 1 章 必备基础知识1. 计算机组成1.1. 硬件计算机硬件主要由五个部分组成分别是运算器、控制器、存储器、输入设备、输出设备。备注:『运算器』和『控制器』一起组成了中央处理器CPU。注意计算机的 CPU 只能理解并执行二进制用 0和1表示信息的机器指令。内存VS硬盘硬盘持久化存储读写速度不如内存快但容量通常比较大500GB、1TB、2TB 等。内存暂时性存储读写速度快但容量通常不如硬盘大8GB、16GB、32GB、64GB 等。通俗理解能安装多少个游戏取决于硬盘的大小能同时开几个游戏取决于内存的大小。运算器运算器简称ALU专门负责执行各种『算术运算』和『逻辑运算』它需要与控制单元、寄存器等紧密配合。控制器计算机的控制中心它指挥计算机各部分协调地工作保证计算机按照预先规定的任务有条不紊地进行操作及处理。存储器计算机中的“资料库”它既保存程序指令又保存数据各个硬件在需要访问或更新数据时都会与它打交道有了存储器计算机才有“记忆”。输入设备向计算机输入数据和信息的设备是计算机与外界通信的桥梁。输出设备用于输出计算机执行任务的结果把各种结果数据或信息以数字、字符、图像、声音等形式表示出来。1.2. 软件计算机软件主要分为系统软件、应用软件。系统软件直接管理和控制计算机硬件的软件为应用软件提供运行平台它负责协调硬件资源如内存、处理器并提供通用服务例如文件管理、设备控制、任务调度。应用软件用于执行特定任务的软件满足用户的具体需求如文档编辑、数据分析、娱乐等它依赖系统软件提供的资源和服务。2. 计算机语言、代码、程序2.1.计算机语言计算机语言是人类与计算机进行『交互』和『指令传达』所使用的一种形式化语言。比如人与人之间需要使用各种语言进行交流那人与计算机之间同样也需要语言进行沟通。2.2.代码代码是在计算机语言规则的约束下编写出来的一组指令具体描述了要让计算机去执行的操作。简言之就是计算机语言是规则代码是基于这些规则所编写出来的一行一行的指令。2.3.程序代码按照特定的顺序和逻辑组合后就是程序程序通常用于完成某种特定的任务或功能。如果说程序是一道菜那代码就是做这道菜的某个步骤。3. 计算机语言简史3.1. 第一代语言机器语言计算机问世的初期人们只能通过『机器语言』又称机器码来操作计算机所谓机器语言就是0和1组成的二进制内容。而且在当时录入和修改信息通常都需要拨动开关、或插拔连线、或使用打孔纸带来输入指令。机器语言虽然能充分利用硬件性能但所有操作都必须通过二进制来完成所以编程的过程极为繁琐且容易出错对程序员的理解能力和耐心都要求极高。例如在x86的 CPU 架构下使用机器码编写1 1的运算代码如下10110000 00000001 00000100 000000013.2. 第二代语言汇编语言用机器语言编程程序员很难理解每一条指令的含义为了解决这个问题『汇编语言』应运而生它将机器语言中的二进制指令转化为更容易记忆的助记符如MOV、ADD、LOAD等从而让程序员能以近似“英文简写”的方式进行编程简单说就是『汇编语言』是对『机器语言』的“人性化翻译”汇编语言显著降低了编程的门槛也为后续高级语言的诞生打下了基础。例如在x86的 CPU 架构下使用『汇编语言』编写1 1的运算代码如下mov al, 1 add al, 1注意『汇编语言』需要翻译成『机器码』才能交给 CPU 执行因为 CPU 只认二进制指令。3.3. 第三代语言高级语言相对『机器语言』和『汇编语言』而言『高级语言』更接近人类的自然语言它允许程序员使用英语来编写程序并向程序员屏蔽了大部分的底层细节语言中的符号和算式也和日常的数学算式差不多它更容易被掌握常见的『高级语言』有C、C、Java、PHP、Go、Rust、JavaScript、Python 等。例如下面的 Python 代码可以输出Hello, world!print(Hello, world!)例如下面的 Java 代码可以输出Hello, world!public class Main { public static void main(String[] args) System.out.println(1 1); } }注意计算机不能直接执行『高级语言』同样需要将其转换为『机器语言』才能被计算机执行。4.『编译型语言』与『解释型语言』对于高级语言来说我们会根据其转换成二进制指令过程的不同可将其分为『编译型』和『解释型』4.1.编译型语言将程序翻译成计算机能理解的二进制内容并且通常会生成一个可执行文件例如在 windows 系统上生成的可执行文件是.exe文件常见的『编译型』语言有C、C、Go、Rust 等。编译型语言的特点优势同一运行平台代码只需编译一次且执行效率高。劣势跨平台性差大型项目编译时间较长开发效率略低。4.2. 解释型语言将程序一句一句的翻译为计算机可以执行的指令整个过程通常不生成可执行文件常见的解释型语言有Python、Php、JavaScript 等。解释型语言的特点优势跨平台性好无需编译开发调试灵活高效。劣势每次运行都需要解释执行效率较低。4.3. 二者对比编译型语言解释型语言举例C、C、Go、Rust 等Python、JavaScript、Ruby 等执行流程运行前把所有程序一次性翻译成机器码并生成可执行文件。运行时靠对应的解释器把代码一句一句翻译成机器码执行。是否生成可执行文件是一次编译多处运行。否每次都要靠解释器翻译后再运行。运行速度快慢是否跨平台否需要针对平台编译。是只要该平台下有解释器就能运行。适合场景系统底层、性能要求较高的场景。脚本、数据分析、AI 应用、Web开发等。5. 什么是协程概念协程Coroutine是一种线程内部的任务调度机制它通过事件循环在用户态中实现任务的挂起与恢复执行从而在遇到 IO 操作时不让 CPU 等待而是继续执行其它需要 CPU 的任务。协程的本质就是在一个线程里趁着某些任务在等 IO把 CPU 交给其它任务去用。关键点1️⃣协程不是线程也不是进程协程不是操作系统提供的并且 CPU 看不见协程。操作系统不知道协程的存在。协程是程序员在用户态用代码“设计出来”的任务切换机制。关键点2️⃣协程发生在一个线程内部协程不是线程之间的切换。而是线程内部多个任务之间的切换。本质是一个线程里写了很多任务由事件循环统一调度。关键点3️⃣协程的核心能力挂起与恢复当任务 遇到 IO 操作 时任务会被挂起。当 IO 操作完成后任务会被恢复执行。关键点4️⃣协程依赖一个关键角色事件循环事件循环负责调度任务、判断是否该挂起、决定何时恢复执行事件循环是协程系统的“大脑”关键点5️⃣协程的目标是尽量减少线程切换在单线程场景下最大化 CPU 利用率特别适合 IO 密集型任务6. 协程函数 vs 协程对象协程函数(coroutine Function)使用『async关键字』修饰的函数就是协程函数。协程对象(coroutine Object)调用『协程函数』就会得到『协程对象』。注意调用『协程函数』并不会执行『协程函数』中的代码。# 定义一个协程函数 async def work(): print(work开始) print(work执行中......) print(work结束) return 工作结果 # 调用协程函数会得到协程对象 coroutine_object work()将协程对象交给asyncio.run()asyncio.run()会将协程对象包装成一个任务交给事件循环。# asyncio.run 方法做了3件事 # 1.创建一个事件循环。 # 2.将收到的协程对象包装成一个任务task交给事件循环。 # 3.启动事件循环。 # 注意asyncio.run 会阻塞当前线程直到任务执行完毕并返回该任务 return 的最终结果。 result asyncio.run(coroutine_object) print(result)7. await 关键字await 关键有三个作用挂起await 会暂停当前协程的执行。等待遇到 await 关键字事件循环会立即安排 await 后面的对象去执行并等待该对象执行完成并且可以拿到执行结果。关键点在执行 await 后面的对象时会出现两种情况:情况一如果在执行该对象中的代码时遇到了【await I/O操作】需要等待外部资源返回结果的操作例如网络请求、文件读写等那 CPU 的控制权就会交给事件循环。事件循环会去调度循环中的其他任务如果有的话。情况二如果该对象中的代码不包含任何【await I/O操作】。例如print打印、数学计算、逻辑计算等。此时事件循环拿不到 CPU 控制权无法调度循环中的其他任务不会发生任务切换。恢复当 await 后的对象执行完毕事件循环会恢复之前被挂起的协程该协程会从当时挂起的位置继续执行并拿到返回值。注意await 后面只能写『可等待对象』常见可等待对象有协程对象、Future对象、Task对象。import asyncio async def work(): print(work开始) print(work执行中......) # await去等待一个协程对象靠asyncio.sleep方法返回一个协程对象 res await asyncio.sleep(2) print(res) print(work结束) return 工作结果 async def main(): print(main开始) # await去等待一个协程对象靠自己去编写协程函数随后调用该函数来得到协程对象 res await work() print(res) print(main结束) return main的返回值 result asyncio.run(main()) print(result)8. 多个任务同步执行使用 await 实现多个任务同步执行import asyncio import time # 定义一个协程函数 async def work(n, delay): print(fwork{n}开始) print(fwork{n}执行中......) # 模拟一个IO等待 await asyncio.sleep(delay) print(fwork{n}结束) return fwork{n}的返回值 async def main(): print(main开始) start time.time() # 调用三次work函数分别得到三个协程对象 coroutine1 work(1, 2) coroutine2 work(2, 2) coroutine3 work(3, 2) # 此处会等待coroutine1执行完成 res1 await coroutine1 print(res1) # 等待上面的coroutine1完成后再等待coroutine2完成 res2 await coroutine2 print(res2) # 等待上面的coroutine2完成后再等待coroutine3完成 res3 await coroutine3 print(res3) print(main结束, time.time() - start) return 我是main的返回值 # 将协程对象交给事件循环 result asyncio.run(main()) print(result)9. 多个任务异步执行使用asyncio.create_task()方法向事件循环中添加任务从而实现多个任务异步执行。import asyncio import time # 定义一个协程函数 async def work(n, delay): print(fwork{n}开始) print(fwork{n}执行中......) # 模拟一个IO等待 await asyncio.sleep(delay) print(fwork{n}结束) return fwork{n}的返回值 async def main(): print(main开始) start time.time() # asyncio.create_task 会把一个协程对象包装成一个可被事件循环调度的任务并注册到事件循环中 task1 asyncio.create_task(work(1, 2)) task2 asyncio.create_task(work(2, 2)) task3 asyncio.create_task(work(3, 2)) # 此处会等待task1执行完成 res1 await task1 print(res1) # 等待上面的task1完成后再等待task2完成 res2 await task2 print(res2) # 等待上面的task2完成后再等待task3完成 res3 await task3 print(res3) print(main结束, time.time() - start) return 我是main的返回值 # 将协程对象交给事件循环 result asyncio.run(main()) print(result)10. asyncio.gatherasyncio.gather方法可以把多个协程对象丢给事件循环并在全部执行完后一次性拿到所有结果。import asyncio import time # 定义一个协程函数 async def work(n, delay): print(fwork{n}开始) print(fwork{n}执行中......) # 模拟一个IO等待 await asyncio.sleep(delay) print(fwork{n}结束) return fwork{n}的返回值 async def main(): print(main开始) start time.time() # 把多个协程对象同时丢给事件循环并在全部执行完后一次性拿到所有结果。 result await asyncio.gather(work(1, 2), work(2, 2), work(3, 2)) print(result) print(main结束, time.time() - start) return 我是main的返回值 # 将协程对象交给事件循环 result asyncio.run(main()) print(result)11. 下载图片案例1️⃣使用传统方式下载图片如下代码的特点图片是一张一张下载的当前图片没有下载完成后一张图片的下载就不能开始这属于典型的同步下载。import requests def download_picture(url): print(f开始下载{url}) # 发送网络请求获取这张图片 response requests.get(url) print(下载完毕) # 保存图片到本地 with open(url[-10:], wb) as file: file.write(response.content) def main(): url_list [ https://n.sinaimg.cn/spider20260129/217/w600h417/20260129/3e26-917ee55a8a42b8626807c332c24981de.png, https://n.sinaimg.cn/finance/transform/97/w630h267/20260129/97c4-b211cc51784830f09ee19e450475c93b.png, https://n.sinaimg.cn/spider20260129/539/w1439h700/20260129/e09a-cc2ca319e00f701ccfca3ebc62aa8772.png ] for url in url_list: download_picture(url) main()2️⃣使用协程方式下载图如下代码的特点多张图片会几乎同时发起下载请求当某一张图片在等待网络数据返回时其它图片的下载任务并不会被阻塞而是可以继续执行这属于典型的协程并发下载。import aiohttp import asyncio async def download_picture(session, url): print(f开始下载{url}) # 发送网络请求获取这张图片请求发出去后要等待服务器把数据返回等的这段时间就是IO等待 response await session.get(url) # 等待数据图片数据可能分多次传输需要等待数据全部读完等的这段时间也是IO等待 content await response.read() print(下载完毕) # 保存图片到本地 with open(url[-10:], wb) as file: file.write(content) # 释放连接资源告诉 aiohttp这个连接我不用了你可以回收了 await response.release() async def main(): url_list [ https://n.sinaimg.cn/spider20260129/217/w600h417/20260129/3e26-917ee55a8a42b8626807c332c24981de.png, https://n.sinaimg.cn/finance/transform/97/w630h267/20260129/97c4-b211cc51784830f09ee19e450475c93b.png, https://n.sinaimg.cn/spider20260129/539/w1439h700/20260129/e09a-cc2ca319e00f701ccfca3ebc62aa8772.png ] # 创建会话对象发请求的工具 session aiohttp.ClientSession() # 创建多个协程对象 coroutine_list [download_picture(session, url) for url in url_list] # 将多个协程对象交给事件循环 await asyncio.gather(*coroutine_list) # 关闭会话 await session.close() asyncio.run(main())