<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://ryviuszero.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://ryviuszero.github.io/" rel="alternate" type="text/html" hreflang="zh" /><updated>2026-05-23T10:05:54+00:00</updated><id>https://ryviuszero.github.io/feed.xml</id><title type="html">ryviuszero</title><subtitle>Personal technical notes on programming, graphics, and software development.</subtitle><entry xml:lang="en"><title type="html">After Building a Complex Project with Codex: A Personal Retrospective</title><link href="https://ryviuszero.github.io/en/posts/codex-complex-project-development/" rel="alternate" type="text/html" title="After Building a Complex Project with Codex: A Personal Retrospective" /><published>2026-05-21T16:00:00+00:00</published><updated>2026-05-21T16:00:00+00:00</updated><id>https://ryviuszero.github.io/en/posts/codex-complex-project-development-EN</id><content type="html" xml:base="https://ryviuszero.github.io/en/posts/codex-complex-project-development/"><![CDATA[<h2 id="introduction-written-for-myself-one-month-ago">Introduction: Written for Myself One Month Ago</h2>

<p>After bringing Fast Sub to its current stage, I finally feel able to answer a question I kept asking myself a month ago: if I really want to use tools like Codex or Claude Code to build a complete project, how would I actually do it?</p>

<p>Back then, I wanted to find an article or video that showed a real project built from zero to something releasable with AI coding tools. Not a ten-minute demo, not a “type one sentence and generate an app” showcase, but a slightly complex real project: changing requirements, architecture tradeoffs, refactoring, testing, UI, packaging, QA, and cleanup before open sourcing. I looked around and found very little that I could actually reference.</p>

<p>To make the rest easier to understand, let me first describe the scale of Fast Sub. It ended up being much more than a simple script. It includes a Python CLI, Go product core, Go daemon/job API, Electron desktop UI, local worker, model download flow, providers, packaging, release checks, and open source documentation.</p>

<p>This is the complexity estimate I asked Codex to produce from the git history:</p>

<blockquote>
  <p>Based on the git history, from the first commit to the current state, the time span is from 2026-04-24 to 2026-05-22, about 28 calendar days; counting both start and end dates, it is within 29 days. The number of active development days with commits is about 13. There are 71 commits in total, and the latest commit is <code class="language-plaintext highlighter-rouge">2026-05-22 Stabilize desktop readiness tests</code>. More precisely, this is an MVP demo built over roughly 4 calendar weeks, with about 2 weeks of active development days. In terms of human team effort, the current output feels like 2-3 people working intensely for 3-5 weeks, or a solo founder plus AI agents pushing it forward in about one month.</p>
</blockquote>

<p>The project evolution can be compressed into this line:</p>

<pre><code class="language-mermaid">flowchart LR
  A["Python CLI&lt;br/&gt;Run the core subtitle flow"] --&gt; B["Go core / daemon&lt;br/&gt;Stabilize product boundaries"]
  B --&gt; C["Electron mock-first&lt;br/&gt;Validate UI state flow first"]
  C --&gt; D["Daemon integration&lt;br/&gt;Connect real local capabilities"]
  D --&gt; E["Release readiness&lt;br/&gt;Packaging, QA, and open source cleanup"]
</code></pre>

<p>After going through it myself, I now understand why this kind of experience is hard to compress into a short “tutorial.” Many articles eventually turn into distilled methodology. They look correct, but when I actually start a project, I still get stuck on what to do first, when to stop, and which parts require my own judgment.</p>

<p>So this article is more like a note to myself from one month ago. It is not meant to prove how powerful Codex is, nor to claim that AI can replace all development work. After this round, I simply have a more concrete feeling for what it means to move a project forward together with AI.</p>

<p>I am still figuring this out myself. What follows is definitely not a standard answer. More accurately, it is a set of temporary lessons I learned by building Fast Sub.</p>

<h2 id="1-after-this-project-my-view-of-ai-coding-changed">1. After This Project, My View of AI Coding Changed</h2>

<p>When I first started using AI to write code, it was easy to fall into an illusion: as long as I described the requirement clearly, I could hand the rest over to it.</p>

<p>For temporary scripts, that feeling is often true. I say I want to process a file, call an API, or generate some test data. It writes the code, I run it, fix a few small issues, and the task is done.</p>

<p>But Fast Sub was not that kind of project. It started as a Python CLI, then added a Go product core, daemon/job API, Electron desktop app, packaging, model downloads, real providers, QA smoke tests, and open source documentation. Once the project reached that scale, it became increasingly obvious to me that I could not treat AI coding as hands-off delegation.</p>

<p>It is more like a collaborator with strong execution ability and broad knowledge, but one that badly needs context and boundaries. If I tell it, “For now, only build the mock; do not connect to the real daemon,” it can execute that well. If I tell it, “This JSON schema and these exit codes must not be broken,” it will try to respect that. But if those constraints exist only in my head, or are scattered in chat history from several days ago, it will eventually forget.</p>

<p>I kept coming back to one sentence:</p>

<blockquote>
  <p>Complexity does not disappear. It only moves somewhere else.</p>
</blockquote>

<p>If I do not control scope during the MVP stage, complexity moves into rework later. If I do not freeze context in documentation, complexity moves into the cost of explaining things in every new conversation. If I do not expose problems during testing and QA, complexity moves to the moment when real users try the product.</p>

<p>That is how I now think about Codex: it is not an automatic system that can cover everything for me. It is more like a very capable teammate. I need to give it context, boundaries, and acceptance criteria, and I also need to know when to pause and rethink.</p>

<h2 id="2-mvp-i-eventually-came-back-to-the-smallest-loop">2. MVP: I Eventually Came Back to the Smallest Loop</h2>

<p>When I say MVP here, I do not mean a formal product concept. I simply mean the smallest version that can run through the core flow.</p>

<p>When starting from zero, I also had the impulse to think: since AI can write code so fast, maybe I can plan the whole system from the beginning. Features, architecture, documentation, tests, UI, all done at once.</p>

<p>Later I found this unrealistic for a personal project. Mature teams can invest heavily in upfront design because they have people, review processes, experience, and relatively stable requirement inputs. But for personal projects, many things only become clear while building. AI can help me discuss options, but it cannot make all product judgments for me.</p>

<p>Fast Sub’s path basically emerged while building:</p>

<ol>
  <li>First, use a Python CLI to run the local subtitle generation and translation flow.</li>
  <li>Then gradually move more stable orchestration, providers, models, and daemon boundaries into Go.</li>
  <li>Then build the Electron UI, using a mock-first approach instead of rushing into the real backend.</li>
  <li>Finally, enter packaging, real smoke tests, QA, and open source cleanup.</li>
</ol>

<p>Looking back, this path was not elegant, but it fit a personal project well. Each stage answered one question: can the core capability run? Does the engineering boundary need to change? What is still missing for real user usage?</p>

<p>But MVP does not mean letting AI start coding immediately. This is a trap I almost fell into myself. Claude Code and Codex are both very eager to start writing code after I describe a requirement. They are almost too proactive. If I am writing a temporary script, that is fine. But for a somewhat serious project, this is exactly where things can start going out of control.</p>

<p>I later started by asking AI to do research first: search similar projects, analyze existing approaches, compare technology stacks, list risks, and generate an initial MVP document. During this process, I would paste in materials I found, and I would also ask another model to review it from a different angle. For example, I might ask it to act like a senior architect and check whether the MVP is overdesigned; or the opposite, ask it to identify places where the plan is too optimistic.</p>

<p>There is nothing magical about the prompt here. At least as of 2026-05-22, my feeling is that instead of worshiping a particular incantation, it is better to clearly explain the requirement, constraints, reference projects, and my own questions. For dependency versions, GitHub Actions, Node configuration, and similar details, I later tended to ask AI to verify them online. Otherwise it can occasionally produce outdated answers, which later turn into strange warnings or build failures.</p>

<p>After the MVP document is ready, I ask AI to act as a project manager and split the project into several rounds. Each round states its goal, scope, and acceptance criteria. Execution then becomes much simpler: refine the round document, create a branch, implement, and finally validate according to the document.</p>

<p>This step looks slow, but it saves time later.</p>

<h2 id="3-spec-and-documentation-i-started-writing-down-the-ambiguous-parts-first">3. SPEC and Documentation: I Started Writing Down the Ambiguous Parts First</h2>

<p>When coding with AI, it is easy to treat the prompt as the requirement document. But in complex projects, prompts are too lightweight.</p>

<p>A prompt is more like a verbal handoff. It can start a task, but it cannot carry long-term constraints. A feature may span multiple files, multiple modules, multiple conversations, or even multiple days. If all decisions live only in chat history, they will almost certainly be lost later.</p>

<p>In the later Electron development of Fast Sub, I basically switched to specs-driven work. Round 11 was the Electron mock-first shell. Round 12 connected the Go daemon. Round 13 focused on productization and release readiness. Each round had a corresponding document that stated what this round would do, what it would not do, which contracts it would affect, and how it would be accepted.</p>

<p>This matters a lot for AI, because AI is very good at “while I am here.” I ask it to fix a UI issue, and it may also adjust state management. I ask it to add a daemon API, and it may also adjust the renderer contract. Often it is not trying to misbehave. It is simply judging from local context that “this is more reasonable.” But in a project, not everything that is locally reasonable can be changed: CLI arguments, JSON schemas, exit codes, provider contracts, and remote upload confirmation flows are examples.</p>

<p>To AI, code is just code. To the project, some code is actually a contract. Once it changes, users, tests, UI, and documentation may all break. This is especially true when working on boundaries like a CLI, daemon API, or provider. I cannot let AI treat “this looks more reasonable” as “it is okay to change public behavior.”</p>

<p>So in a SPEC, what I later cared about most was not only “what to do,” but “what not to do.”</p>

<p>For example, Round 11 explicitly did not connect to the real daemon and did not call real ffmpeg, the Python worker, or provider runtimes. Round 12 was when the real daemon was connected. Round 13 focused on packaging, diagnostics, tests, and release quality, without introducing new core business architecture.</p>

<p>With these boundaries, AI’s freedom actually becomes more stable. It is more likely to understand that this round should only solve this round’s problems. When it finds something out of scope, it records a follow-up instead of immediately changing it.</p>

<p>My current habit is: before a large implementation, write a SPEC, review it myself, then ask another conversation or model to review it. The goal is not to make the document beautiful. The goal is to expose ambiguity before touching code.</p>

<h2 id="4-context-management-i-stopped-relying-only-on-chat-history">4. Context Management: I Stopped Relying Only on Chat History</h2>

<p>The first few rounds of Fast Sub were mostly driven by conversations. At the beginning, this was fine. The project was still small, and AI could keep up. But as development continued, the problems became obvious.</p>

<p>The most typical case was that the project manager conversation forgot its own role and started editing code. Some conversations forgot what had already been completed, or did not know which contracts were untouchable. If I reminded it once, it would return to normal; after a while, it might drift again.</p>

<p>This is not because one specific model is especially bad. LLM conversations are simply not stable memory. Tools like Claude Code and Codex help me maintain context, but context is still limited. Long conversations get compressed, details are lost, and lots of no-longer-important information gets mixed in.</p>

<p>Later I referred to JS Mastery’s <a href="https://www.youtube.com/watch?v=14RP8liACqo">The Six-File Context System</a>, as well as the corresponding <a href="https://jsmastery.com/waitlist/six-file-context">Six-File Context System Guide download page</a>. I did not copy it exactly. Instead, I adapted it into a set of documents that fit Fast Sub at the time. For the UI stage, the most important ones were:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">project-overview.md</code>: project goals, scope, and stage.</li>
  <li><code class="language-plaintext highlighter-rouge">architecture.md</code>: architecture boundaries, module responsibilities, and data flow.</li>
  <li><code class="language-plaintext highlighter-rouge">code-standards.md</code>: code style and implementation conventions.</li>
  <li><code class="language-plaintext highlighter-rouge">ui-context.md</code>: UI tokens, visual rules, and component conventions.</li>
  <li><code class="language-plaintext highlighter-rouge">ai-workflow-rules.md</code>: AI workflow rules.</li>
  <li><code class="language-plaintext highlighter-rouge">project-tracker.md</code>: current progress, decisions, and next steps.</li>
</ul>

<p>These files were not there to make the repository look more “professional.” They were there so a new conversation could pick up the project. Before an implementation conversation starts, I ask it to read these context files. After code changes are done, the tracker is updated. Project memory no longer depends entirely on a single chat window.</p>

<p>The context structure I ended up with looks roughly like this:</p>

<pre><code class="language-mermaid">flowchart TD
  A["AGENTS.md&lt;br/&gt;Global working rules"] --&gt; B["Long-term context documents"]
  B --&gt; B1["project-overview&lt;br/&gt;Goals and scope"]
  B --&gt; B2["architecture&lt;br/&gt;Architecture and boundaries"]
  B --&gt; B3["code-standards / ui-context&lt;br/&gt;Code and UI conventions"]
  B --&gt; B4["ai-workflow-rules&lt;br/&gt;AI collaboration rules"]
  B --&gt; C["Round SPEC&lt;br/&gt;What this stage does / does not do"]
  C --&gt; D["Implementation conversation"]
  D --&gt; E["project-tracker&lt;br/&gt;Results, decisions, next steps"]
  E --&gt; C
</code></pre>

<p>This change helped a lot later. With these files in place, Codex’s plans became noticeably closer to the actual project state, and it forgot constraints less often. The cost was that it spent a bit longer thinking each time, and the answers felt less lightweight. For a complex project, I am willing to pay that cost.</p>

<p>I also realized that context management does not mean stuffing everything into AI. Too little context causes misunderstanding. Too much context slows it down and may make it miss the point. For me, the smoother approach is to put long-term stable information in project documents, and current-stage information in SPECs and the tracker.</p>

<h2 id="5-multi-conversation-collaboration-i-split-the-roles-apart">5. Multi-Conversation Collaboration: I Split the Roles Apart</h2>

<p>I now rarely put a complex project into a single chat window.</p>

<p>The longer a single conversation gets, the more it turns into a stew. Planning, implementation, review, QA, and release documentation all mixed together make it difficult for AI to maintain a stable role. I might ask it to finish an implementation round, then review its own code, then plan the next round. The contexts easily get tangled.</p>

<p>Later, I kept several standing conversations:</p>

<ol>
  <li>Project manager: does not write code; only handles planning, documentation, round breakdown, and prompts for implementation branches.</li>
  <li>Plan: reviews and refines documents, for example by referencing strong projects and filling gaps in round docs.</li>
  <li>Review: performs code review before an implementation branch is merged, identifies issues, and pushes for fixes.</li>
</ol>

<p>Actual implementation happens in a new conversation. The implementation conversation receives only the SPEC and context needed for the current round, without carrying too much historical baggage.</p>

<p>At first this felt slightly cumbersome, but later it became much more comfortable. The PM conversation acts more like a project state machine, responsible for clarifying work. The implementation conversation is more like a temporary worker that takes a clear task and executes. The review conversation focuses on finding problems. Handoffs between conversations do not rely on “remember this?” but on documents and the tracker.</p>

<p>For example: the PM conversation first generates the Round 12 daemon integration spec; the Plan conversation reviews it, focusing on daemon API, secrets, SSE, and configuration write boundaries; the implementation conversation connects the real daemon client according to the spec; the Review conversation checks contracts, privacy, and test gaps; finally, the QA conversation organizes issues found during real desktop testing and writes results back to the tracker. Each conversation carries one type of cognitive load.</p>

<p>Drawn as a loop, it looks roughly like this:</p>

<pre><code class="language-mermaid">flowchart LR
  A["PM conversation&lt;br/&gt;Split rounds / write initial spec"] --&gt; B["Plan conversation&lt;br/&gt;Review spec / fill boundaries"]
  B --&gt; C["Implementation conversation&lt;br/&gt;Change code according to spec"]
  C --&gt; D["Review conversation&lt;br/&gt;Check contracts / tests / privacy"]
  D --&gt; E["QA conversation&lt;br/&gt;Organize real issues"]
  E --&gt; F["Tracker&lt;br/&gt;Persist state and next steps"]
  F --&gt; A
</code></pre>

<p>Some rounds can be split further into multiple worktrees and developed in parallel. I once tried up to 4 worktrees at the same time, and the efficiency was genuinely high. But there is one precondition: the tasks need to be cut cleanly enough. If several branches all modify the same state management or the same contract, merging becomes painful.</p>

<p>My workflow is basically to keep the main branch clean, rebase feature branches onto the primary branch (<code class="language-plaintext highlighter-rouge">main</code> / <code class="language-plaintext highlighter-rouge">master</code>), then merge with fast-forward. This makes review and rollback clearer. This may not fit everyone, but for a personal project, it was easier to control than having many branches merge into each other.</p>

<p>If each conversation is also given some dedicated skills, it starts to feel a bit like agents. But I do not want to make it sound mystical. For code development, simply making different conversations responsible for different roles already solves many problems.</p>

<h2 id="6-code-quality-and-refactoring-tests-became-how-i-decided-whether-it-was-safe-to-continue">6. Code Quality and Refactoring: Tests Became How I Decided Whether It Was Safe to Continue</h2>

<p>After using Codex, it became hard for me to review every generated line one by one. Not because I did not want to, but because it was not realistic. AI generates code too fast. Once the project becomes complex, human line-by-line review quickly falls behind.</p>

<p>My feeling later was that tests cannot be decorative. They have to participate in deciding whether a change broke something.</p>

<p>In Fast Sub, many things cannot be changed casually: CLI command names, arguments, JSON schemas, exit codes, provider contracts, daemon APIs, secret redaction, and remote upload confirmation. Verbal reminders are not enough. I need to write these into documentation and cover them by tests as much as possible.</p>

<p>The validation I ran differed by stage. On the Python side, there were ruff, mypy, and pytest. On the Go side, there was go test. On the Electron side, there were typecheck, unit tests, build, and smoke tests. By Round 13, I also needed packaged smoke, installer smoke, real local provider/file smoke, long-task cancellation smoke, screenshot baselines, and license inventory.</p>

<p>But automated tests are not everything. I still need to manually run the core flow. This is especially true for desktop apps. Many problems only appear when I actually click through the app: whether a button is clickable, whether a long filename breaks a dialog, whether error messages are understandable, whether task state jumps after switching away and back, whether GPU processes remain after canceling a job.</p>

<p>I also hit issues with refactoring.</p>

<p>After the Python part was done, the project already had more than ten Python files, and some files were thousands of lines long. That was when I realized I was paying debt for not defining the project architecture and code style earlier. If there had been clearer context files and code boundaries from the beginning, the later pain might have been smaller.</p>

<p>At first I wanted Codex to refactor everything in one shot based on a reasonable architecture diagram. The result was not ideal. It could produce a directory structure that looked good, but at the concrete file level it often used shims to route around the problem, and the code was not really moved much.</p>

<p>In the end, I had to point out issues one directory at a time and let Codex make smaller changes. Fortunately, the test coverage was good enough that after each change I could quickly verify whether behavior had been broken.</p>

<p>My view on AI refactoring has become more conservative since then: it is very good at splitting files, extracting types, and organizing modules, but only if I first define what “not broken” means. Without tests, boundaries, and a small-step rhythm, refactoring can easily become another disaster.</p>

<h2 id="7-ui-prototyping-mock-first-was-one-of-the-better-choices">7. UI Prototyping: Mock-First Was One of the Better Choices</h2>

<p>After the Python and Go parts were completed, UI was what worried me most, because I had almost no experience building a complete desktop UI.</p>

<p>The first time I used Claude Design to generate prototypes, I was honestly stunned. I only gave it a rough page description and the <code class="language-plaintext highlighter-rouge">daemon-api.md</code> iterated earlier, and it produced several visual styles plus prototypes for more than a dozen major pages. It felt like I was still describing requirements with stone-age tools, while it had already placed an entire interface world in front of me.</p>

<p>But the shock soon turned into another practical problem: this kind of visual iteration consumes a lot of quota and context. After changing only a few pages, the account quota would already start to feel tight.</p>

<p>Later I downloaded the prototype files and let Codex continue modifying them. One detail I only realized later: prototype code alone is not enough. If Codex only sees the code, it is hard for it to reliably reproduce the visual effect. I later provided screenshots of each prototype page as well, so it could reference both structure and final appearance.</p>

<p>Looking back, there may already be easier ways to do this now. For example, <a href="https://opendesigner.io/">Open Design</a> can basically be understood as an open source alternative to Claude Design. It connects the design-generation workflow to existing coding-agent CLIs, including Codex, Claude Code, Cursor, Gemini, OpenCode, and others. If I had used something like that at the time, I might not have needed to move prototype files, screenshots, and revision notes back and forth between Claude Design and Codex.</p>

<p>Once the prototype was roughly ready, I did not connect the real backend immediately. I went mock-first.</p>

<p>Fast Sub’s Round 11 was the Electron mock-first shell: establish the <code class="language-plaintext highlighter-rouge">FastSubClient</code> contract, Mock client, first launch flow, main screen, job queue, and settings page. This stage did not connect to the real daemon, and did not call real ffmpeg, Python workers, or model downloads.</p>

<p>This later proved very worthwhile. The UI could validate information architecture and state flow first, without being blocked by backend readiness. When Round 12 connected the real daemon, the client contract already existed, and the pages did not need to be rebuilt.</p>

<p>If the UI had connected to the real daemon from the beginning, problems would have been mixed together: if a button did not respond, was it a UI state bug, a daemon API bug, or a job event mapping bug? Mock-first removed at least half of that uncertainty.</p>

<p>So if I build a similar project again, I will probably still mock first. Even if part of the backend is already available, I would rather smooth out the user flow with mocks first, then gradually replace them with real implementation.</p>

<h2 id="8-qa-and-open-source-cleanup-the-last-mile-takes-the-most-time">8. QA and Open Source Cleanup: The Last Mile Takes the Most Time</h2>

<p>After connecting the real daemon worker, the project entered the stage where I spent the most time.</p>

<p>When building command-line tools, I had always wondered: why does wrapping a CLI in a UI shell noticeably lower the barrier to use? AI is so convenient now, so why are many tools still stuck at the command line?</p>

<p>After actually building a desktop version myself, I understood. The last mile of UI is extremely fragmented, and many problems are hard to catch ahead of time with automated tests.</p>

<p>For example: is the button where users expect it to be? Will very long batch filenames break the confirmation dialog? Is the error message understandable after a task fails? Does the state jump when a generating task goes to the background and comes back? Are GPU processes left behind after canceling a long task? These are not problems I can confidently solve with one unit test.</p>

<p>I spent more than a week on this part. Honestly, it was quite draining.</p>

<p>The thing that improved efficiency a bit was building a QA test table. I stopped opening a new conversation and fixing one issue immediately every time I found a problem. Instead, I recorded issues in batches, classified them in batches, and then let Codex handle them by category. This is much more efficient, and it also makes it easier to confirm which issues have been fixed and which still need retesting.</p>

<p>Late-stage Fast Sub QA covered many scenarios that only fail in real usage:</p>

<ul>
  <li>Installer and portable zip.</li>
  <li>First launch and default model download.</li>
  <li>Chinese, Japanese, Korean, and paths with spaces.</li>
  <li>Local Faster Whisper, whisper.cpp, and NLLB.</li>
  <li>Long-task cancellation and process cleanup.</li>
  <li>API key save, replace, and delete.</li>
  <li>Privacy redaction.</li>
  <li>Diagnostics page and screenshot baselines.</li>
</ul>

<p>There is another point I only truly understood later: the packaged app is the real product. Dev mode running successfully only proves that it runs in the development environment. In a packaged app, many previously invisible issues appear: app-private Python runtime, daemon cwd, resource paths, Windows installer, portable zip, child processes left after exit, macOS signing and permissions. Fast Sub Round 13 spent a lot of time on these things, and looking back, it was worth it, because they determine whether a user can actually use the app after downloading it.</p>

<p>Before open sourcing, there was another category of cleanup that did not look like coding, but was not small at all: README, CHANGELOG, CONTRIBUTING, SECURITY, LICENSE, third-party dependency license inventory, privacy notes, installation instructions, and troubleshooting documentation.</p>

<p>I also had to clean up things that cannot be public, such as API keys, local machine paths, large model files, large media files, real benchmark output, and temporary build artifacts.</p>

<p>I underestimated this step at first. For developers, working code can feel like the end. But for external users, documentation, privacy notes, installation instructions, and known limitations are all part of the project’s credibility. AI is good at generating a first draft of documentation, but I still have to review the promises in those docs myself. Installation instructions, privacy notes, and known limitations in particular cannot be allowed to sound too optimistic.</p>

<p>Fast Sub is also local-first, so the privacy boundary has to be explicit. Remote providers must not become implicit behavior. I want any path that uploads audio or subtitle text to be explicitly selected and confirmed by the user.</p>

<h2 id="9-pitfalls-i-hit-complexity-does-not-disappear-it-only-moves">9. Pitfalls I Hit: Complexity Does Not Disappear, It Only Moves</h2>

<p>If I compress this experience into a few pitfalls, they would be these.</p>

<p>At first, I trusted AI too much to “write and organize as it goes.” The result was that the Python code later had large files and mixed responsibilities, and I had to spend dedicated time refactoring.</p>

<p>Early context depended too much on chat windows. Once a conversation became long, AI easily forgot constraints and sometimes even started doing things it should not do.</p>

<p>At some stages I asked AI to do too much at once. When the scope became large, bugs and drift stacked together, making later debugging exhausting.</p>

<p>The early UI prototypes were not detailed enough. Many interaction issues only appeared during real QA.</p>

<p>I also underestimated the difference between packaged apps and real environments. Running in dev mode does not mean running after packaging. System Python, app-private Python, daemon cwd, process cleanup, and path permissions are only truly exposed after packaging.</p>

<p>Behind all these pitfalls is the same issue: complexity does not disappear, it only moves.</p>

<p>AI can help me write code faster, but it cannot make complexity vanish. Problems I do not handle early will still appear later during implementation, QA, or real usage.</p>

<h2 id="10-if-i-did-it-again-what-i-would-do-earlier">10. If I Did It Again, What I Would Do Earlier</h2>

<p>If I were to build Fast Sub again from scratch now, I would not overturn the overall path, but there are several things I would do earlier.</p>

<p>First, create context files from the beginning. I would not wait until conversations start forgetting things and code starts swelling. Even rough <code class="language-plaintext highlighter-rouge">project-overview</code>, <code class="language-plaintext highlighter-rouge">architecture</code>, <code class="language-plaintext highlighter-rouge">code-standards</code>, and <code class="language-plaintext highlighter-rouge">project-tracker</code> files are better than relying entirely on chat history.</p>

<p>Second, define directory structure and code style earlier. Refactoring after early Python files grew large cost more than I expected. AI can help me refactor, but it is not good at absorbing the consequences of “we did not define boundaries earlier” on my behalf.</p>

<p>Third, during the UI prototype stage, organize screenshots, states, and copy more carefully. The rougher the prototype, the more interaction detail I need to patch during QA. Especially for desktop tools, I would try to think through empty states, error states, batch jobs, cancellation, and failed retry flows during the mock stage.</p>

<p>Fourth, establish a QA table earlier. I do not want to wait until last-mile issues explode before systematically recording them. A QA table is not only a test checklist; it is also an input format for collaborating with AI on bug fixes.</p>

<p>Fifth, separate feature work, refactoring, and release work more strictly in every round. AI easily mixes them together, but their acceptance criteria are completely different. For features, I check whether user capability increased. For refactoring, I check whether behavior stayed the same. For release work, I check whether the real environment runs successfully.</p>

<p>None of these are flashy tricks, but if I had done them earlier, I probably would have carried less debt.</p>

<h2 id="11-conclusion-after-this-i-trust-magic-prompts-less">11. Conclusion: After This, I Trust Magic Prompts Less</h2>

<p>After building this project with Codex, my view of AI coding changed quite a bit.</p>

<p>I no longer think the key is finding a universal prompt. Prompts are useful, skills are useful, and tools will keep getting stronger. But after this project, I feel more clearly that the parts that really consume energy in complex projects are scope control, context, validation, and closing things out.</p>

<p>If I were to turn this experience into a note for myself, I would write down these things:</p>

<ol>
  <li>I would build an MVP first, not plan the whole system up front.</li>
  <li>Before large tasks, I would write a SPEC, especially making clear what this round will not do.</li>
  <li>I would put long-term context in project documents, not only in chat history.</li>
  <li>I would continue separating PM, implementation, review, and QA into different conversations.</li>
  <li>After each round of changes, I would look at tests and validation results, not only trust AI saying “done.”</li>
  <li>I would still use mock-first for UI, then connect the real backend after the flow is stable.</li>
  <li>I would record QA issues in batches, fix them in batches, and retest them in batches.</li>
  <li>Before open sourcing, I would leave separate time for documentation, privacy, licenses, and release notes.</li>
</ol>

<p>None of this sounds cool, and it is less attractive than a single magical prompt. But these are the things that stayed with me after this project.</p>

<p>AI coding is not something I can hand off and forget. It is more like bringing a very capable collaborator into the development process. The clearer the process is, the more it amplifies human ability. The messier the process is, the faster it amplifies the mess.</p>

<p>That is probably what I most want to tell myself from one month ago after finishing this project.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Introduction: Written for Myself One Month Ago]]></summary></entry><entry xml:lang="zh"><title type="html">用 Codex 做完一个中型项目后，我的一些复盘</title><link href="https://ryviuszero.github.io/zh/posts/codex-complex-project-development/" rel="alternate" type="text/html" title="用 Codex 做完一个中型项目后，我的一些复盘" /><published>2026-05-21T16:00:00+00:00</published><updated>2026-05-21T16:00:00+00:00</updated><id>https://ryviuszero.github.io/zh/posts/codex-complex-project-development</id><content type="html" xml:base="https://ryviuszero.github.io/zh/posts/codex-complex-project-development/"><![CDATA[<h2 id="目录">目录</h2>

<ul>
  <li><a href="#intro">开头：写给一个月前的自己</a></li>
  <li><a href="#ai-programming-view">1. 做完这个项目后，我对 AI 编程的看法变了</a></li>
  <li><a href="#mvp-loop">2. MVP：我后来还是先回到了最小闭环</a></li>
  <li><a href="#specs-and-docs">3. SPEC 和文档：我开始把模糊的地方先写下来</a></li>
  <li><a href="#context-management">4. 上下文管理：后来我不再只依赖聊天记录</a></li>
  <li><a href="#multi-conversation-workflow">5. 多对话协作：我把不同角色拆开了</a></li>
  <li><a href="#quality-and-refactoring">6. 代码质量和重构：测试成了我判断能不能继续改的依据</a></li>
  <li><a href="#mock-first-ui">7. UI 原型：mock-first 是这次比较值的一步</a></li>
  <li><a href="#qa-and-release-readiness">8. QA 和开源前收口：最后一公里最花时间</a></li>
  <li><a href="#lessons-learned">9. 我踩过的坑：复杂度不会消失，只会转移</a></li>
  <li><a href="#what-i-would-do-earlier">10. 如果重来一次，我会提前做什么</a></li>
  <li><a href="#conclusion">11. 总结：这次之后，我没那么迷信 prompt 了</a></li>
</ul>

<h2 id="intro">开头：写给一个月前的自己</h2>

<p>Fast Sub 做到现在这个阶段后，我发现自己终于能回答一个月前一直想问的问题了：如果真的想用 Codex、Claude Code 这类工具做一个完整项目，到底应该怎么做？</p>

<p>我当时很想找一篇真正从 0 开始，用 AI 编程工具把项目做到能发布的文章或者视频。不是那种十分钟 demo，也不是“输入一句话生成一个 App”的展示，而是一个稍微复杂一点的真实项目：会有需求变化、架构取舍、重构、测试、UI、打包、QA、开源前整理。找了一圈之后，能参考的东西不多。</p>

<p>先交代一下 Fast Sub 的体量，方便理解后面为什么我一直在讲上下文、测试和 QA。它最后不是一个单纯脚本，而是包含 Python CLI、Go product core、Go daemon/job API、Electron 桌面 UI、本地 worker、模型下载、provider、打包、发布检查和开源文档的一整套项目。</p>

<p>下面这段是我让 Codex 根据 git 历史给出的复杂度估算：</p>

<blockquote>
  <p>按 git 历史看，从 first commit 到当前状态，时间跨度是 2026-04-24 到 2026-05-22，约 28 天自然日；如果按包含首尾日期说，就是 29 天内。真正有提交的活跃开发日大概是 13 天，总提交数 71 个，最新提交是 <code class="language-plaintext highlighter-rouge">2026-05-22 Stabilize desktop readiness tests</code>。更准确地说，它是一个约 4 周自然时间、集中开发大概 2 周左右活跃工作日推进出来的 MVP Demo。按人类小队估算，当前产出更像是 2-3 人高强度干 3-5 周，或者一个 solo founder 加 AI agent 在约 1 个月里推出来的结果。</p>
</blockquote>

<p>项目演进大概可以压成这条线：</p>

<pre><code class="language-mermaid">flowchart LR
  A["Python CLI&lt;br/&gt;跑通核心字幕流程"] --&gt; B["Go core / daemon&lt;br/&gt;稳定产品边界"]
  B --&gt; C["Electron mock-first&lt;br/&gt;先跑通 UI 状态流"]
  C --&gt; D["Daemon integration&lt;br/&gt;接入真实本机能力"]
  D --&gt; E["Release readiness&lt;br/&gt;打包、QA、开源前收口"]
</code></pre>

<p>现在自己做完一遍，大概也知道为什么了。因为这种体量的经验，很难被压缩成一个几千字的“教程”。很多文章最后都会变成提纯过的方法论，看起来很正确，但我真正开项目时，还是会卡在第一步该怎么做、什么时候该停下来、哪些地方要自己判断这些问题上。</p>

<p>所以这篇文章更像是给一个月前的我写的备忘录。它不是要证明 Codex 多强，也不是要说 AI 可以替我完成所有开发。只是做完这一轮后，我对“怎么和 AI 一起推进一个项目”有了一些更具体的感受。</p>

<p>我自己也还在摸索，下面的内容肯定不是标准答案。更准确地说，它只是我用 Fast Sub 这个项目踩出来的一套阶段性经验。</p>

<h2 id="ai-programming-view">1. 做完这个项目后，我对 AI 编程的看法变了</h2>

<p>刚开始用 AI 写代码的时候，很容易有一种错觉：只要把需求说清楚，后面就可以交给它自己推进。</p>

<p>写临时脚本时，这个感觉确实成立。我说要处理一个文件、调一个 API、生成一段测试数据，它写出来，我跑一下，修几个小问题，事情就结束了。</p>

<p>但 Fast Sub 不是这种项目。它从 Python CLI 开始，后来加了 Go product core、daemon/job API，再到 Electron 桌面端、打包、模型下载、真实 provider、QA smoke 和开源文档。项目一旦到这个规模，我越来越明显地感觉到，不能把 AI 编程当成放手托管。</p>

<p>它更像一个执行力很强、知识面很广、但非常需要上下文和边界的协作者。我告诉它“现在只做 mock，不要接真实 daemon”，它可以很好地执行。我告诉它“这个 JSON schema 和退出码不能破坏”，它也能尽量遵守。但如果这些约束只存在我脑子里，或者散落在前几天的聊天记录里，那它迟早会忘。</p>

<p>我后来反复想到一句话：</p>

<blockquote>
  <p>复杂度不会消失，只会转移。</p>
</blockquote>

<p>我不在 MVP 阶段控制范围，复杂度就会转移到后面的返工里。我不在文档里固定上下文，复杂度就会转移到每个新对话的解释成本里。我不在测试和 QA 阶段暴露问题，复杂度就会转移到用户真正使用的时候。</p>

<p>这也是我现在看 Codex 的方式：它不是一个可以替我兜底的全自动系统，更像是一个很强的队友。我得给它上下文、边界、验收标准，也得知道什么时候该暂停下来重新判断。</p>

<h2 id="mvp-loop">2. MVP：我后来还是先回到了最小闭环</h2>

<p>我这里说的 MVP，不是一个很正式的产品概念，只是第一个能跑通核心流程的最小版本。</p>

<p>从零做项目时，我一开始也会有冲动：既然 AI 写代码这么快，那是不是可以一上来就把完整系统规划好？功能、架构、文档、测试、UI，全都一次性做出来。</p>

<p>后来发现，这个想法对个人项目不太现实。成熟团队可以在前期做大量设计，因为他们有人力、有评审、有经验，也有比较稳定的需求输入。但个人项目很多时候是在做的过程中才逐渐想清楚。AI 可以帮我讨论方案，但不能替我做所有产品判断。</p>

<p>Fast Sub 的路线基本是边做边收敛出来的：</p>

<ol>
  <li>先用 Python CLI 把本地字幕生成和翻译主流程跑通。</li>
  <li>再把更稳定的业务编排、provider、模型和 daemon 边界逐步迁到 Go。</li>
  <li>然后做 Electron UI，而且先走 mock-first，不急着接真实后端。</li>
  <li>最后才进入打包、真实 smoke、QA、开源前整理。</li>
</ol>

<p>现在回头看，这条路线不算优雅，但它很适合个人项目。每一阶段都能回答一个问题：核心能力能不能跑？工程边界要不要调整？真实用户使用时还缺什么？</p>

<p>不过 MVP 不等于让 AI 直接开写。这个坑我自己也差点踩进去。Claude Code、Codex 都很容易在我提完需求后立刻开始写代码，它们太积极了。如果只是写一个临时脚本，这没问题。但如果是一个稍微严肃一点的项目，从这里开始就可能失控。</p>

<p>我后来会先让 AI 做调研：搜索类似项目，分析现有方案，比较技术栈，列风险，再生成一份初版 MVP 文档。这个过程里，我会把自己看到的资料贴进去，也会让另一个模型从不同角度审一遍。比如让它假设自己是资深架构师，看看这个 MVP 有没有过度设计；或者反过来，让它挑一下哪些地方太乐观。</p>

<p>这里 prompt 没有多神秘。至少到 2026-05-22 这个阶段，我的感受是，与其迷信某句咒语，不如把需求、约束、参考项目和自己的疑问讲清楚。有些依赖版本、GitHub Actions、Node 配置之类的信息，我后来也会让 AI 联网确认一下。不然它偶尔会给出过时答案，后面就会变成莫名其妙的 warning 或构建失败。</p>

<p>MVP 文档出来后，我会继续让 AI 扮演 project manager，把项目拆成几轮 round。每轮写清楚目标、范围和验收方式。后面执行时就简单很多：先完善 round 文档，再切分支实现，最后按文档验收。</p>

<p>这一步看起来慢，但它是在帮后面省时间。</p>

<h2 id="specs-and-docs">3. SPEC 和文档：我开始把模糊的地方先写下来</h2>

<p>用 AI 编程时，很容易把 prompt 当成需求文档。但复杂项目里，prompt 太轻了。</p>

<p>prompt 更像一次口头交代，可以启动任务，但承载不了长期约束。一个功能可能会跨多个文件、多个模块、多个对话，甚至跨几天时间。如果所有决策都只在聊天记录里，后面基本一定会丢。</p>

<p>Fast Sub 后期的 Electron 开发，我基本改成 specs-driven。Round 11 做 Electron mock-first shell，Round 12 接 Go daemon，Round 13 做产品化和发布准备。每一轮都有对应文档，里面写清楚这一轮做什么、不做什么、会影响哪些 contract、完成后怎么验收。</p>

<p>这对 AI 特别重要。因为 AI 很擅长“顺便”。我让它修一个 UI 问题，它可能顺便改了状态管理。我让它补一个 daemon API，它可能顺便调整了 renderer contract。很多时候它不是故意乱来，只是它会根据局部上下文判断“这样更合理”。但项目里有些东西不是局部合理就能改的，比如 CLI 参数、JSON schema、退出码、provider contract、远程上传确认逻辑。</p>

<p>对 AI 来说，代码只是代码；但对项目来说，有些代码其实是 contract。它们一旦变了，用户、测试、UI、文档都会跟着坏。尤其是做 CLI、daemon API、provider 这类边界时，不能让 AI 把“看起来更合理”当成“可以改公开行为”。</p>

<p>所以 SPEC 里我后来最看重的，其实不是“要做什么”，而是“不能做什么”。</p>

<p>比如 Round 11 明确不接真实 daemon，不调用真实 ffmpeg、Python worker 或 provider runtime。Round 12 才接真实 daemon。Round 13 聚焦打包、诊断、测试和发布质量，不再引入新的核心业务架构。</p>

<p>有了这些边界，AI 的发挥空间反而更稳定。它更容易知道这一轮只该解决这一轮的问题，遇到超出范围的东西就记录 follow-up，而不是直接动手。</p>

<p>我现在的习惯是：大的实现前先写 SPEC，自己审一遍，再让另一个对话或模型审一遍。不是为了写得多漂亮，而是为了在动代码前先把模糊的地方暴露出来。</p>

<h2 id="context-management">4. 上下文管理：后来我不再只依赖聊天记录</h2>

<p>Fast Sub 前几轮基本是靠对话推进的。一开始没什么问题，项目还小，AI 也能跟得上。但后面继续做时，问题开始变明显。</p>

<p>最典型的情况是 project manager 对话忘记了自己的角色，开始改代码。还有些对话会忘记之前完成了什么，或者不知道哪些 contract 是不能动的。我提醒它一次，它会恢复正常；过一段时间，又可能偏掉。</p>

<p>这不是某个模型特别差，而是 LLM 对话本来就不是稳定记忆。Claude Code、Codex 这些工具会帮我维护上下文，但上下文终究有限。对话长了会压缩，会丢细节，也会混入很多已经不重要的信息。</p>

<p>后来我参考了 JS Mastery 的 <a href="https://www.youtube.com/watch?v=14RP8liACqo">The Six-File Context System</a> 这套做法，也看了它对应的 <a href="https://jsmastery.com/waitlist/six-file-context">Six-File Context System Guide 下载页</a>。我没有完全照搬，而是按 Fast Sub 当时的状态改成了一组更适合自己项目的文档。以 UI 阶段为例，比较核心的是这几个：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">project-overview.md</code>：项目目标、范围和阶段。</li>
  <li><code class="language-plaintext highlighter-rouge">architecture.md</code>：架构边界、模块职责和数据流。</li>
  <li><code class="language-plaintext highlighter-rouge">code-standards.md</code>：代码风格和实现约定。</li>
  <li><code class="language-plaintext highlighter-rouge">ui-context.md</code>：UI token、视觉规则和组件约定。</li>
  <li><code class="language-plaintext highlighter-rouge">ai-workflow-rules.md</code>：AI 工作流规则。</li>
  <li><code class="language-plaintext highlighter-rouge">project-tracker.md</code>：当前进度、决策和下一步。</li>
</ul>

<p>这些文件不是为了让仓库看起来更“工程化”，而是为了让新对话能接得上。实现对话开始前，我会让它先读这些上下文。改完代码后，再更新 tracker。这样项目记忆就不再完全依赖某一个聊天窗口。</p>

<p>我后来理解的上下文结构，大概是这样：</p>

<pre><code class="language-mermaid">flowchart TD
  A["AGENTS.md&lt;br/&gt;全局工作规则"] --&gt; B["长期上下文文档"]
  B --&gt; B1["project-overview&lt;br/&gt;目标和范围"]
  B --&gt; B2["architecture&lt;br/&gt;架构和边界"]
  B --&gt; B3["code-standards / ui-context&lt;br/&gt;代码和 UI 约定"]
  B --&gt; B4["ai-workflow-rules&lt;br/&gt;AI 协作规则"]
  B --&gt; C["Round SPEC&lt;br/&gt;当前阶段要做什么 / 不做什么"]
  C --&gt; D["实现对话"]
  D --&gt; E["project-tracker&lt;br/&gt;结果、决策、下一步"]
  E --&gt; C
</code></pre>

<p>这个改变对后续帮助很大。有了这些文件后，Codex 给出的方案明显更贴近项目现状，也更少出现忘记约束的情况。代价是它每次思考会更久一点，回答也没那么轻快。但对复杂项目来说，我愿意付这个成本。</p>

<p>我后来也意识到，上下文管理不是把所有东西都塞给 AI。上下文太少，它会误解；上下文太多，它会变慢，也可能抓不住重点。所以对我来说更顺的方式，是把长期稳定的信息写进项目文档，把当前阶段的信息写进 SPEC 和 tracker。</p>

<h2 id="multi-conversation-workflow">5. 多对话协作：我把不同角色拆开了</h2>

<p>我现在不太会把一个复杂项目塞进一个聊天窗口里。</p>

<p>单个对话越长，越容易变成一锅粥。规划、实现、review、QA、发布文档全混在一起后，AI 很难稳定保持同一个角色。我刚让它做完一轮实现，又让它审自己的代码，再让它规划下一轮，它很容易把上下文搅在一起。</p>

<p>我后面比较固定地保留了几个常驻对话：</p>

<ol>
  <li>Project manager：不写代码，只负责规划、文档、round 拆分，以及生成实现分支需要的 prompt。</li>
  <li>Plan：审阅和细化文档，比如让它参考优秀项目，补充 round 文档里的缺口。</li>
  <li>Review：在实现分支合并前做审查，发现问题后推动修复。</li>
</ol>

<p>具体实现则重新开对话。实现对话只拿本轮需要的 SPEC 和上下文，不背太多历史包袱。</p>

<p>这个做法一开始看起来有点麻烦，但后面确实舒服很多。PM 对话更像项目状态机，负责把事情拆清楚；实现对话更像临时工，拿到明确任务就做；review 对话则专门挑问题。不同对话之间不靠“还记得吗”，而靠文档和 tracker 交接。</p>

<p>举个具体一点的流程：PM 对话先生成 Round 12 的 daemon integration spec；Plan 对话审一遍，重点看 daemon API、secret、SSE、配置写入这些边界有没有缺口；实现对话按 spec 接入真实 daemon client；Review 对话再检查 contract、隐私、测试遗漏；最后 QA 对话整理真实桌面测试里发现的问题，再把结果回写到 tracker。这样每个对话都只承担一类认知负担。</p>

<p>如果画成图，大概是这个循环：</p>

<pre><code class="language-mermaid">flowchart LR
  A["PM 对话&lt;br/&gt;拆 round / 写初版 spec"] --&gt; B["Plan 对话&lt;br/&gt;审 spec / 补边界"]
  B --&gt; C["实现对话&lt;br/&gt;按 spec 改代码"]
  C --&gt; D["Review 对话&lt;br/&gt;查 contract / 测试 / 隐私"]
  D --&gt; E["QA 对话&lt;br/&gt;整理真实问题"]
  E --&gt; F["Tracker&lt;br/&gt;沉淀状态和下一步"]
  F --&gt; A
</code></pre>

<p>有些 round 还能进一步拆成多个 worktree 并行做。我最多试过 4 个 worktree 同时开发，效率确实很高。但这里有一个前提：任务要切得足够干净。最后如果几个分支都改同一块状态管理、同一个 contract，合并时就会很痛苦。</p>

<p>我的工作流基本是主分支保持干净，功能分支 rebase 到主分支（<code class="language-plaintext highlighter-rouge">main</code> / <code class="language-plaintext highlighter-rouge">master</code>）后 fast-forward 合并。这样 review 和回滚都比较清楚。这个做法不一定适合所有人，但至少对我这种个人项目来说，比一堆分支互相 merge 要更容易控制。</p>

<p>每个对话再加上一些专属 skills，其实就有点像 agents 了。不过我现在还不想把它说得太玄。对代码开发来说，先做到“不同对话承担不同职责”，已经能解决很多问题。</p>

<h2 id="quality-and-refactoring">6. 代码质量和重构：测试成了我判断能不能继续改的依据</h2>

<p>用 Codex 之后，我很难再逐行 review 每一行代码。不是不想，而是不现实。AI 生成代码的速度太快，项目一复杂，靠人工一行行看，很快就会跟不上。</p>

<p>后来我的感受是，测试不能只是“有就行”的装饰，它得真的参与判断这次修改有没有把东西弄坏。</p>

<p>Fast Sub 里有很多东西不能被随便改：CLI 命令名、参数、JSON schema、退出码、provider contract、daemon API、secret redaction、远程上传确认。这些东西只靠口头提醒不够，要写进文档，也要尽量有测试覆盖。</p>

<p>不同阶段我跑的验证不一样。Python 侧有 ruff、mypy、pytest。Go 侧有 go test。Electron 侧有 typecheck、unit test、build、smoke。到了 Round 13，还要做 packaged smoke、installer smoke、真实本地 provider/file smoke、长任务取消 smoke、截图 baseline、license inventory。</p>

<p>但自动测试也不是万能的。核心流程我还是会自己手动跑一遍。尤其是桌面应用，很多问题只有真实点一遍才知道：按钮是不是能点，弹窗会不会被长文件名撑爆，失败信息用户能不能看懂，任务取消后进程有没有残留。</p>

<p>重构这块我也踩过坑。</p>

<p>Python 部分做完后，项目里已经有十几个 Python 文件，有些文件甚至几千行。那一刻我才意识到，前期没有规定好项目架构和代码风格，是会还债的。如果一开始就有更明确的 context 文件和代码边界，后面可能不会这么痛。</p>

<p>我一开始想让 Codex 直接根据合理架构图一步到位重构。结果并不理想。它能给出看起来不错的目录结构，但到了具体文件级别，经常用 shim 的方式把问题绕过去，代码其实没怎么挪。</p>

<p>最后我只能一个目录一个目录地指出问题，让 Codex 小范围改。好在测试比较齐全，每次改完都能很快验证有没有破坏行为。</p>

<p>后来我对 AI 重构的判断变得保守了：它很适合拆文件、抽类型、整理模块，但前提是我先定义什么叫“没改坏”。如果没有测试，没有边界，没有“小步改”的节奏，重构很容易变成另一场灾难。</p>

<h2 id="mock-first-ui">7. UI 原型：mock-first 是这次比较值的一步</h2>

<p>Python 和 Go 这部分完成后，我最担心的是 UI。因为我几乎没有完整做过桌面 UI。</p>

<p>结果第一次用 Claude Design 生成原型时，我是真的被震住了。我只给了很粗糙的页面描述和前面迭代出来的 <code class="language-plaintext highlighter-rouge">daemon-api.md</code>，它就给出了几种风格，以及十几个主要页面的原型图。那种感觉有点像：我还在用石器时代的方式描述需求，它已经把一整个界面世界摆在我面前了。</p>

<p>不过震惊很快变成了另一个现实问题：这种视觉迭代非常吃额度，也非常吃上下文。基本每改几个页面，账号额度就开始紧张。</p>

<p>后来我把原型文件下载下来，让 Codex 继续接手修改。这里有一个细节我后来才意识到：只给原型代码不够。Codex 只看代码，很难稳定还原视觉效果。我后来会同时给每个原型页面的截图，让它同时参考结构和最终视觉。</p>

<p>现在回头看，这一步现在可能已经有更省事的做法了。比如 <a href="https://opendesigner.io/">Open Design</a>，基本可以理解成 Claude Design 的开源替代方案。它把设计生成流程接到现有的 coding-agent CLI 上，包括 Codex、Claude Code、Cursor、Gemini、OpenCode 等。如果当时已经用上这类工具，我可能就不用在 Claude Design 和 Codex 之间来回搬原型文件、截图和修改说明。</p>

<p>原型差不多后，我没有直接接真实后端，而是先做 mock-first。</p>

<p>Fast Sub 的 Round 11 就是 Electron mock-first shell：先建立 <code class="language-plaintext highlighter-rouge">FastSubClient</code> contract、Mock client、首次启动、主界面、任务队列、设置页。这个阶段不接真实 daemon，不调用真实 ffmpeg、Python worker 或模型下载。</p>

<p>这一步后来证明很值。UI 可以先把信息架构和状态流跑通，后端没完成时也不会阻塞。等到 Round 12 接真实 daemon 时，前面已经有了 client contract，页面也不需要推倒重来。</p>

<p>如果一开始 UI 就接真实 daemon，问题会混在一起：这个按钮没反应，到底是 UI 状态错了，还是 daemon API 错了，还是 job event 映射错了？mock-first 至少能先把一半不确定性拿掉。</p>

<p>所以现在让我再做类似项目，我大概率还是会先 mock。哪怕后端已经有一部分可用，我也更愿意先用 mock 把用户流程走顺，再逐步替换成真实实现。</p>

<h2 id="qa-and-release-readiness">8. QA 和开源前收口：最后一公里最花时间</h2>

<p>接入真实 daemon worker 后，项目进入了我花时间最多的阶段。</p>

<p>做命令行工具时，我一直有点好奇：为什么只是给 CLI 套一个 UI 壳，就会明显降低使用门槛？现在 AI 这么方便，为什么很多工具还是停在命令行？</p>

<p>等自己真的做 desktop 版本后，我大概明白了。UI 的最后一公里非常碎，而且很多问题很难靠自动化测试提前发现。</p>

<p>比如按钮是不是在用户预期的位置，批量文件名太长会不会把确认弹窗撑爆，任务失败后错误信息是不是能看懂，生成中的任务切到后台再回来状态会不会跳，取消长任务后 GPU 进程有没有残留。这些问题不是写一个单元测试就能放心的。</p>

<p>这部分我做了一周多，说实话挺消耗耐心。</p>

<p>后来稍微提高效率的办法，是建立 QA 测试表。我不再发现一个问题就立刻开一个新对话修一个问题，而是批量记录、批量分类，再让 Codex 按类别处理。这样效率会高很多，也方便回头确认哪些问题已经修、哪些还要复测。</p>

<p>Fast Sub 后期 QA 覆盖了不少“真实使用才会出问题”的场景：</p>

<ul>
  <li>installer 和 portable zip。</li>
  <li>首次启动和默认模型下载。</li>
  <li>中文、日文、韩文、空格路径。</li>
  <li>本地 Faster Whisper、whisper.cpp、NLLB。</li>
  <li>长任务取消和进程清理。</li>
  <li>API key 保存、替换、删除。</li>
  <li>隐私 redaction。</li>
  <li>诊断页和截图 baseline。</li>
</ul>

<p>这里还有一个我后面才真正体会到的点：打包后才是真实产品。dev 模式能跑，只能说明开发环境里能跑。到了 packaged app，很多之前看不到的问题才会冒出来，比如 app 私有 Python runtime、daemon 的 cwd、资源路径、Windows installer、portable zip、退出后子进程残留、macOS 签名和权限。Fast Sub Round 13 花了很多时间在这些事情上，回头看很值，因为它们才决定一个用户下载后能不能真的用。</p>

<p>开源前还有另一类收口工作，看起来不像写代码，但一点也不少：README、CHANGELOG、CONTRIBUTING、SECURITY、LICENSE、第三方依赖 license inventory、隐私说明、安装说明、排障文档。</p>

<p>还要清理不能公开的东西，比如 API key、本机路径、大模型文件、大型媒体文件、真实 benchmark 输出、临时构建产物。</p>

<p>这一步我一开始也低估了。对开发者来说，代码能跑好像就结束了。但对外部用户来说，文档、隐私说明、安装说明和已知限制，都是项目可信度的一部分。AI 很适合帮我生成第一版文档，但文档里的承诺还是要自己审。尤其是安装说明、隐私说明、已知限制，不能让 AI 写得太乐观。</p>

<p>Fast Sub 又是本地优先工具，所以隐私边界得写清楚。远程 provider 不能变成隐式行为，任何上传音频或字幕文本的路径，我都希望它是用户显式选择和确认的。</p>

<h2 id="lessons-learned">9. 我踩过的坑：复杂度不会消失，只会转移</h2>

<p>如果把这次经历压缩成几个坑，大概是这些。</p>

<p>一开始太相信 AI 可以边写边整理。结果 Python 代码后面出现大文件和职责混杂，不得不专门花时间重构。</p>

<p>前期上下文太依赖聊天窗口。对话一长，AI 就容易忘记约束，甚至开始做自己不该做的事情。</p>

<p>有些阶段让 AI 一次做太多。scope 一大，bug 和偏差就会叠在一起，后面排查起来很累。</p>

<p>UI 原型早期不够细。后面真实 QA 时，很多交互问题才暴露出来。</p>

<p>还有就是低估了 packaged app 和真实环境的差异。dev 模式能跑，不代表打包后能跑。系统 Python、app 私有 Python、daemon cwd、进程清理、路径权限，这些问题只有打包后才真的暴露。</p>

<p>这些坑背后其实是同一个问题：复杂度不会消失，只会转移。</p>

<p>AI 可以帮我更快写代码，但不能让复杂度消失。有些问题早期不处理，后面还是会在实现、QA 或真实使用时冒出来。</p>

<h2 id="what-i-would-do-earlier">10. 如果重来一次，我会提前做什么</h2>

<p>如果现在让我从头再做一次 Fast Sub，我不会推翻现在的路线，但有几件事一定会提前做。</p>

<p>第一，项目一开始就建立 context 文件。不是等到对话开始忘事、代码开始膨胀后再补。哪怕一开始只是很粗的 <code class="language-plaintext highlighter-rouge">project-overview</code>、<code class="language-plaintext highlighter-rouge">architecture</code>、<code class="language-plaintext highlighter-rouge">code-standards</code> 和 <code class="language-plaintext highlighter-rouge">project-tracker</code>，也比全靠聊天记录强。</p>

<p>第二，更早规定目录结构和代码风格。Python 前期文件膨胀后再重构，成本比我预期高。AI 可以帮我重构，但它不擅长替我承担“前期没定边界”的后果。</p>

<p>第三，UI 原型阶段会把截图、状态和文案整理得更细。原型越粗，后面 QA 时补交互细节就越多。尤其是桌面工具，空状态、错误状态、批量任务、取消、失败重试这些页面，我会尽量在 mock 阶段就想清楚。</p>

<p>第四，更早建立 QA 表。我不想再等到最后一公里问题爆出来，才开始系统记录。QA 表不仅是测试清单，也是和 AI 协作修 bug 的输入格式。</p>

<p>第五，每个 round 更严格地区分 feature、refactor 和 release work。AI 很容易把它们混在一起，但这三类工作的验收标准完全不同。feature 看用户能力有没有增加，refactor 看行为有没有保持，release work 看真实环境有没有跑通。</p>

<p>这些都不是很炫的技巧，但如果一开始就做，我大概能少还不少债。</p>

<h2 id="conclusion">11. 总结：这次之后，我没那么迷信 prompt 了</h2>

<p>用 Codex 做完这个项目后，我对 AI 编程的看法变了不少。</p>

<p>我不再觉得关键是找到某个万能 prompt。prompt 有用，skills 有用，工具也会越来越强。但这次做下来，我更明显地感觉到，复杂项目真正消耗人的地方，还是范围控制、上下文、验证和收尾。</p>

<p>如果只给这次经历做一个备忘录，我大概会记下这几件事：</p>

<ol>
  <li>我会先做 MVP，不会一上来就规划完整系统。</li>
  <li>大任务前，我会先写 SPEC，尤其写清楚这轮不做什么。</li>
  <li>长期上下文会放进项目文档，不再只靠聊天记录。</li>
  <li>PM、实现、review、QA 这几类对话，我会继续分开。</li>
  <li>每轮修改后，我会看测试和验证结果，不只看 AI 说“完成了”。</li>
  <li>UI 还是会先 mock-first，等流程稳定再接真实后端。</li>
  <li>QA 问题会批量记录、批量修复、批量复测。</li>
  <li>开源前会单独留时间做文档、隐私、许可证和发布说明收口。</li>
</ol>

<p>这些听起来都不酷，也没有一句神奇 prompt 来得吸引人。但这是我这次真正留下来的东西。</p>

<p>AI 编程不是把项目交出去就结束了。它更像是把一个很强的协作者接进开发流程。流程越清楚，它越能放大人的能力。流程越混乱，它也会更快地放大混乱。</p>

<p>这大概就是我目前做完这个项目后，最想写给一个月前自己的结论。</p>]]></content><author><name></name></author><summary type="html"><![CDATA[目录]]></summary></entry><entry xml:lang="en"><title type="html">Perlin Noise - Principles and Implementation</title><link href="https://ryviuszero.github.io/en/posts/perlin-noise/" rel="alternate" type="text/html" title="Perlin Noise - Principles and Implementation" /><published>2025-11-07T08:34:01+00:00</published><updated>2025-11-07T08:34:01+00:00</updated><id>https://ryviuszero.github.io/en/posts/Perlin-Noise-EN</id><content type="html" xml:base="https://ryviuszero.github.io/en/posts/perlin-noise/"><![CDATA[<blockquote>
  <p>“There is no true randomness in nature, only patterns we haven’t understood yet.” — Philosophy of Perlin Noise</p>
</blockquote>

<hr />

<h2 id="table-of-contents">Table of Contents</h2>

<ol>
  <li><a href="#1-what-is-perlin-noise">What is Perlin Noise?</a></li>
  <li><a href="#2-why-do-we-need-perlin-noise">Why Do We Need Perlin Noise?</a></li>
  <li><a href="#3-core-principles">Core Principles</a></li>
  <li><a href="#4-1d-perlin-noise-explained">1D Perlin Noise Explained</a></li>
  <li><a href="#5-2d-perlin-noise-explained">2D Perlin Noise Explained</a></li>
  <li><a href="#6-fractal-noise-fractal-noise--fbm">Fractal Noise (Fractal Noise / fBm)</a></li>
  <li><a href="#7-simplex-noise">Simplex Noise</a></li>
  <li><a href="#8-code-implementation">Code Implementation</a></li>
  <li><a href="#9-common-applications">Common Applications</a></li>
  <li><a href="#10-parameter-tuning-tips">Parameter Tuning Tips</a></li>
  <li><a href="#11-common-misconceptions">Common Misconceptions</a></li>
  <li><a href="#12-references">References</a></li>
</ol>

<hr />

<h2 id="1-what-is-perlin-noise">1. What is Perlin Noise?</h2>

<p><strong>Perlin Noise</strong> is a <strong>gradient noise algorithm</strong> developed by Ken Perlin in 1983 for the film <em>Tron</em>, and formally published in 1985 in the SIGGRAPH paper <em>An Image Synthesizer</em>. Perlin received an Academy Award for this contribution.</p>

<p>It is a type of <strong>coherent noise</strong> with these characteristics:</p>

<ul>
  <li><strong>Smooth transitions</strong> between neighboring sample points (no sudden jumps)</li>
  <li><strong>Statistically isotropic</strong> (no preferred direction)</li>
  <li>Output values in a defined range (typically <code class="language-plaintext highlighter-rouge">-1</code> to <code class="language-plaintext highlighter-rouge">1</code>, or normalized to <code class="language-plaintext highlighter-rouge">0</code> to <code class="language-plaintext highlighter-rouge">1</code>)</li>
  <li><strong>Deterministic</strong>: same input always produces same output</li>
</ul>

<hr />

<h2 id="2-why-do-we-need-perlin-noise">2. Why Do We Need Perlin Noise?</h2>

<h3 id="the-problem-with-pure-randomness">The Problem with Pure Randomness</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Pure Random (White Noise):  9 4 1 7 3 8 2 5 6 ...
Perlin Noise:              3 4 5 6 5 4 3 2 3 ...
</code></pre></div></div>

<p>Pure random noise (white noise) treats each pixel independently, creating a “TV static” effect that <strong>almost never appears in nature</strong>.</p>

<p>Real natural phenomena (mountains, clouds, flames, water surfaces) exhibit <strong>local coherence</strong>: nearby locations have similar values, but with overall macroscopic variation. Perlin Noise mimics exactly this property.</p>

<h3 id="comparison">Comparison</h3>

<table>
  <thead>
    <tr>
      <th>Property</th>
      <th>White Noise</th>
      <th>Perlin Noise</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Smooth neighbors</td>
      <td>❌</td>
      <td>✅</td>
    </tr>
    <tr>
      <td>Natural looking</td>
      <td>❌</td>
      <td>✅</td>
    </tr>
    <tr>
      <td>Controllability</td>
      <td>Low</td>
      <td>High</td>
    </tr>
    <tr>
      <td>Computational complexity</td>
      <td>O(1)</td>
      <td>O(2ⁿ), n = dimensions</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="3-core-principles">3. Core Principles</h2>

<p>Perlin Noise follows three core steps:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>① Create grid, assign random gradient vectors to each grid point
          ↓
② Compute distance vectors from sample point to surrounding points
       Dot product with gradient vectors
          ↓
③ Use smooth interpolation to blend contributions from surrounding points
</code></pre></div></div>

<h3 id="key-concept-gradient-vectors">Key Concept: Gradient Vectors</h3>

<p>At each integer coordinate (grid point), pre-assign a <strong>random unit direction vector</strong>—this is the “gradient.”</p>

<p>Unlike Value Noise (storing random values at points), Perlin Noise stores <strong>directions, not values</strong>. This produces smoother, more natural output.</p>

<h3 id="key-concept-smooth-interpolation-curve">Key Concept: Smooth Interpolation Curve</h3>

<p>Simple linear interpolation (<code class="language-plaintext highlighter-rouge">lerp</code>) produces linear artifacts. Perlin uses a <strong>quintic smoothing curve</strong>:</p>

\[f(t) = 6t^5 - 15t^4 + 10t^3\]

<p>Properties:</p>
<ul>
  <li>$f(0) = 0$, $f(1) = 1$</li>
  <li>$f’(0) = 0$, $f’(1) = 0$ (zero derivatives at endpoints for smooth joining)</li>
  <li>$f’‘(0) = 0$, $f’‘(1) = 0$ (zero second derivatives for extra smoothness)</li>
</ul>

<blockquote>
  <p>Early versions used cubic curve $3t^2 - 2t^3$ (Smoothstep); improved versions use quintic (Smootherstep).</p>
</blockquote>

<hr />

<h2 id="4-1d-perlin-noise-explained">4. 1D Perlin Noise Explained</h2>

<p>For a 1D sample point $x = 2.3$:</p>

<h3 id="step-1-find-the-grid-cell">Step 1: Find the Grid Cell</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Left point:  x0 = floor(2.3) = 2
Right point: x1 = x0 + 1 = 3
Local coordinate: t = 2.3 - 2 = 0.3
</code></pre></div></div>

<h3 id="step-2-get-gradients-at-endpoints">Step 2: Get Gradients at Endpoints</h3>

<p>In 1D, gradients are just two directions: <code class="language-plaintext highlighter-rouge">+1</code> or <code class="language-plaintext highlighter-rouge">-1</code>, determined via <strong>permutation table</strong>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>grad(2) = +1
grad(3) = -1
</code></pre></div></div>

<h3 id="step-3-calculate-dot-products">Step 3: Calculate Dot Products</h3>

<p>Distance vector × gradient vector (multiplication in 1D):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Left contribution: dot(grad(x0), x - x0) = (+1) × (2.3 - 2) = +0.3
Right contribution: dot(grad(x1), x - x1) = (-1) × (2.3 - 3) = +0.7
</code></pre></div></div>

<h3 id="step-4-smooth-interpolation">Step 4: Smooth Interpolation</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>u = fade(t) = fade(0.3) ≈ 0.163  (quintic curve)
result = lerp(u, 0.3, 0.7) = 0.3 + 0.163 × (0.7 - 0.3) ≈ 0.365
</code></pre></div></div>

<hr />

<h2 id="5-2d-perlin-noise-explained">5. 2D Perlin Noise Explained</h2>

<p>In 2D, sample point $(x, y)$ is surrounded by four grid points:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(x0,y1) -------- (x1,y1)
   |         .        |
   |      (x,y)       |
   |                  |
(x0,y0) -------- (x1,y0)
</code></pre></div></div>

<h3 id="gradient-vectors-2d">Gradient Vectors (2D)</h3>

<p>2D gradients typically chosen from 4 (or 8) vectors:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(1,0), (-1,0), (0,1), (0,-1)
(1,1), (-1,1), (1,-1), (-1,-1)  ← normalized version
</code></pre></div></div>

<h3 id="computation-flow">Computation Flow</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>① Calculate distance vectors from sample to 4 corners
② Dot each with corner gradient → 4 values
③ Interpolate along x using fade(tx), then along y with fade(ty)
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Pseudocode
</span><span class="n">a</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">grad</span><span class="p">(</span><span class="n">x0</span><span class="p">,</span> <span class="n">y0</span><span class="p">),</span> <span class="n">dx</span><span class="p">,</span>   <span class="n">dy</span>  <span class="p">)</span>
<span class="n">b</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">grad</span><span class="p">(</span><span class="n">x1</span><span class="p">,</span> <span class="n">y0</span><span class="p">),</span> <span class="n">dx</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">dy</span>  <span class="p">)</span>
<span class="n">c</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">grad</span><span class="p">(</span><span class="n">x0</span><span class="p">,</span> <span class="n">y1</span><span class="p">),</span> <span class="n">dx</span><span class="p">,</span>   <span class="n">dy</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="n">d</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">grad</span><span class="p">(</span><span class="n">x1</span><span class="p">,</span> <span class="n">y1</span><span class="p">),</span> <span class="n">dx</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">dy</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>

<span class="n">u</span> <span class="o">=</span> <span class="n">fade</span><span class="p">(</span><span class="n">dx</span><span class="p">)</span>
<span class="n">v</span> <span class="o">=</span> <span class="n">fade</span><span class="p">(</span><span class="n">dy</span><span class="p">)</span>

<span class="n">ab</span> <span class="o">=</span> <span class="n">lerp</span><span class="p">(</span><span class="n">u</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span>
<span class="n">cd</span> <span class="o">=</span> <span class="n">lerp</span><span class="p">(</span><span class="n">u</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">d</span><span class="p">)</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">lerp</span><span class="p">(</span><span class="n">v</span><span class="p">,</span> <span class="n">ab</span><span class="p">,</span> <span class="n">cd</span><span class="p">)</span>
</code></pre></div></div>

<hr />

<h2 id="6-fractal-noise-fractal-noise--fbm">6. Fractal Noise (Fractal Noise / fBm)</h2>

<p>Single-layer Perlin Noise produces overly “smooth and monotonous” terrain lacking detail. Natural landscapes (mountains, clouds) are <strong>multi-scale layered</strong> phenomena.</p>

<h3 id="fbm-fractional-brownian-motion">fBm (Fractional Brownian Motion)</h3>

<p>Layer multiple noise octaves with different frequencies and amplitudes:</p>

\[\text{fBm}(x) = \sum_{i=0}^{n} \text{amplitude}_i \cdot \text{noise}(\text{frequency}_i \cdot x)\]

<h3 id="key-parameters">Key Parameters</h3>

<table>
  <thead>
    <tr>
      <th>Parameter</th>
      <th>Meaning</th>
      <th>Typical Value</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Octaves</strong></td>
      <td>Number of layers; more = richer detail</td>
      <td>4 ~ 8</td>
    </tr>
    <tr>
      <td><strong>Lacunarity</strong></td>
      <td>Frequency multiplier per layer</td>
      <td>2.0</td>
    </tr>
    <tr>
      <td><strong>Persistence / Gain</strong></td>
      <td>Amplitude reduction per layer</td>
      <td>0.5</td>
    </tr>
  </tbody>
</table>

<h3 id="example-4-layers">Example (4 Layers)</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Layer 0: frequency=1,  amplitude=1.0   → Large terrain outline
Layer 1: frequency=2,  amplitude=0.5   → Mid-scale hills
Layer 2: frequency=4,  amplitude=0.25  → Small-scale rocks
Layer 3: frequency=8,  amplitude=0.125 → Fine texture
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">fbm</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">octaves</span><span class="o">=</span><span class="mi">6</span><span class="p">,</span> <span class="n">lacunarity</span><span class="o">=</span><span class="mf">2.0</span><span class="p">,</span> <span class="n">gain</span><span class="o">=</span><span class="mf">0.5</span><span class="p">):</span>
    <span class="n">value</span> <span class="o">=</span> <span class="mf">0.0</span>
    <span class="n">amplitude</span> <span class="o">=</span> <span class="mf">1.0</span>
    <span class="n">frequency</span> <span class="o">=</span> <span class="mf">1.0</span>
    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">octaves</span><span class="p">):</span>
        <span class="n">value</span> <span class="o">+=</span> <span class="n">amplitude</span> <span class="o">*</span> <span class="n">perlin</span><span class="p">(</span><span class="n">x</span> <span class="o">*</span> <span class="n">frequency</span><span class="p">,</span> <span class="n">y</span> <span class="o">*</span> <span class="n">frequency</span><span class="p">)</span>
        <span class="n">amplitude</span> <span class="o">*=</span> <span class="n">gain</span>
        <span class="n">frequency</span> <span class="o">*=</span> <span class="n">lacunarity</span>
    <span class="k">return</span> <span class="n">value</span>
</code></pre></div></div>

<hr />

<h2 id="7-simplex-noise">7. Simplex Noise</h2>

<p>Ken Perlin proposed <strong>Simplex Noise</strong> in 2001 as an improved version.</p>

<h3 id="improvements">Improvements</h3>

<table>
  <thead>
    <tr>
      <th>Property</th>
      <th>Perlin Noise</th>
      <th>Simplex Noise</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Interpolation corners</td>
      <td>2ⁿ corners</td>
      <td>n+1 vertices</td>
    </tr>
    <tr>
      <td>Complexity (high-dim)</td>
      <td>O(2ⁿ)</td>
      <td>O(n²)</td>
    </tr>
    <tr>
      <td>Directional artifacts</td>
      <td>Yes (axis-aligned)</td>
      <td>Less</td>
    </tr>
    <tr>
      <td>Implementation</td>
      <td>Simpler</td>
      <td>Complex</td>
    </tr>
    <tr>
      <td>Patent issues</td>
      <td>None</td>
      <td>3D+ patented (use OpenSimplex)</td>
    </tr>
  </tbody>
</table>

<p>For high-dimensional scenarios (3D, 4D), <strong>Simplex Noise offers dramatic performance gains</strong>.</p>

<blockquote>
  <p>For patent-free implementation, use <strong>OpenSimplex2</strong> or <strong>SuperSimplex</strong>.</p>
</blockquote>

<hr />

<h2 id="8-code-implementation">8. Code Implementation</h2>

<h3 id="python-implementation-simplified-2d-perlin-noise">Python Implementation (Simplified 2D Perlin Noise)</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">math</span>
<span class="kn">import</span> <span class="nn">random</span>

<span class="k">def</span> <span class="nf">fade</span><span class="p">(</span><span class="n">t</span><span class="p">):</span>
    <span class="s">"""Quintic smoothing curve"""</span>
    <span class="k">return</span> <span class="n">t</span> <span class="o">*</span> <span class="n">t</span> <span class="o">*</span> <span class="n">t</span> <span class="o">*</span> <span class="p">(</span><span class="n">t</span> <span class="o">*</span> <span class="p">(</span><span class="n">t</span> <span class="o">*</span> <span class="mi">6</span> <span class="o">-</span> <span class="mi">15</span><span class="p">)</span> <span class="o">+</span> <span class="mi">10</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">lerp</span><span class="p">(</span><span class="n">t</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">):</span>
    <span class="s">"""Linear interpolation"""</span>
    <span class="k">return</span> <span class="n">a</span> <span class="o">+</span> <span class="n">t</span> <span class="o">*</span> <span class="p">(</span><span class="n">b</span> <span class="o">-</span> <span class="n">a</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">grad</span><span class="p">(</span><span class="n">hash_val</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">):</span>
    <span class="s">"""Select gradient direction and compute dot product"""</span>
    <span class="n">h</span> <span class="o">=</span> <span class="n">hash_val</span> <span class="o">&amp;</span> <span class="mi">3</span>
    <span class="k">if</span> <span class="n">h</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span> <span class="k">return</span>  <span class="n">x</span> <span class="o">+</span> <span class="n">y</span>
    <span class="k">if</span> <span class="n">h</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span> <span class="k">return</span> <span class="o">-</span><span class="n">x</span> <span class="o">+</span> <span class="n">y</span>
    <span class="k">if</span> <span class="n">h</span> <span class="o">==</span> <span class="mi">2</span><span class="p">:</span> <span class="k">return</span>  <span class="n">x</span> <span class="o">-</span> <span class="n">y</span>
    <span class="k">return</span>              <span class="o">-</span><span class="n">x</span> <span class="o">-</span> <span class="n">y</span>

<span class="k">class</span> <span class="nc">PerlinNoise2D</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">seed</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
        <span class="n">random</span><span class="p">.</span><span class="n">seed</span><span class="p">(</span><span class="n">seed</span><span class="p">)</span>
        <span class="c1"># Build permutation table
</span>        <span class="bp">self</span><span class="p">.</span><span class="n">perm</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="mi">256</span><span class="p">))</span>
        <span class="n">random</span><span class="p">.</span><span class="n">shuffle</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">perm</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">perm</span> <span class="o">+=</span> <span class="bp">self</span><span class="p">.</span><span class="n">perm</span>  <span class="c1"># Double for easier indexing
</span>
    <span class="k">def</span> <span class="nf">noise</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">):</span>
        <span class="c1"># Grid coordinates
</span>        <span class="n">xi</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">math</span><span class="p">.</span><span class="n">floor</span><span class="p">(</span><span class="n">x</span><span class="p">))</span> <span class="o">&amp;</span> <span class="mi">255</span>
        <span class="n">yi</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">math</span><span class="p">.</span><span class="n">floor</span><span class="p">(</span><span class="n">y</span><span class="p">))</span> <span class="o">&amp;</span> <span class="mi">255</span>
        <span class="c1"># Local coordinates
</span>        <span class="n">xf</span> <span class="o">=</span> <span class="n">x</span> <span class="o">-</span> <span class="n">math</span><span class="p">.</span><span class="n">floor</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
        <span class="n">yf</span> <span class="o">=</span> <span class="n">y</span> <span class="o">-</span> <span class="n">math</span><span class="p">.</span><span class="n">floor</span><span class="p">(</span><span class="n">y</span><span class="p">)</span>
        <span class="c1"># Smooth
</span>        <span class="n">u</span> <span class="o">=</span> <span class="n">fade</span><span class="p">(</span><span class="n">xf</span><span class="p">)</span>
        <span class="n">v</span> <span class="o">=</span> <span class="n">fade</span><span class="p">(</span><span class="n">yf</span><span class="p">)</span>
        <span class="c1"># Hash values for 4 corners
</span>        <span class="n">p</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">perm</span>
        <span class="n">aa</span> <span class="o">=</span> <span class="n">p</span><span class="p">[</span><span class="n">p</span><span class="p">[</span><span class="n">xi</span>    <span class="p">]</span> <span class="o">+</span> <span class="n">yi</span>    <span class="p">]</span>
        <span class="n">ab</span> <span class="o">=</span> <span class="n">p</span><span class="p">[</span><span class="n">p</span><span class="p">[</span><span class="n">xi</span>    <span class="p">]</span> <span class="o">+</span> <span class="n">yi</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span>
        <span class="n">ba</span> <span class="o">=</span> <span class="n">p</span><span class="p">[</span><span class="n">p</span><span class="p">[</span><span class="n">xi</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="n">yi</span>    <span class="p">]</span>
        <span class="n">bb</span> <span class="o">=</span> <span class="n">p</span><span class="p">[</span><span class="n">p</span><span class="p">[</span><span class="n">xi</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="n">yi</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span>
        <span class="c1"># Interpolate
</span>        <span class="n">x1</span> <span class="o">=</span> <span class="n">lerp</span><span class="p">(</span><span class="n">u</span><span class="p">,</span> <span class="n">grad</span><span class="p">(</span><span class="n">aa</span><span class="p">,</span> <span class="n">xf</span><span class="p">,</span>   <span class="n">yf</span>  <span class="p">),</span> <span class="n">grad</span><span class="p">(</span><span class="n">ba</span><span class="p">,</span> <span class="n">xf</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">yf</span>  <span class="p">))</span>
        <span class="n">x2</span> <span class="o">=</span> <span class="n">lerp</span><span class="p">(</span><span class="n">u</span><span class="p">,</span> <span class="n">grad</span><span class="p">(</span><span class="n">ab</span><span class="p">,</span> <span class="n">xf</span><span class="p">,</span>   <span class="n">yf</span><span class="o">-</span><span class="mi">1</span><span class="p">),</span> <span class="n">grad</span><span class="p">(</span><span class="n">bb</span><span class="p">,</span> <span class="n">xf</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">yf</span><span class="o">-</span><span class="mi">1</span><span class="p">))</span>
        <span class="k">return</span> <span class="n">lerp</span><span class="p">(</span><span class="n">v</span><span class="p">,</span> <span class="n">x1</span><span class="p">,</span> <span class="n">x2</span><span class="p">)</span>

<span class="c1"># Usage
</span><span class="n">pn</span> <span class="o">=</span> <span class="n">PerlinNoise2D</span><span class="p">(</span><span class="n">seed</span><span class="o">=</span><span class="mi">42</span><span class="p">)</span>
<span class="k">for</span> <span class="n">y</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
    <span class="n">row</span> <span class="o">=</span> <span class="p">[</span><span class="n">pn</span><span class="p">.</span><span class="n">noise</span><span class="p">(</span><span class="n">x</span> <span class="o">*</span> <span class="mf">0.1</span><span class="p">,</span> <span class="n">y</span> <span class="o">*</span> <span class="mf">0.1</span><span class="p">)</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">)]</span>
    <span class="k">print</span><span class="p">([</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">v</span><span class="si">:</span><span class="p">.</span><span class="mi">2</span><span class="n">f</span><span class="si">}</span><span class="s">"</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">row</span><span class="p">])</span>
</code></pre></div></div>

<h3 id="glsl-shader-implementation-2d">GLSL Shader Implementation (2D)</h3>

<div class="language-glsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Hash function (approximation)</span>
<span class="kt">float</span> <span class="nf">hash</span><span class="p">(</span><span class="kt">vec2</span> <span class="n">p</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">p</span> <span class="o">=</span> <span class="n">fract</span><span class="p">(</span><span class="n">p</span> <span class="o">*</span> <span class="kt">vec2</span><span class="p">(</span><span class="mi">127</span><span class="p">.</span><span class="mi">1</span><span class="p">,</span> <span class="mi">311</span><span class="p">.</span><span class="mi">7</span><span class="p">));</span>
    <span class="n">p</span> <span class="o">+=</span> <span class="n">dot</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">p</span> <span class="o">+</span> <span class="mi">19</span><span class="p">.</span><span class="mi">19</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">fract</span><span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">x</span> <span class="o">*</span> <span class="n">p</span><span class="p">.</span><span class="n">y</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// Gradient vectors</span>
<span class="kt">vec2</span> <span class="nf">gradient</span><span class="p">(</span><span class="kt">vec2</span> <span class="n">p</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">float</span> <span class="n">h</span> <span class="o">=</span> <span class="n">hash</span><span class="p">(</span><span class="n">p</span><span class="p">);</span>
    <span class="kt">float</span> <span class="n">angle</span> <span class="o">=</span> <span class="n">h</span> <span class="o">*</span> <span class="mi">6</span><span class="p">.</span><span class="mi">2832</span><span class="p">;</span> <span class="c1">// 0 ~ 2π</span>
    <span class="k">return</span> <span class="kt">vec2</span><span class="p">(</span><span class="n">cos</span><span class="p">(</span><span class="n">angle</span><span class="p">),</span> <span class="n">sin</span><span class="p">(</span><span class="n">angle</span><span class="p">));</span>
<span class="p">}</span>

<span class="c1">// 2D Perlin Noise</span>
<span class="kt">float</span> <span class="nf">perlin</span><span class="p">(</span><span class="kt">vec2</span> <span class="n">p</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">vec2</span> <span class="n">i</span> <span class="o">=</span> <span class="n">floor</span><span class="p">(</span><span class="n">p</span><span class="p">);</span>
    <span class="kt">vec2</span> <span class="n">f</span> <span class="o">=</span> <span class="n">fract</span><span class="p">(</span><span class="n">p</span><span class="p">);</span>
    <span class="kt">vec2</span> <span class="n">u</span> <span class="o">=</span> <span class="n">f</span> <span class="o">*</span> <span class="n">f</span> <span class="o">*</span> <span class="n">f</span> <span class="o">*</span> <span class="p">(</span><span class="n">f</span> <span class="o">*</span> <span class="p">(</span><span class="n">f</span> <span class="o">*</span> <span class="mi">6</span><span class="p">.</span><span class="mi">0</span> <span class="o">-</span> <span class="mi">15</span><span class="p">.</span><span class="mi">0</span><span class="p">)</span> <span class="o">+</span> <span class="mi">10</span><span class="p">.</span><span class="mi">0</span><span class="p">);</span> <span class="c1">// fade</span>

    <span class="kt">float</span> <span class="n">a</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">gradient</span><span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="kt">vec2</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">)),</span> <span class="n">f</span> <span class="o">-</span> <span class="kt">vec2</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">));</span>
    <span class="kt">float</span> <span class="n">b</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">gradient</span><span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="kt">vec2</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">0</span><span class="p">)),</span> <span class="n">f</span> <span class="o">-</span> <span class="kt">vec2</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">0</span><span class="p">));</span>
    <span class="kt">float</span> <span class="n">c</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">gradient</span><span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="kt">vec2</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">)),</span> <span class="n">f</span> <span class="o">-</span> <span class="kt">vec2</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">));</span>
    <span class="kt">float</span> <span class="n">d</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">gradient</span><span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="kt">vec2</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">)),</span> <span class="n">f</span> <span class="o">-</span> <span class="kt">vec2</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">));</span>

    <span class="k">return</span> <span class="n">mix</span><span class="p">(</span><span class="n">mix</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">u</span><span class="p">.</span><span class="n">x</span><span class="p">),</span> <span class="n">mix</span><span class="p">(</span><span class="n">c</span><span class="p">,</span> <span class="n">d</span><span class="p">,</span> <span class="n">u</span><span class="p">.</span><span class="n">x</span><span class="p">),</span> <span class="n">u</span><span class="p">.</span><span class="n">y</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h2 id="9-common-applications">9. Common Applications</h2>

<h3 id="terrain-generation">Terrain Generation</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Height map = fBm(x, z)
Texture blend weight = noise(x, z)  → grass/dirt/rock
</code></pre></div></div>

<h3 id="clouds-and-sky">Clouds and Sky</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Cloud density = smoothstep(0.4, 0.6, fBm(x, y, t))
</code></pre></div></div>

<h3 id="water-surface-and-waves">Water Surface and Waves</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Water height(x, z, t) = noise(x + t, z) + noise(x, z + t * 0.7) * 0.5
Surface normal = gradient of height field
</code></pre></div></div>

<h3 id="procedural-textures">Procedural Textures</h3>

<ul>
  <li><strong>Marble</strong>: <code class="language-plaintext highlighter-rouge">sin(x + noise(x, y) * 10)</code></li>
  <li><strong>Wood grain</strong>: <code class="language-plaintext highlighter-rouge">sin(sqrt(x² + y²) + noise(x, y) * 5)</code></li>
  <li><strong>Fire effect</strong>: Dynamic noise with color gradient</li>
</ul>

<h3 id="animation-and-motion">Animation and Motion</h3>

<ul>
  <li><strong>Camera shake</strong>: Low-frequency noise drives camera offset, simulating handheld effect</li>
  <li><strong>NPC pathfinding</strong>: Noise generates natural wandering directions</li>
  <li><strong>Particle systems</strong>: Noise field controls particle forces</li>
</ul>

<hr />

<h2 id="10-parameter-tuning-tips">10. Parameter Tuning Tips</h2>

<h3 id="frequency-frequency--scale">Frequency (Frequency / Scale)</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Low frequency (large scale) → Slow variation, large-scale features (mountains, continents)
High frequency (small scale) → Fast variation, fine details (stone texture, grass)
</code></pre></div></div>

<h3 id="amplitude">Amplitude</h3>

<p>Controls noise value intensity, typically normalized:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Normalize to [0, 1]
</span><span class="n">normalized</span> <span class="o">=</span> <span class="p">(</span><span class="n">noise_value</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span>
</code></pre></div></div>

<h3 id="octave-selection">Octave Selection</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2 ~ 3 layers: Smooth, simple (clouds, water)
4 ~ 6 layers: Natural terrain (mountains, hills)
7 ~ 8 layers: Rich detail (rocks, soil)
</code></pre></div></div>

<blockquote>
  <p><strong>Note:</strong> More layers have diminishing visual returns but linear computation cost.</p>
</blockquote>

<h3 id="domain-warping">Domain Warping</h3>

<p>Advanced technique—warp coordinates using noise for highly natural flow effects:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">warped_noise</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">):</span>
    <span class="c1"># Use noise to offset input coordinates
</span>    <span class="n">dx</span> <span class="o">=</span> <span class="n">fbm</span><span class="p">(</span><span class="n">x</span> <span class="o">+</span> <span class="mf">1.7</span><span class="p">,</span> <span class="n">y</span> <span class="o">+</span> <span class="mf">9.2</span><span class="p">)</span>
    <span class="n">dy</span> <span class="o">=</span> <span class="n">fbm</span><span class="p">(</span><span class="n">x</span> <span class="o">+</span> <span class="mf">8.3</span><span class="p">,</span> <span class="n">y</span> <span class="o">+</span> <span class="mf">2.8</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">fbm</span><span class="p">(</span><span class="n">x</span> <span class="o">+</span> <span class="mf">4.0</span> <span class="o">*</span> <span class="n">dx</span><span class="p">,</span> <span class="n">y</span> <span class="o">+</span> <span class="mf">4.0</span> <span class="o">*</span> <span class="n">dy</span><span class="p">)</span>
</code></pre></div></div>

<p>This is the core technique behind many Shadertoy works by Inigo Quilez.</p>

<hr />

<h2 id="11-common-misconceptions">11. Common Misconceptions</h2>

<h3 id="misconception-1-perlin-noise-is-random">Misconception 1: Perlin Noise is Random</h3>

<p>Perlin Noise is <strong>not random</strong>. Same input always returns same output. It’s <strong>deterministic pseudo-randomness</strong>. True randomness comes from shuffling the permutation table at initialization (<code class="language-plaintext highlighter-rouge">seed</code>).</p>

<h3 id="misconception-2-output-range-is--1-1">Misconception 2: Output Range is [-1, 1]</h3>

<p>Theoretical range is $[-\sqrt{n/4}, \sqrt{n/4}]$ (n = dimensions), but actual implementations vary. <strong>Don’t assume output strictly constrained to any range</strong>—normalize or clamp in practice.</p>

<h3 id="misconception-3-higher-frequency--better-detail">Misconception 3: Higher Frequency = Better Detail</h3>

<p>High-frequency noise causes <strong>aliasing</strong>, producing flickering in dynamic scenes. Choose max frequency based on sampling rate (consult Nyquist theorem).</p>

<h3 id="misconception-4-using-random-directly">Misconception 4: Using random() Directly</h3>

<p>Many beginners regenerate random gradients on each call, breaking coherence. <strong>Pre-build permutation tables</strong>, query via lookup.</p>

<hr />

<h2 id="12-references">12. References</h2>

<ul>
  <li>Ken Perlin’s improved version: <a href="https://mrl.cs.nyu.edu/~perlin/paper445.pdf">Improving Noise (SIGGRAPH 2002)</a></li>
  <li>Inigo Quilez on Domain Warping: <a href="https://iquilezles.org/articles/warp/">iquilezles.org/articles/warp</a></li>
  <li>The Book of Shaders (highly recommended): <a href="https://thebookofshaders.com/11/">thebookofshaders.com/11/</a></li>
  <li>Wikipedia - Perlin Noise: <a href="https://en.wikipedia.org/wiki/Perlin_noise">en.wikipedia.org/wiki/Perlin_noise</a></li>
  <li>OpenSimplex2 (patent-free alternative): <a href="https://github.com/KdotJPG/OpenSimplex2">github.com/KdotJPG/OpenSimplex2</a></li>
</ul>

<hr />]]></content><author><name></name></author><summary type="html"><![CDATA[“There is no true randomness in nature, only patterns we haven’t understood yet.” — Philosophy of Perlin Noise]]></summary></entry><entry xml:lang="zh"><title type="html">Perlin Noise 原理与实现</title><link href="https://ryviuszero.github.io/zh/posts/perlin-noise/" rel="alternate" type="text/html" title="Perlin Noise 原理与实现" /><published>2025-11-07T08:34:01+00:00</published><updated>2025-11-07T08:34:01+00:00</updated><id>https://ryviuszero.github.io/zh/posts/Perlin-Noise</id><content type="html" xml:base="https://ryviuszero.github.io/zh/posts/perlin-noise/"><![CDATA[<blockquote>
  <p>“自然界中没有真正的随机，只有我们尚未理解的规律。” —— Perlin Noise 的哲学</p>
</blockquote>

<hr />

<h2 id="目录">目录</h2>

<ol>
  <li><a href="#1-什么是-perlin-noise">什么是 Perlin Noise？</a></li>
  <li><a href="#2-为什么需要-perlin-noise">为什么需要 Perlin Noise？</a></li>
  <li><a href="#3-核心原理">核心原理</a></li>
  <li><a href="#4-一维-perlin-noise-详解">一维 Perlin Noise 详解</a></li>
  <li><a href="#5-二维-perlin-noise-详解">二维 Perlin Noise 详解</a></li>
  <li><a href="#6-分形噪声fractal-noise--fbm">分形噪声（Fractal Noise / fBm）</a></li>
  <li><a href="#7-simplex-noise">Simplex Noise</a></li>
  <li><a href="#8-代码实现">代码实现</a></li>
  <li><a href="#9-常见应用场景">常见应用场景</a></li>
  <li><a href="#10-参数调节技巧">参数调节技巧</a></li>
  <li><a href="#11-常见误区">常见误区</a></li>
  <li><a href="#12-参考资料">参考资料</a></li>
</ol>

<hr />

<h2 id="1-什么是-perlin-noise">1. 什么是 Perlin Noise？</h2>

<p><strong>Perlin Noise</strong>（柏林噪声）是由 Ken Perlin 在 1983 年为电影《创：战纪》（Tron）开发的一种<strong>梯度噪声算法</strong>，并于 1985 年在 SIGGRAPH 论文 <em>An Image Synthesizer</em> 中正式发表。Ken Perlin 也因此获得了奥斯卡科学技术奖。</p>

<p>它是一种<strong>相干噪声（Coherent Noise）</strong>，具有以下特点：</p>

<ul>
  <li>相邻采样点之间<strong>平滑过渡</strong>，不会突变</li>
  <li>在统计上是<strong>各向同性</strong>的（没有明显方向偏好）</li>
  <li>输出值在一定范围内（通常 <code class="language-plaintext highlighter-rouge">-1</code> 到 <code class="language-plaintext highlighter-rouge">1</code>，或归一化到 <code class="language-plaintext highlighter-rouge">0</code> 到 <code class="language-plaintext highlighter-rouge">1</code>）</li>
  <li><strong>可重复</strong>：相同输入总是产生相同输出</li>
</ul>

<hr />

<h2 id="2-为什么需要-perlin-noise">2. 为什么需要 Perlin Noise？</h2>

<h3 id="纯随机的问题">纯随机的问题</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>纯随机（White Noise）：9 4 1 7 3 8 2 5 6 ...
Perlin Noise：        3 4 5 6 5 4 3 2 3 ...
</code></pre></div></div>

<p>纯随机噪声（白噪声）每个像素完全独立，产生的是”电视雪花”效果，<strong>在自然界中几乎不存在</strong>。</p>

<p>真实的自然现象（山脉、云朵、火焰、水面）都具有<strong>局部相似性</strong>：靠近的地方高度/颜色差异不大，但整体上又有宏观的起伏变化。Perlin Noise 正是为了模拟这种特性而生。</p>

<h3 id="对比总结">对比总结</h3>

<table>
  <thead>
    <tr>
      <th>特性</th>
      <th>白噪声</th>
      <th>Perlin Noise</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>相邻平滑</td>
      <td>❌</td>
      <td>✅</td>
    </tr>
    <tr>
      <td>自然感</td>
      <td>❌</td>
      <td>✅</td>
    </tr>
    <tr>
      <td>可控性</td>
      <td>低</td>
      <td>高</td>
    </tr>
    <tr>
      <td>计算复杂度</td>
      <td>O(1)</td>
      <td>O(2ⁿ)，n 为维度</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="3-核心原理">3. 核心原理</h2>

<p>Perlin Noise 的核心思路分三步：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>① 建立网格，为每个格点分配随机梯度向量
        ↓
② 计算采样点到周围格点的距离向量，与梯度向量做点积
        ↓
③ 用平滑插值函数，将周围格点的点积值融合成最终输出
</code></pre></div></div>

<h3 id="关键概念梯度向量gradient-vector">关键概念：梯度向量（Gradient Vector）</h3>

<p>在每个整数坐标（格点）上，预先分配一个<strong>随机的单位方向向量</strong>，这就是”梯度”。</p>

<p>与 Value Noise（直接在格点上存随机值）不同，Perlin Noise 存的是<strong>方向</strong>，而不是<strong>值</strong>。这使得它的输出更加平滑、视觉上更自然。</p>

<h3 id="关键概念平滑插值函数">关键概念：平滑插值函数</h3>

<p>普通线性插值（<code class="language-plaintext highlighter-rouge">lerp</code>）会产生折线感。Perlin 使用了<strong>五次平滑曲线（Quintic Curve）</strong>：</p>

\[f(t) = 6t^5 - 15t^4 + 10t^3\]

<p>这个函数的特点：</p>
<ul>
  <li>$f(0) = 0$，$f(1) = 1$</li>
  <li>$f’(0) = 0$，$f’(1) = 0$（端点处导数为 0，保证平滑衔接）</li>
  <li>$f’‘(0) = 0$，$f’‘(1) = 0$（二阶导也为 0，更加平滑）</li>
</ul>

<blockquote>
  <p>早期版本使用的是三次曲线 $3t^2 - 2t^3$（Smoothstep），改进版使用五次曲线（Smootherstep）。</p>
</blockquote>

<hr />

<h2 id="4-一维-perlin-noise-详解">4. 一维 Perlin Noise 详解</h2>

<p>以一维为例，假设采样点 $x = 2.3$：</p>

<h3 id="step-1找到所属格子">Step 1：找到所属格子</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>左格点：x0 = floor(2.3) = 2
右格点：x1 = x0 + 1 = 3
局部坐标：t = 2.3 - 2 = 0.3
</code></pre></div></div>

<h3 id="step-2获取两端梯度">Step 2：获取两端梯度</h3>

<p>一维中，梯度只有两个方向：<code class="language-plaintext highlighter-rouge">+1</code> 或 <code class="language-plaintext highlighter-rouge">-1</code>，通过<strong>置换表（Permutation Table）</strong>伪随机确定：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>grad(2) = +1
grad(3) = -1
</code></pre></div></div>

<h3 id="step-3计算点积">Step 3：计算点积</h3>

<p>距离向量 × 梯度向量（一维中即乘法）：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>左端点贡献：dot(grad(x0), x - x0) = (+1) × (2.3 - 2) = +0.3
右端点贡献：dot(grad(x1), x - x1) = (-1) × (2.3 - 3) = +0.7
</code></pre></div></div>

<h3 id="step-4平滑插值">Step 4：平滑插值</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>u = fade(t) = fade(0.3) ≈ 0.163  （五次曲线）
result = lerp(u, 0.3, 0.7) = 0.3 + 0.163 × (0.7 - 0.3) ≈ 0.365
</code></pre></div></div>

<hr />

<h2 id="5-二维-perlin-noise-详解">5. 二维 Perlin Noise 详解</h2>

<p>二维时，采样点 $(x, y)$ 被四个格点围绕：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(x0,y1) -------- (x1,y1)
   |         .        |
   |      (x,y)       |
   |                  |
(x0,y0) -------- (x1,y0)
</code></pre></div></div>

<h3 id="梯度向量2d">梯度向量（2D）</h3>

<p>二维梯度通常从以下 4 个（或 8 个）向量中随机选取：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(1,0), (-1,0), (0,1), (0,-1)
(1,1), (-1,1), (1,-1), (-1,-1)  ← 归一化后使用
</code></pre></div></div>

<h3 id="计算流程">计算流程</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>① 计算采样点到 4 个角的距离向量
② 分别与各角的梯度做点积，得到 4 个值
③ 用 fade(tx) 和 fade(ty) 先沿 x 插值，再沿 y 插值
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 伪代码
</span><span class="n">a</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">grad</span><span class="p">(</span><span class="n">x0</span><span class="p">,</span> <span class="n">y0</span><span class="p">),</span> <span class="n">dx</span><span class="p">,</span>   <span class="n">dy</span>  <span class="p">)</span>
<span class="n">b</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">grad</span><span class="p">(</span><span class="n">x1</span><span class="p">,</span> <span class="n">y0</span><span class="p">),</span> <span class="n">dx</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">dy</span>  <span class="p">)</span>
<span class="n">c</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">grad</span><span class="p">(</span><span class="n">x0</span><span class="p">,</span> <span class="n">y1</span><span class="p">),</span> <span class="n">dx</span><span class="p">,</span>   <span class="n">dy</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="n">d</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">grad</span><span class="p">(</span><span class="n">x1</span><span class="p">,</span> <span class="n">y1</span><span class="p">),</span> <span class="n">dx</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">dy</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>

<span class="n">u</span> <span class="o">=</span> <span class="n">fade</span><span class="p">(</span><span class="n">dx</span><span class="p">)</span>
<span class="n">v</span> <span class="o">=</span> <span class="n">fade</span><span class="p">(</span><span class="n">dy</span><span class="p">)</span>

<span class="n">ab</span> <span class="o">=</span> <span class="n">lerp</span><span class="p">(</span><span class="n">u</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span>
<span class="n">cd</span> <span class="o">=</span> <span class="n">lerp</span><span class="p">(</span><span class="n">u</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">d</span><span class="p">)</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">lerp</span><span class="p">(</span><span class="n">v</span><span class="p">,</span> <span class="n">ab</span><span class="p">,</span> <span class="n">cd</span><span class="p">)</span>
</code></pre></div></div>

<hr />

<h2 id="6-分形噪声fractal-noise--fbm">6. 分形噪声（Fractal Noise / fBm）</h2>

<p>单层 Perlin Noise 产生的地形过于”平滑单调”，缺乏细节。自然界的地貌（山脉、云朵）是<strong>多尺度叠加</strong>的结果。</p>

<h3 id="fbmfractional-brownian-motion">fBm（Fractional Brownian Motion）</h3>

<p>通过叠加多个频率和振幅不同的 Noise 层（<strong>倍频程，Octaves</strong>）：</p>

\[\text{fBm}(x) = \sum_{i=0}^{n} \text{amplitude}_i \cdot \text{noise}(\text{frequency}_i \cdot x)\]

<h3 id="关键参数">关键参数</h3>

<table>
  <thead>
    <tr>
      <th>参数</th>
      <th>说明</th>
      <th>典型值</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Octaves（倍频程数）</strong></td>
      <td>叠加的层数，越多细节越丰富</td>
      <td>4 ~ 8</td>
    </tr>
    <tr>
      <td><strong>Lacunarity（缺陷度）</strong></td>
      <td>每层频率的倍增系数</td>
      <td>2.0</td>
    </tr>
    <tr>
      <td><strong>Persistence / Gain</strong></td>
      <td>每层振幅的衰减系数</td>
      <td>0.5</td>
    </tr>
  </tbody>
</table>

<h3 id="示例4-层叠加">示例（4 层叠加）</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Layer 0: frequency=1,  amplitude=1.0   → 大尺度地形轮廓
Layer 1: frequency=2,  amplitude=0.5   → 中尺度丘陵
Layer 2: frequency=4,  amplitude=0.25  → 小尺度岩石
Layer 3: frequency=8,  amplitude=0.125 → 细节纹理
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">fbm</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">octaves</span><span class="o">=</span><span class="mi">6</span><span class="p">,</span> <span class="n">lacunarity</span><span class="o">=</span><span class="mf">2.0</span><span class="p">,</span> <span class="n">gain</span><span class="o">=</span><span class="mf">0.5</span><span class="p">):</span>
    <span class="n">value</span> <span class="o">=</span> <span class="mf">0.0</span>
    <span class="n">amplitude</span> <span class="o">=</span> <span class="mf">1.0</span>
    <span class="n">frequency</span> <span class="o">=</span> <span class="mf">1.0</span>
    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">octaves</span><span class="p">):</span>
        <span class="n">value</span> <span class="o">+=</span> <span class="n">amplitude</span> <span class="o">*</span> <span class="n">perlin</span><span class="p">(</span><span class="n">x</span> <span class="o">*</span> <span class="n">frequency</span><span class="p">,</span> <span class="n">y</span> <span class="o">*</span> <span class="n">frequency</span><span class="p">)</span>
        <span class="n">amplitude</span> <span class="o">*=</span> <span class="n">gain</span>
        <span class="n">frequency</span> <span class="o">*=</span> <span class="n">lacunarity</span>
    <span class="k">return</span> <span class="n">value</span>
</code></pre></div></div>

<hr />

<h2 id="7-simplex-noise">7. Simplex Noise</h2>

<p>Ken Perlin 于 2001 年提出了 <strong>Simplex Noise</strong>，作为 Perlin Noise 的改进版本。</p>

<h3 id="改进点">改进点</h3>

<table>
  <thead>
    <tr>
      <th>特性</th>
      <th>Perlin Noise</th>
      <th>Simplex Noise</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>插值方向数</td>
      <td>2ⁿ 个角</td>
      <td>n+1 个顶点</td>
    </tr>
    <tr>
      <td>计算复杂度（高维）</td>
      <td>O(2ⁿ)</td>
      <td>O(n²)</td>
    </tr>
    <tr>
      <td>方向性伪影</td>
      <td>有（轴对齐）</td>
      <td>较少</td>
    </tr>
    <tr>
      <td>实现复杂度</td>
      <td>较简单</td>
      <td>较复杂</td>
    </tr>
    <tr>
      <td>专利问题</td>
      <td>无</td>
      <td>3D+ 版本有专利（OpenSimplex 可替代）</td>
    </tr>
  </tbody>
</table>

<p>在高维（3D、4D）场景中，<strong>Simplex Noise 的性能优势非常显著</strong>。</p>

<blockquote>
  <p>如果需要无专利约束的实现，可以使用 <strong>OpenSimplex2</strong> 或 <strong>SuperSimplex</strong>。</p>
</blockquote>

<hr />

<h2 id="8-代码实现">8. 代码实现</h2>

<h3 id="python-实现简化版-2d-perlin-noise">Python 实现（简化版 2D Perlin Noise）</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">math</span>
<span class="kn">import</span> <span class="nn">random</span>

<span class="k">def</span> <span class="nf">fade</span><span class="p">(</span><span class="n">t</span><span class="p">):</span>
    <span class="s">"""五次平滑曲线"""</span>
    <span class="k">return</span> <span class="n">t</span> <span class="o">*</span> <span class="n">t</span> <span class="o">*</span> <span class="n">t</span> <span class="o">*</span> <span class="p">(</span><span class="n">t</span> <span class="o">*</span> <span class="p">(</span><span class="n">t</span> <span class="o">*</span> <span class="mi">6</span> <span class="o">-</span> <span class="mi">15</span><span class="p">)</span> <span class="o">+</span> <span class="mi">10</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">lerp</span><span class="p">(</span><span class="n">t</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">):</span>
    <span class="s">"""线性插值"""</span>
    <span class="k">return</span> <span class="n">a</span> <span class="o">+</span> <span class="n">t</span> <span class="o">*</span> <span class="p">(</span><span class="n">b</span> <span class="o">-</span> <span class="n">a</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">grad</span><span class="p">(</span><span class="n">hash_val</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">):</span>
    <span class="s">"""根据 hash 值选择梯度方向并计算点积"""</span>
    <span class="n">h</span> <span class="o">=</span> <span class="n">hash_val</span> <span class="o">&amp;</span> <span class="mi">3</span>
    <span class="k">if</span> <span class="n">h</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span> <span class="k">return</span>  <span class="n">x</span> <span class="o">+</span> <span class="n">y</span>
    <span class="k">if</span> <span class="n">h</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span> <span class="k">return</span> <span class="o">-</span><span class="n">x</span> <span class="o">+</span> <span class="n">y</span>
    <span class="k">if</span> <span class="n">h</span> <span class="o">==</span> <span class="mi">2</span><span class="p">:</span> <span class="k">return</span>  <span class="n">x</span> <span class="o">-</span> <span class="n">y</span>
    <span class="k">return</span>              <span class="o">-</span><span class="n">x</span> <span class="o">-</span> <span class="n">y</span>

<span class="k">class</span> <span class="nc">PerlinNoise2D</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">seed</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
        <span class="n">random</span><span class="p">.</span><span class="n">seed</span><span class="p">(</span><span class="n">seed</span><span class="p">)</span>
        <span class="c1"># 构建置换表
</span>        <span class="bp">self</span><span class="p">.</span><span class="n">perm</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="mi">256</span><span class="p">))</span>
        <span class="n">random</span><span class="p">.</span><span class="n">shuffle</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">perm</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">perm</span> <span class="o">+=</span> <span class="bp">self</span><span class="p">.</span><span class="n">perm</span>  <span class="c1"># 复制一份，方便索引
</span>
    <span class="k">def</span> <span class="nf">noise</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">):</span>
        <span class="c1"># 格点坐标
</span>        <span class="n">xi</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">math</span><span class="p">.</span><span class="n">floor</span><span class="p">(</span><span class="n">x</span><span class="p">))</span> <span class="o">&amp;</span> <span class="mi">255</span>
        <span class="n">yi</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">math</span><span class="p">.</span><span class="n">floor</span><span class="p">(</span><span class="n">y</span><span class="p">))</span> <span class="o">&amp;</span> <span class="mi">255</span>
        <span class="c1"># 局部坐标
</span>        <span class="n">xf</span> <span class="o">=</span> <span class="n">x</span> <span class="o">-</span> <span class="n">math</span><span class="p">.</span><span class="n">floor</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
        <span class="n">yf</span> <span class="o">=</span> <span class="n">y</span> <span class="o">-</span> <span class="n">math</span><span class="p">.</span><span class="n">floor</span><span class="p">(</span><span class="n">y</span><span class="p">)</span>
        <span class="c1"># 平滑
</span>        <span class="n">u</span> <span class="o">=</span> <span class="n">fade</span><span class="p">(</span><span class="n">xf</span><span class="p">)</span>
        <span class="n">v</span> <span class="o">=</span> <span class="n">fade</span><span class="p">(</span><span class="n">yf</span><span class="p">)</span>
        <span class="c1"># 四个角的 hash
</span>        <span class="n">p</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">perm</span>
        <span class="n">aa</span> <span class="o">=</span> <span class="n">p</span><span class="p">[</span><span class="n">p</span><span class="p">[</span><span class="n">xi</span>    <span class="p">]</span> <span class="o">+</span> <span class="n">yi</span>    <span class="p">]</span>
        <span class="n">ab</span> <span class="o">=</span> <span class="n">p</span><span class="p">[</span><span class="n">p</span><span class="p">[</span><span class="n">xi</span>    <span class="p">]</span> <span class="o">+</span> <span class="n">yi</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span>
        <span class="n">ba</span> <span class="o">=</span> <span class="n">p</span><span class="p">[</span><span class="n">p</span><span class="p">[</span><span class="n">xi</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="n">yi</span>    <span class="p">]</span>
        <span class="n">bb</span> <span class="o">=</span> <span class="n">p</span><span class="p">[</span><span class="n">p</span><span class="p">[</span><span class="n">xi</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="n">yi</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span>
        <span class="c1"># 插值
</span>        <span class="n">x1</span> <span class="o">=</span> <span class="n">lerp</span><span class="p">(</span><span class="n">u</span><span class="p">,</span> <span class="n">grad</span><span class="p">(</span><span class="n">aa</span><span class="p">,</span> <span class="n">xf</span><span class="p">,</span>   <span class="n">yf</span>  <span class="p">),</span> <span class="n">grad</span><span class="p">(</span><span class="n">ba</span><span class="p">,</span> <span class="n">xf</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">yf</span>  <span class="p">))</span>
        <span class="n">x2</span> <span class="o">=</span> <span class="n">lerp</span><span class="p">(</span><span class="n">u</span><span class="p">,</span> <span class="n">grad</span><span class="p">(</span><span class="n">ab</span><span class="p">,</span> <span class="n">xf</span><span class="p">,</span>   <span class="n">yf</span><span class="o">-</span><span class="mi">1</span><span class="p">),</span> <span class="n">grad</span><span class="p">(</span><span class="n">bb</span><span class="p">,</span> <span class="n">xf</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">yf</span><span class="o">-</span><span class="mi">1</span><span class="p">))</span>
        <span class="k">return</span> <span class="n">lerp</span><span class="p">(</span><span class="n">v</span><span class="p">,</span> <span class="n">x1</span><span class="p">,</span> <span class="n">x2</span><span class="p">)</span>

<span class="c1"># 使用示例
</span><span class="n">pn</span> <span class="o">=</span> <span class="n">PerlinNoise2D</span><span class="p">(</span><span class="n">seed</span><span class="o">=</span><span class="mi">42</span><span class="p">)</span>
<span class="k">for</span> <span class="n">y</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
    <span class="n">row</span> <span class="o">=</span> <span class="p">[</span><span class="n">pn</span><span class="p">.</span><span class="n">noise</span><span class="p">(</span><span class="n">x</span> <span class="o">*</span> <span class="mf">0.1</span><span class="p">,</span> <span class="n">y</span> <span class="o">*</span> <span class="mf">0.1</span><span class="p">)</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">)]</span>
    <span class="k">print</span><span class="p">([</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">v</span><span class="si">:</span><span class="p">.</span><span class="mi">2</span><span class="n">f</span><span class="si">}</span><span class="s">"</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">row</span><span class="p">])</span>
</code></pre></div></div>

<h3 id="glsl-shader-实现2d">GLSL Shader 实现（2D）</h3>

<div class="language-glsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 置换函数（近似）</span>
<span class="kt">float</span> <span class="nf">hash</span><span class="p">(</span><span class="kt">vec2</span> <span class="n">p</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">p</span> <span class="o">=</span> <span class="n">fract</span><span class="p">(</span><span class="n">p</span> <span class="o">*</span> <span class="kt">vec2</span><span class="p">(</span><span class="mi">127</span><span class="p">.</span><span class="mi">1</span><span class="p">,</span> <span class="mi">311</span><span class="p">.</span><span class="mi">7</span><span class="p">));</span>
    <span class="n">p</span> <span class="o">+=</span> <span class="n">dot</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">p</span> <span class="o">+</span> <span class="mi">19</span><span class="p">.</span><span class="mi">19</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">fract</span><span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">x</span> <span class="o">*</span> <span class="n">p</span><span class="p">.</span><span class="n">y</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// 梯度向量</span>
<span class="kt">vec2</span> <span class="nf">gradient</span><span class="p">(</span><span class="kt">vec2</span> <span class="n">p</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">float</span> <span class="n">h</span> <span class="o">=</span> <span class="n">hash</span><span class="p">(</span><span class="n">p</span><span class="p">);</span>
    <span class="kt">float</span> <span class="n">angle</span> <span class="o">=</span> <span class="n">h</span> <span class="o">*</span> <span class="mi">6</span><span class="p">.</span><span class="mi">2832</span><span class="p">;</span> <span class="c1">// 0 ~ 2π</span>
    <span class="k">return</span> <span class="kt">vec2</span><span class="p">(</span><span class="n">cos</span><span class="p">(</span><span class="n">angle</span><span class="p">),</span> <span class="n">sin</span><span class="p">(</span><span class="n">angle</span><span class="p">));</span>
<span class="p">}</span>

<span class="c1">// 2D Perlin Noise</span>
<span class="kt">float</span> <span class="nf">perlin</span><span class="p">(</span><span class="kt">vec2</span> <span class="n">p</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">vec2</span> <span class="n">i</span> <span class="o">=</span> <span class="n">floor</span><span class="p">(</span><span class="n">p</span><span class="p">);</span>
    <span class="kt">vec2</span> <span class="n">f</span> <span class="o">=</span> <span class="n">fract</span><span class="p">(</span><span class="n">p</span><span class="p">);</span>
    <span class="kt">vec2</span> <span class="n">u</span> <span class="o">=</span> <span class="n">f</span> <span class="o">*</span> <span class="n">f</span> <span class="o">*</span> <span class="n">f</span> <span class="o">*</span> <span class="p">(</span><span class="n">f</span> <span class="o">*</span> <span class="p">(</span><span class="n">f</span> <span class="o">*</span> <span class="mi">6</span><span class="p">.</span><span class="mi">0</span> <span class="o">-</span> <span class="mi">15</span><span class="p">.</span><span class="mi">0</span><span class="p">)</span> <span class="o">+</span> <span class="mi">10</span><span class="p">.</span><span class="mi">0</span><span class="p">);</span> <span class="c1">// fade</span>

    <span class="kt">float</span> <span class="n">a</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">gradient</span><span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="kt">vec2</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">)),</span> <span class="n">f</span> <span class="o">-</span> <span class="kt">vec2</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">));</span>
    <span class="kt">float</span> <span class="n">b</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">gradient</span><span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="kt">vec2</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">0</span><span class="p">)),</span> <span class="n">f</span> <span class="o">-</span> <span class="kt">vec2</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">0</span><span class="p">));</span>
    <span class="kt">float</span> <span class="n">c</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">gradient</span><span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="kt">vec2</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">)),</span> <span class="n">f</span> <span class="o">-</span> <span class="kt">vec2</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">));</span>
    <span class="kt">float</span> <span class="n">d</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">gradient</span><span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="kt">vec2</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">)),</span> <span class="n">f</span> <span class="o">-</span> <span class="kt">vec2</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">));</span>

    <span class="k">return</span> <span class="n">mix</span><span class="p">(</span><span class="n">mix</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">u</span><span class="p">.</span><span class="n">x</span><span class="p">),</span> <span class="n">mix</span><span class="p">(</span><span class="n">c</span><span class="p">,</span> <span class="n">d</span><span class="p">,</span> <span class="n">u</span><span class="p">.</span><span class="n">x</span><span class="p">),</span> <span class="n">u</span><span class="p">.</span><span class="n">y</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h2 id="9-常见应用场景">9. 常见应用场景</h2>

<h3 id="地形生成">地形生成</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>高度图 = fBm(x, z)
纹理混合权重 = noise(x, z)  → 草地/泥土/岩石
</code></pre></div></div>

<h3 id="云朵与天空">云朵与天空</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>云密度 = smoothstep(0.4, 0.6, fBm(x, y, t))
</code></pre></div></div>

<h3 id="水面与波浪">水面与波浪</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>水面高度(x, z, t) = noise(x + t, z) + noise(x, z + t * 0.7) * 0.5
法线方向 = gradient of height field
</code></pre></div></div>

<h3 id="程序化纹理">程序化纹理</h3>

<ul>
  <li>大理石纹理：<code class="language-plaintext highlighter-rouge">sin(x + noise(x, y) * 10)</code></li>
  <li>木纹纹理：<code class="language-plaintext highlighter-rouge">sin(sqrt(x² + y²) + noise(x, y) * 5)</code></li>
  <li>火焰效果：动态 noise 配合颜色渐变</li>
</ul>

<h3 id="动画与运动">动画与运动</h3>

<ul>
  <li><strong>相机抖动</strong>：用低频 noise 驱动相机偏移，模拟手持效果</li>
  <li><strong>NPC 行走路径</strong>：noise 生成自然的漫游方向</li>
  <li><strong>粒子系统</strong>：用 noise 场控制粒子受力方向</li>
</ul>

<hr />

<h2 id="10-参数调节技巧">10. 参数调节技巧</h2>

<h3 id="频率frequency--scale">频率（Frequency / Scale）</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>频率低（Scale 大）→ 变化缓慢，大尺度特征（山脉、大陆）
频率高（Scale 小）→ 变化快速，细节特征（石头纹理、草地）
</code></pre></div></div>

<h3 id="振幅amplitude">振幅（Amplitude）</h3>

<p>控制 noise 值的强度范围，通常配合归一化：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 归一化到 [0, 1]
</span><span class="n">normalized</span> <span class="o">=</span> <span class="p">(</span><span class="n">noise_value</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span>
</code></pre></div></div>

<h3 id="octaves-的选择">Octaves 的选择</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2 ~ 3 层：平滑、简洁（适合云朵、水面）
4 ~ 6 层：自然地形（适合山脉、丘陵）
7 ~ 8 层：极丰富细节（适合岩石、土壤）
</code></pre></div></div>

<blockquote>
  <p>注意：层数过多在视觉上收益递减，但计算量线性增加。</p>
</blockquote>

<h3 id="域变形domain-warping">域变形（Domain Warping）</h3>

<p>一种高级技巧，用 noise 对坐标本身进行扭曲，产生极度自然的流动感：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">warped_noise</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">):</span>
    <span class="c1"># 用 noise 偏移输入坐标
</span>    <span class="n">dx</span> <span class="o">=</span> <span class="n">fbm</span><span class="p">(</span><span class="n">x</span> <span class="o">+</span> <span class="mf">1.7</span><span class="p">,</span> <span class="n">y</span> <span class="o">+</span> <span class="mf">9.2</span><span class="p">)</span>
    <span class="n">dy</span> <span class="o">=</span> <span class="n">fbm</span><span class="p">(</span><span class="n">x</span> <span class="o">+</span> <span class="mf">8.3</span><span class="p">,</span> <span class="n">y</span> <span class="o">+</span> <span class="mf">2.8</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">fbm</span><span class="p">(</span><span class="n">x</span> <span class="o">+</span> <span class="mf">4.0</span> <span class="o">*</span> <span class="n">dx</span><span class="p">,</span> <span class="n">y</span> <span class="o">+</span> <span class="mf">4.0</span> <span class="o">*</span> <span class="n">dy</span><span class="p">)</span>
</code></pre></div></div>

<p>这正是 Inigo Quilez 等人创作的许多 Shadertoy 作品的核心技巧。</p>

<hr />

<h2 id="11-常见误区">11. 常见误区</h2>

<h3 id="误区-1perlin-noise-是随机的">误区 1：Perlin Noise 是随机的</h3>

<p>Perlin Noise <strong>不是随机的</strong>。相同的输入坐标永远返回相同的值。它是<strong>确定性的伪随机过程</strong>。真正的随机来自初始化时打乱的置换表（<code class="language-plaintext highlighter-rouge">seed</code>）。</p>

<h3 id="误区-2输出范围是--1-1">误区 2：输出范围是 [-1, 1]</h3>

<p>理论范围是 $[-\sqrt{n/4}, \sqrt{n/4}]$（n 为维度），实际实现中因梯度选取不同而略有差异。在实践中，<strong>不要假设输出严格限制在某个范围内</strong>，应对输出进行归一化或 clamp 处理。</p>

<h3 id="误区-3频率越高细节越好">误区 3：频率越高细节越好</h3>

<p>高频 noise 容易产生<strong>走样（Aliasing）</strong>，在动态场景中会出现闪烁。应根据采样率选择合适的最高频率（参考奈奎斯特定理）。</p>

<h3 id="误区-4用-random-直接生成梯度">误区 4：用 <code class="language-plaintext highlighter-rouge">random()</code> 直接生成梯度</h3>

<p>不少初学者在每次调用时重新生成随机梯度，导致输出不连贯或不可重复。正确做法是<strong>预先构建置换表</strong>，通过查表确定梯度。</p>

<hr />

<h2 id="12-参考资料">12. 参考资料</h2>

<ul>
  <li>Ken Perlin 改进版：<a href="https://mrl.cs.nyu.edu/~perlin/paper445.pdf">Improving Noise (SIGGRAPH 2002)</a></li>
  <li>Inigo Quilez 的 Domain Warping：<a href="https://iquilezles.org/articles/warp/">iquilezles.org/articles/warp</a></li>
  <li>The Book of Shaders（强烈推荐）：<a href="https://thebookofshaders.com/11/">thebookofshaders.com/11/</a></li>
  <li>Wikipedia - Perlin Noise：<a href="https://en.wikipedia.org/wiki/Perlin_noise">en.wikipedia.org/wiki/Perlin_noise</a></li>
  <li>OpenSimplex2（无专利替代方案）：<a href="https://github.com/KdotJPG/OpenSimplex2">github.com/KdotJPG/OpenSimplex2</a></li>
</ul>

<hr />]]></content><author><name></name></author><summary type="html"><![CDATA[“自然界中没有真正的随机，只有我们尚未理解的规律。” —— Perlin Noise 的哲学]]></summary></entry><entry xml:lang="en"><title type="html">Python Async IO Learning Notes</title><link href="https://ryviuszero.github.io/en/posts/python-async-io-notes/" rel="alternate" type="text/html" title="Python Async IO Learning Notes" /><published>2024-05-18T08:34:01+00:00</published><updated>2024-05-18T08:34:01+00:00</updated><id>https://ryviuszero.github.io/en/posts/Python-Async-IO-Notes</id><content type="html" xml:base="https://ryviuszero.github.io/en/posts/python-async-io-notes/"><![CDATA[<blockquote>
  <p>Async is not “faster,” but “smarter waiting.”</p>
</blockquote>

<hr />

<h2 id="table-of-contents">Table of Contents</h2>

<ol>
  <li><a href="#1-why-do-we-need-async-io">Why Do We Need Async IO?</a></li>
  <li><a href="#2-core-concepts">Core Concepts</a></li>
  <li><a href="#3-asyncio-basics">asyncio Basics</a></li>
  <li><a href="#4-tasks-and-concurrency">Tasks and Concurrency</a></li>
  <li><a href="#5-async-context-managers-and-iterators">Async Context Managers and Iterators</a></li>
  <li><a href="#6-asyncio-synchronization-primitives">asyncio Synchronization Primitives</a></li>
  <li><a href="#7-queue-producer-consumer-pattern">Queue: Producer-Consumer Pattern</a></li>
  <li><a href="#8-async-network-io-aiohttp">Async Network IO: aiohttp</a></li>
  <li><a href="#9-async-file-io-aiofiles">Async File IO: aiofiles</a></li>
  <li><a href="#10-async-databases">Async Databases</a></li>
  <li><a href="#11-calling-async-from-sync-code">Calling Async from Sync Code</a></li>
  <li><a href="#12-event-loop-internals">Event Loop Internals</a></li>
  <li><a href="#13-common-pitfalls-and-best-practices">Common Pitfalls and Best Practices</a></li>
  <li><a href="#14-performance-comparison">Performance Comparison</a></li>
</ol>

<hr />

<h2 id="1-why-do-we-need-async-io">1. Why Do We Need Async IO?</h2>

<h3 id="three-concurrency-models-compared">Three Concurrency Models Compared</h3>

<table>
  <thead>
    <tr>
      <th>Model</th>
      <th>Mechanism</th>
      <th>Use Case</th>
      <th>Drawback</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Multiprocessing</strong></td>
      <td>Multiple CPU cores in parallel</td>
      <td>CPU-intensive (compute, compression)</td>
      <td>High memory, expensive context switches</td>
    </tr>
    <tr>
      <td><strong>Multithreading</strong></td>
      <td>OS-level thread scheduling</td>
      <td>IO-intensive (with GIL limits)</td>
      <td>GIL, race conditions, deadlocks</td>
    </tr>
    <tr>
      <td><strong>Async IO</strong></td>
      <td>Single-threaded event loop</td>
      <td>IO-intensive (network, files)</td>
      <td>Not suitable for CPU-bound work</td>
    </tr>
  </tbody>
</table>

<h3 id="the-core-problem-wasted-waiting">The Core Problem: Wasted Waiting</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Synchronous mode (one chef cooking one dish):
[Request A sent] → [wait 200ms] → [process] → [Request B sent] → [wait 200ms] → ...
Total time: 200ms × N

Async mode (one chef managing multiple dishes):
[Request A sent] ↘
[Request B sent]  → [waiting...] → [A returns, process] → [B returns, process]
[Request C sent] ↗
Total time: ≈ 200ms (longest single request)
</code></pre></div></div>

<p><strong>Key insight:</strong> Async IO’s essence is <strong>doing other work while waiting for IO</strong>, not true parallel computation.</p>

<hr />

<h2 id="2-core-concepts">2. Core Concepts</h2>

<h3 id="coroutines">Coroutines</h3>

<p>Functions defined with <code class="language-plaintext highlighter-rouge">async def</code> return a coroutine object when called, <strong>not executing immediately</strong>:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">say_hello</span><span class="p">():</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"Hello"</span><span class="p">)</span>

<span class="c1"># Calling doesn't execute! Just creates a coroutine object
</span><span class="n">coro</span> <span class="o">=</span> <span class="n">say_hello</span><span class="p">()</span>
<span class="k">print</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">coro</span><span class="p">))</span>  <span class="c1"># &lt;class 'coroutine'&gt;
</span>
<span class="c1"># Must run via event loop
</span><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">say_hello</span><span class="p">())</span>  <span class="c1"># This prints Hello
</span></code></pre></div></div>

<h3 id="event-loop">Event Loop</h3>

<p>The “control center” of async programs, responsible for:</p>
<ul>
  <li>Running coroutines</li>
  <li>Listening for IO events</li>
  <li>Scheduling callbacks</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌─────────────────────────────────────┐
│          Event Loop                  │
│                                     │
│  Task A ──► await IO ──► suspended  │
│  Task B ◄── IO done  ◄── wake up    │
│  Task C ──► await IO ──► suspended  │
│  Task A ◄── IO done  ◄── wake up    │
└─────────────────────────────────────┘
</code></pre></div></div>

<h3 id="the-await-keyword">The await Keyword</h3>

<p><code class="language-plaintext highlighter-rouge">await</code> can only be used inside <code class="language-plaintext highlighter-rouge">async def</code> functions. It:</p>
<ol>
  <li>Pauses the current coroutine, yielding control back to the event loop</li>
  <li>Waits for the awaited object to complete</li>
  <li>Resumes execution after completion, returning the result</li>
</ol>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">fetch</span><span class="p">():</span>
    <span class="c1"># Pauses here; event loop runs other tasks
</span>    <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">some_async_operation</span><span class="p">()</span>
    <span class="c1"># Resumes here after IO completes
</span>    <span class="k">return</span> <span class="n">result</span>
</code></pre></div></div>

<h3 id="awaitable-objects">Awaitable Objects</h3>

<p>Three types of objects can be awaited:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 1. Coroutine
</span><span class="k">async</span> <span class="k">def</span> <span class="nf">coro</span><span class="p">():</span> <span class="p">...</span>
<span class="k">await</span> <span class="n">coro</span><span class="p">()</span>

<span class="c1"># 2. Task (created via asyncio.create_task)
</span><span class="n">task</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">coro</span><span class="p">())</span>
<span class="k">await</span> <span class="n">task</span>

<span class="c1"># 3. Future (low-level, rarely used directly)
</span><span class="n">future</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">get_event_loop</span><span class="p">().</span><span class="n">create_future</span><span class="p">()</span>
<span class="k">await</span> <span class="n">future</span>
</code></pre></div></div>

<hr />

<h2 id="3-asyncio-basics">3. asyncio Basics</h2>

<h3 id="asynciorun-program-entry-point">asyncio.run(): Program Entry Point</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"Start"</span><span class="p">)</span>
    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>  <span class="c1"># Simulate IO wait
</span>    <span class="k">print</span><span class="p">(</span><span class="s">"Done"</span><span class="p">)</span>

<span class="c1"># Python 3.7+ standard entry point
</span><span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<h3 id="asynciosleep">asyncio.sleep()</h3>

<p>Async version of <code class="language-plaintext highlighter-rouge">time.sleep()</code>, <strong>doesn’t block the event loop</strong> during wait:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">import</span> <span class="nn">time</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">task</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">delay</span><span class="p">):</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s"> start"</span><span class="p">)</span>
    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="n">delay</span><span class="p">)</span>  <span class="c1"># ✅ Async wait
</span>    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s"> done"</span><span class="p">)</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span>
    <span class="c1"># Sequential execution (total time = 1 + 2 = 3s)
</span>    <span class="k">await</span> <span class="n">task</span><span class="p">(</span><span class="s">"A"</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="k">await</span> <span class="n">task</span><span class="p">(</span><span class="s">"B"</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Elapsed: </span><span class="si">{</span><span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="si">:</span><span class="p">.</span><span class="mi">1</span><span class="n">f</span><span class="si">}</span><span class="s">s"</span><span class="p">)</span>

<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
<span class="c1"># Elapsed: 3.0s
</span></code></pre></div></div>

<hr />

<h2 id="4-tasks-and-concurrency">4. Tasks and Concurrency</h2>

<h3 id="asynciocreate_task-true-concurrency">asyncio.create_task(): True Concurrency</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">import</span> <span class="nn">time</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">task</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">delay</span><span class="p">):</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s"> start"</span><span class="p">)</span>
    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="n">delay</span><span class="p">)</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s"> done"</span><span class="p">)</span>
    <span class="k">return</span> <span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s">'s result"</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span>

    <span class="c1"># Create tasks, scheduled immediately (but not yet running)
</span>    <span class="n">task_a</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">task</span><span class="p">(</span><span class="s">"A"</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span>
    <span class="n">task_b</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">task</span><span class="p">(</span><span class="s">"B"</span><span class="p">,</span> <span class="mi">2</span><span class="p">))</span>
    <span class="n">task_c</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">task</span><span class="p">(</span><span class="s">"C"</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span>

    <span class="c1"># Wait for all tasks
</span>    <span class="n">result_a</span> <span class="o">=</span> <span class="k">await</span> <span class="n">task_a</span>
    <span class="n">result_b</span> <span class="o">=</span> <span class="k">await</span> <span class="n">task_b</span>
    <span class="n">result_c</span> <span class="o">=</span> <span class="k">await</span> <span class="n">task_c</span>

    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Elapsed: </span><span class="si">{</span><span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="si">:</span><span class="p">.</span><span class="mi">1</span><span class="n">f</span><span class="si">}</span><span class="s">s"</span><span class="p">)</span>
    <span class="c1"># Elapsed: 2.0s (concurrent; total = longest single task)
</span>
<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<h3 id="asynciogather-bulk-concurrency">asyncio.gather(): Bulk Concurrency</h3>

<p>Most common concurrent pattern—waits for all coroutines and collects results:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>  <span class="c1"># Simulate network request
</span>    <span class="k">return</span> <span class="sa">f</span><span class="s">"Data from </span><span class="si">{</span><span class="n">url</span><span class="si">}</span><span class="s">"</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">urls</span> <span class="o">=</span> <span class="p">[</span><span class="s">"url1"</span><span class="p">,</span> <span class="s">"url2"</span><span class="p">,</span> <span class="s">"url3"</span><span class="p">,</span> <span class="s">"url4"</span><span class="p">,</span> <span class="s">"url5"</span><span class="p">]</span>

    <span class="c1"># Execute all requests concurrently
</span>    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span>
        <span class="o">*</span><span class="p">[</span><span class="n">fetch</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">urls</span><span class="p">]</span>
    <span class="p">)</span>

    <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="n">r</span><span class="p">)</span>

<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
<span class="c1"># 5 requests sent concurrently; total time ≈ 1s
</span></code></pre></div></div>

<p><strong>Exception handling in gather:</strong></p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Default: any exception raises immediately
</span><span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span><span class="n">coro1</span><span class="p">(),</span> <span class="n">coro2</span><span class="p">(),</span> <span class="n">coro3</span><span class="p">())</span>

<span class="c1"># return_exceptions=True: exceptions returned as results, no interruption
</span><span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span>
    <span class="n">coro1</span><span class="p">(),</span> <span class="n">coro2</span><span class="p">(),</span> <span class="n">coro3</span><span class="p">(),</span>
    <span class="n">return_exceptions</span><span class="o">=</span><span class="bp">True</span>
<span class="p">)</span>
<span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">:</span>
    <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="nb">Exception</span><span class="p">):</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Error: </span><span class="si">{</span><span class="n">r</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Success: </span><span class="si">{</span><span class="n">r</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="asynciowait-fine-grained-control">asyncio.wait(): Fine-Grained Control</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">task</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">n</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">asyncio</span><span class="p">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">task</span><span class="p">(</span><span class="n">i</span><span class="p">))</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="p">[</span><span class="mi">3</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">]]</span>

    <span class="c1"># Continue when first completes
</span>    <span class="n">done</span><span class="p">,</span> <span class="n">pending</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">wait</span><span class="p">(</span>
        <span class="n">tasks</span><span class="p">,</span>
        <span class="n">return_when</span><span class="o">=</span><span class="n">asyncio</span><span class="p">.</span><span class="n">FIRST_COMPLETED</span>
    <span class="p">)</span>

    <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">done</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Done: </span><span class="si">{</span><span class="n">t</span><span class="p">.</span><span class="n">result</span><span class="p">()</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>

    <span class="c1"># Cancel remaining
</span>    <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">pending</span><span class="p">:</span>
        <span class="n">t</span><span class="p">.</span><span class="n">cancel</span><span class="p">()</span>

<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th><code class="language-plaintext highlighter-rouge">return_when</code> Parameter</th>
      <th>Meaning</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ALL_COMPLETED</code> (default)</td>
      <td>Return when all complete</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">FIRST_COMPLETED</code></td>
      <td>Return when first completes</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">FIRST_EXCEPTION</code></td>
      <td>Return when first fails</td>
    </tr>
  </tbody>
</table>

<h3 id="asynciotimeout-timeout-control-python-311">asyncio.timeout(): Timeout Control (Python 3.11+)</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="k">async</span> <span class="k">with</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">timeout</span><span class="p">(</span><span class="mf">5.0</span><span class="p">):</span>
            <span class="k">await</span> <span class="n">long_running_task</span><span class="p">()</span>
    <span class="k">except</span> <span class="nb">TimeoutError</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"Timed out!"</span><span class="p">)</span>

<span class="c1"># Python 3.10 and below: use wait_for
</span><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">wait_for</span><span class="p">(</span>
            <span class="n">long_running_task</span><span class="p">(),</span>
            <span class="n">timeout</span><span class="o">=</span><span class="mf">5.0</span>
        <span class="p">)</span>
    <span class="k">except</span> <span class="n">asyncio</span><span class="p">.</span><span class="nb">TimeoutError</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"Timed out!"</span><span class="p">)</span>
</code></pre></div></div>

<hr />

<h2 id="5-async-context-managers-and-iterators">5. Async Context Managers and Iterators</h2>

<h3 id="async-context-managers">Async Context Managers</h3>

<p>Implement <code class="language-plaintext highlighter-rouge">__aenter__</code> and <code class="language-plaintext highlighter-rouge">__aexit__</code>:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">AsyncDBConnection</span><span class="p">:</span>
    <span class="k">async</span> <span class="k">def</span> <span class="nf">__aenter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"Connecting to database"</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">conn</span> <span class="o">=</span> <span class="k">await</span> <span class="n">create_connection</span><span class="p">()</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">conn</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">__aexit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">exc_type</span><span class="p">,</span> <span class="n">exc</span><span class="p">,</span> <span class="n">tb</span><span class="p">):</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"Closing connection"</span><span class="p">)</span>
        <span class="k">await</span> <span class="bp">self</span><span class="p">.</span><span class="n">conn</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">AsyncDBConnection</span><span class="p">()</span> <span class="k">as</span> <span class="n">conn</span><span class="p">:</span>
        <span class="k">await</span> <span class="n">conn</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span><span class="s">"SELECT 1"</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="async-iterators">Async Iterators</h3>

<p>Implement <code class="language-plaintext highlighter-rouge">__aiter__</code> and <code class="language-plaintext highlighter-rouge">__anext__</code>:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">AsyncRange</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">n</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">n</span> <span class="o">=</span> <span class="n">n</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span>

    <span class="k">def</span> <span class="nf">__aiter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="bp">self</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">__anext__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">i</span> <span class="o">&gt;=</span> <span class="bp">self</span><span class="p">.</span><span class="n">n</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nb">StopAsyncIteration</span>
        <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>  <span class="c1"># Simulate async operation
</span>        <span class="n">val</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">i</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
        <span class="k">return</span> <span class="n">val</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="k">async</span> <span class="k">for</span> <span class="n">num</span> <span class="ow">in</span> <span class="n">AsyncRange</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
        <span class="k">print</span><span class="p">(</span><span class="n">num</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="async-generators">Async Generators</h3>

<p>Cleaner syntax:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">async_range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
        <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
        <span class="k">yield</span> <span class="n">i</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="k">async</span> <span class="k">for</span> <span class="n">num</span> <span class="ow">in</span> <span class="n">async_range</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
        <span class="k">print</span><span class="p">(</span><span class="n">num</span><span class="p">)</span>

    <span class="c1"># Also supports async list comprehension
</span>    <span class="n">results</span> <span class="o">=</span> <span class="p">[</span><span class="n">i</span> <span class="k">async</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">async_range</span><span class="p">(</span><span class="mi">5</span><span class="p">)]</span>
</code></pre></div></div>

<hr />

<h2 id="6-asyncio-synchronization-primitives">6. asyncio Synchronization Primitives</h2>

<p>When multiple coroutines share resources, synchronization is needed (like locks in multithreading).</p>

<h3 id="lock-mutual-exclusion">Lock: Mutual Exclusion</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>

<span class="n">lock</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">Lock</span><span class="p">()</span>
<span class="n">shared_resource</span> <span class="o">=</span> <span class="mi">0</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">worker</span><span class="p">(</span><span class="n">name</span><span class="p">):</span>
    <span class="k">global</span> <span class="n">shared_resource</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">lock</span><span class="p">:</span>  <span class="c1"># Acquire lock; others wait
</span>        <span class="n">val</span> <span class="o">=</span> <span class="n">shared_resource</span>
        <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>  <span class="c1"># Simulate work
</span>        <span class="n">shared_resource</span> <span class="o">=</span> <span class="n">val</span> <span class="o">+</span> <span class="mi">1</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s">: </span><span class="si">{</span><span class="n">shared_resource</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="n">worker</span><span class="p">(</span><span class="sa">f</span><span class="s">"W</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s">"</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">)])</span>

<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<h3 id="event-signal-notification">Event: Signal Notification</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">waiter</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s"> waiting..."</span><span class="p">)</span>
    <span class="k">await</span> <span class="n">event</span><span class="p">.</span><span class="n">wait</span><span class="p">()</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s"> received signal, continuing"</span><span class="p">)</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">setter</span><span class="p">(</span><span class="n">event</span><span class="p">):</span>
    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"Triggering event!"</span><span class="p">)</span>
    <span class="n">event</span><span class="p">.</span><span class="nb">set</span><span class="p">()</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">event</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">Event</span><span class="p">()</span>
    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span>
        <span class="n">waiter</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="s">"W1"</span><span class="p">),</span>
        <span class="n">waiter</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="s">"W2"</span><span class="p">),</span>
        <span class="n">setter</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
    <span class="p">)</span>
</code></pre></div></div>

<h3 id="semaphore-limit-concurrent-tasks">Semaphore: Limit Concurrent Tasks</h3>

<p>Most commonly used! Controls max concurrent running coroutines:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">semaphore</span><span class="p">):</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">semaphore</span><span class="p">:</span>  <span class="c1"># Max 10 concurrent
</span>        <span class="k">async</span> <span class="k">with</span> <span class="n">session</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">as</span> <span class="n">resp</span><span class="p">:</span>
            <span class="k">return</span> <span class="k">await</span> <span class="n">resp</span><span class="p">.</span><span class="n">text</span><span class="p">()</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">semaphore</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">Semaphore</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>  <span class="c1"># Max concurrency: 10
</span>    <span class="n">urls</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s">"https://example.com/</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s">"</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">)]</span>

    <span class="k">async</span> <span class="k">with</span> <span class="n">aiohttp</span><span class="p">.</span><span class="n">ClientSession</span><span class="p">()</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
        <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">fetch</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">semaphore</span><span class="p">)</span> <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">urls</span><span class="p">]</span>
        <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</code></pre></div></div>

<hr />

<h2 id="7-queue-producer-consumer-pattern">7. Queue: Producer-Consumer Pattern</h2>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">producer</span><span class="p">(</span><span class="n">queue</span><span class="p">,</span> <span class="n">n</span><span class="p">):</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
        <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.5</span><span class="p">)</span>  <span class="c1"># Simulate production
</span>        <span class="k">await</span> <span class="n">queue</span><span class="p">.</span><span class="n">put</span><span class="p">(</span><span class="n">i</span><span class="p">)</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Produced: </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s">, queue size: </span><span class="si">{</span><span class="n">queue</span><span class="p">.</span><span class="n">qsize</span><span class="p">()</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
    <span class="k">await</span> <span class="n">queue</span><span class="p">.</span><span class="n">put</span><span class="p">(</span><span class="bp">None</span><span class="p">)</span>  <span class="c1"># End signal
</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">consumer</span><span class="p">(</span><span class="n">queue</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
    <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
        <span class="n">item</span> <span class="o">=</span> <span class="k">await</span> <span class="n">queue</span><span class="p">.</span><span class="n">get</span><span class="p">()</span>
        <span class="k">if</span> <span class="n">item</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
            <span class="n">queue</span><span class="p">.</span><span class="n">task_done</span><span class="p">()</span>
            <span class="k">break</span>
        <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>  <span class="c1"># Simulate consumption
</span>        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"[</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s">] Consumed: </span><span class="si">{</span><span class="n">item</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
        <span class="n">queue</span><span class="p">.</span><span class="n">task_done</span><span class="p">()</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">queue</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">Queue</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span>  <span class="c1"># Max capacity: 5
</span>
    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span>
        <span class="n">producer</span><span class="p">(</span><span class="n">queue</span><span class="p">,</span> <span class="mi">10</span><span class="p">),</span>
        <span class="n">consumer</span><span class="p">(</span><span class="n">queue</span><span class="p">,</span> <span class="s">"Consumer A"</span><span class="p">),</span>
        <span class="n">consumer</span><span class="p">(</span><span class="n">queue</span><span class="p">,</span> <span class="s">"Consumer B"</span><span class="p">),</span>
    <span class="p">)</span>

<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<hr />

<h2 id="8-async-network-io-aiohttp">8. Async Network IO: aiohttp</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip <span class="nb">install </span>aiohttp
</code></pre></div></div>

<h3 id="basic-get-request">Basic GET Request</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">import</span> <span class="nn">aiohttp</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="n">url</span><span class="p">):</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">session</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">as</span> <span class="n">response</span><span class="p">:</span>
        <span class="k">return</span> <span class="k">await</span> <span class="n">response</span><span class="p">.</span><span class="n">json</span><span class="p">()</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">aiohttp</span><span class="p">.</span><span class="n">ClientSession</span><span class="p">()</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
        <span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">fetch</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="s">"https://api.github.com/users/octocat"</span><span class="p">)</span>
        <span class="k">print</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s">"name"</span><span class="p">])</span>

<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<h3 id="concurrent-url-fetching">Concurrent URL Fetching</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">import</span> <span class="nn">aiohttp</span>
<span class="kn">import</span> <span class="nn">time</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="n">url</span><span class="p">):</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="k">async</span> <span class="k">with</span> <span class="n">session</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="n">aiohttp</span><span class="p">.</span><span class="n">ClientTimeout</span><span class="p">(</span><span class="n">total</span><span class="o">=</span><span class="mi">10</span><span class="p">))</span> <span class="k">as</span> <span class="n">resp</span><span class="p">:</span>
            <span class="k">return</span> <span class="p">{</span><span class="s">"url"</span><span class="p">:</span> <span class="n">url</span><span class="p">,</span> <span class="s">"status"</span><span class="p">:</span> <span class="n">resp</span><span class="p">.</span><span class="n">status</span><span class="p">,</span> <span class="s">"data"</span><span class="p">:</span> <span class="k">await</span> <span class="n">resp</span><span class="p">.</span><span class="n">text</span><span class="p">()}</span>
    <span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
        <span class="k">return</span> <span class="p">{</span><span class="s">"url"</span><span class="p">:</span> <span class="n">url</span><span class="p">,</span> <span class="s">"error"</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)}</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">urls</span> <span class="o">=</span> <span class="p">[</span>
        <span class="s">"https://httpbin.org/delay/1"</span><span class="p">,</span>
        <span class="s">"https://httpbin.org/delay/2"</span><span class="p">,</span>
        <span class="s">"https://httpbin.org/delay/1"</span><span class="p">,</span>
    <span class="p">]</span>

    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">aiohttp</span><span class="p">.</span><span class="n">ClientSession</span><span class="p">()</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
        <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="n">fetch</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="n">url</span><span class="p">)</span> <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">urls</span><span class="p">])</span>

    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Elapsed: </span><span class="si">{</span><span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="si">:</span><span class="p">.</span><span class="mi">1</span><span class="n">f</span><span class="si">}</span><span class="s">s"</span><span class="p">)</span>  <span class="c1"># ≈ 2s, not 4s
</span>    <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="n">r</span><span class="p">[</span><span class="s">"url"</span><span class="p">],</span> <span class="n">r</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"status"</span><span class="p">,</span> <span class="n">r</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"error"</span><span class="p">)))</span>

<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<h3 id="post-request">POST Request</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">post_data</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">payload</span><span class="p">):</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">session</span><span class="p">.</span><span class="n">post</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">payload</span><span class="p">)</span> <span class="k">as</span> <span class="n">resp</span><span class="p">:</span>
        <span class="k">return</span> <span class="k">await</span> <span class="n">resp</span><span class="p">.</span><span class="n">json</span><span class="p">()</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">aiohttp</span><span class="p">.</span><span class="n">ClientSession</span><span class="p">()</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
        <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">post_data</span><span class="p">(</span>
            <span class="n">session</span><span class="p">,</span>
            <span class="s">"https://httpbin.org/post"</span><span class="p">,</span>
            <span class="p">{</span><span class="s">"key"</span><span class="p">:</span> <span class="s">"value"</span><span class="p">,</span> <span class="s">"name"</span><span class="p">:</span> <span class="s">"asyncio"</span><span class="p">}</span>
        <span class="p">)</span>
        <span class="k">print</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</code></pre></div></div>

<hr />

<h2 id="9-async-file-io-aiofiles">9. Async File IO: aiofiles</h2>

<p>Standard <code class="language-plaintext highlighter-rouge">open()</code> is synchronous and blocks the event loop. Use <code class="language-plaintext highlighter-rouge">aiofiles</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip <span class="nb">install </span>aiofiles
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">import</span> <span class="nn">aiofiles</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">read_file</span><span class="p">(</span><span class="n">path</span><span class="p">):</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">aiofiles</span><span class="p">.</span><span class="nb">open</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="s">"r"</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s">"utf-8"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
        <span class="k">return</span> <span class="k">await</span> <span class="n">f</span><span class="p">.</span><span class="n">read</span><span class="p">()</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">write_file</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">content</span><span class="p">):</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">aiofiles</span><span class="p">.</span><span class="nb">open</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="s">"w"</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s">"utf-8"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
        <span class="k">await</span> <span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="c1"># Concurrently read multiple files
</span>    <span class="n">contents</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span>
        <span class="n">read_file</span><span class="p">(</span><span class="s">"file1.txt"</span><span class="p">),</span>
        <span class="n">read_file</span><span class="p">(</span><span class="s">"file2.txt"</span><span class="p">),</span>
        <span class="n">read_file</span><span class="p">(</span><span class="s">"file3.txt"</span><span class="p">),</span>
    <span class="p">)</span>
    <span class="k">for</span> <span class="n">content</span> <span class="ow">in</span> <span class="n">contents</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="n">content</span><span class="p">[:</span><span class="mi">100</span><span class="p">])</span>

<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<hr />

<h2 id="10-async-databases">10. Async Databases</h2>

<h3 id="asyncpg-postgresql">asyncpg (PostgreSQL)</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip <span class="nb">install </span>asyncpg
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">import</span> <span class="nn">asyncpg</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="c1"># Create connection
</span>    <span class="n">conn</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncpg</span><span class="p">.</span><span class="n">connect</span><span class="p">(</span>
        <span class="n">host</span><span class="o">=</span><span class="s">"localhost"</span><span class="p">,</span> <span class="n">database</span><span class="o">=</span><span class="s">"mydb"</span><span class="p">,</span>
        <span class="n">user</span><span class="o">=</span><span class="s">"user"</span><span class="p">,</span> <span class="n">password</span><span class="o">=</span><span class="s">"password"</span>
    <span class="p">)</span>

    <span class="c1"># Query
</span>    <span class="n">rows</span> <span class="o">=</span> <span class="k">await</span> <span class="n">conn</span><span class="p">.</span><span class="n">fetch</span><span class="p">(</span><span class="s">"SELECT id, name FROM users WHERE active = $1"</span><span class="p">,</span> <span class="bp">True</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">rows</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="s">"id"</span><span class="p">],</span> <span class="n">row</span><span class="p">[</span><span class="s">"name"</span><span class="p">])</span>

    <span class="c1"># Insert
</span>    <span class="k">await</span> <span class="n">conn</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span>
        <span class="s">"INSERT INTO users(name, email) VALUES($1, $2)"</span><span class="p">,</span>
        <span class="s">"John Doe"</span><span class="p">,</span> <span class="s">"john@example.com"</span>
    <span class="p">)</span>

    <span class="k">await</span> <span class="n">conn</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>

<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<h3 id="connection-pool-production-required">Connection Pool (Production Required)</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">pool</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncpg</span><span class="p">.</span><span class="n">create_pool</span><span class="p">(</span>
        <span class="n">host</span><span class="o">=</span><span class="s">"localhost"</span><span class="p">,</span> <span class="n">database</span><span class="o">=</span><span class="s">"mydb"</span><span class="p">,</span>
        <span class="n">user</span><span class="o">=</span><span class="s">"user"</span><span class="p">,</span> <span class="n">password</span><span class="o">=</span><span class="s">"password"</span><span class="p">,</span>
        <span class="n">min_size</span><span class="o">=</span><span class="mi">5</span><span class="p">,</span> <span class="n">max_size</span><span class="o">=</span><span class="mi">20</span>
    <span class="p">)</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">query</span><span class="p">(</span><span class="n">user_id</span><span class="p">):</span>
        <span class="k">async</span> <span class="k">with</span> <span class="n">pool</span><span class="p">.</span><span class="n">acquire</span><span class="p">()</span> <span class="k">as</span> <span class="n">conn</span><span class="p">:</span>
            <span class="k">return</span> <span class="k">await</span> <span class="n">conn</span><span class="p">.</span><span class="n">fetchrow</span><span class="p">(</span><span class="s">"SELECT * FROM users WHERE id = $1"</span><span class="p">,</span> <span class="n">user_id</span><span class="p">)</span>

    <span class="c1"># Concurrent queries
</span>    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="n">query</span><span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">11</span><span class="p">)])</span>

    <span class="k">await</span> <span class="n">pool</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>
</code></pre></div></div>

<h3 id="aiosqlite-sqlite">aiosqlite (SQLite)</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip <span class="nb">install </span>aiosqlite
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">aiosqlite</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">aiosqlite</span><span class="p">.</span><span class="n">connect</span><span class="p">(</span><span class="s">"test.db"</span><span class="p">)</span> <span class="k">as</span> <span class="n">db</span><span class="p">:</span>
        <span class="k">await</span> <span class="n">db</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span><span class="s">"""
            CREATE TABLE IF NOT EXISTS users (
                id INTEGER PRIMARY KEY,
                name TEXT
            )
        """</span><span class="p">)</span>
        <span class="k">await</span> <span class="n">db</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span><span class="s">"INSERT INTO users(name) VALUES(?)"</span><span class="p">,</span> <span class="p">(</span><span class="s">"John Doe"</span><span class="p">,))</span>
        <span class="k">await</span> <span class="n">db</span><span class="p">.</span><span class="n">commit</span><span class="p">()</span>

        <span class="k">async</span> <span class="k">with</span> <span class="n">db</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span><span class="s">"SELECT * FROM users"</span><span class="p">)</span> <span class="k">as</span> <span class="n">cursor</span><span class="p">:</span>
            <span class="k">async</span> <span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">cursor</span><span class="p">:</span>
                <span class="k">print</span><span class="p">(</span><span class="n">row</span><span class="p">)</span>
</code></pre></div></div>

<hr />

<h2 id="11-calling-async-from-sync-code">11. Calling Async from Sync Code</h2>

<p>Sometimes you need to call async functions from sync code (regular functions, Django views, etc.):</p>

<h3 id="asynciorun-simplest">asyncio.run() (Simplest)</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">sync_function</span><span class="p">():</span>
    <span class="n">result</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">async_function</span><span class="p">())</span>
    <span class="k">return</span> <span class="n">result</span>
</code></pre></div></div>

<blockquote>
  <p><strong>Note:</strong> <code class="language-plaintext highlighter-rouge">asyncio.run()</code> creates a new event loop and <strong>cannot be called inside an existing event loop</strong>.</p>
</blockquote>

<h3 id="looprun_until_complete">loop.run_until_complete()</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">sync_function</span><span class="p">():</span>
    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">new_event_loop</span><span class="p">()</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">result</span> <span class="o">=</span> <span class="n">loop</span><span class="p">.</span><span class="n">run_until_complete</span><span class="p">(</span><span class="n">async_function</span><span class="p">())</span>
    <span class="k">finally</span><span class="p">:</span>
        <span class="n">loop</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>
    <span class="k">return</span> <span class="n">result</span>
</code></pre></div></div>

<h3 id="running-sync-blocking-functions-in-async">Running Sync Blocking Functions in Async</h3>

<p>For CPU-intensive or legacy blocking code, use <code class="language-plaintext highlighter-rouge">run_in_executor</code> with thread pool:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span>

<span class="k">def</span> <span class="nf">blocking_io</span><span class="p">():</span>
    <span class="kn">import</span> <span class="nn">time</span>
    <span class="n">time</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>      <span class="c1"># Sync blocking operation
</span>    <span class="k">return</span> <span class="s">"done"</span>

<span class="k">def</span> <span class="nf">cpu_heavy</span><span class="p">():</span>
    <span class="k">return</span> <span class="nb">sum</span><span class="p">(</span><span class="n">i</span> <span class="o">*</span> <span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="o">**</span><span class="mi">7</span><span class="p">))</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">get_event_loop</span><span class="p">()</span>

    <span class="c1"># Run in thread pool (for IO-blocking)
</span>    <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">loop</span><span class="p">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="bp">None</span><span class="p">,</span> <span class="n">blocking_io</span><span class="p">)</span>

    <span class="c1"># Run in process pool (for CPU-heavy)
</span>    <span class="k">with</span> <span class="n">ProcessPoolExecutor</span><span class="p">()</span> <span class="k">as</span> <span class="n">pool</span><span class="p">:</span>
        <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">loop</span><span class="p">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="n">pool</span><span class="p">,</span> <span class="n">cpu_heavy</span><span class="p">)</span>
</code></pre></div></div>

<hr />

<h2 id="12-event-loop-internals">12. Event Loop Internals</h2>

<h3 id="getting-the-current-event-loop">Getting the Current Event Loop</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">get_event_loop</span><span class="p">()</span>       <span class="c1"># Get current loop
</span>    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">get_running_loop</span><span class="p">()</span>     <span class="c1"># Inside coroutine (recommended)
</span></code></pre></div></div>

<h3 id="scheduling-callbacks">Scheduling Callbacks</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">get_running_loop</span><span class="p">()</span>

    <span class="c1"># Execute next iteration (regular function, not coroutine)
</span>    <span class="n">loop</span><span class="p">.</span><span class="n">call_soon</span><span class="p">(</span><span class="k">print</span><span class="p">,</span> <span class="s">"Execute immediately"</span><span class="p">)</span>

    <span class="c1"># Delayed execution
</span>    <span class="n">loop</span><span class="p">.</span><span class="n">call_later</span><span class="p">(</span><span class="mf">2.0</span><span class="p">,</span> <span class="k">print</span><span class="p">,</span> <span class="s">"Execute after 2 seconds"</span><span class="p">)</span>

    <span class="c1"># Execute at timestamp
</span>    <span class="n">loop</span><span class="p">.</span><span class="n">call_at</span><span class="p">(</span><span class="n">loop</span><span class="p">.</span><span class="n">time</span><span class="p">()</span> <span class="o">+</span> <span class="mf">5.0</span><span class="p">,</span> <span class="k">print</span><span class="p">,</span> <span class="s">"Execute after 5 seconds"</span><span class="p">)</span>

    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="uvloop-high-performance-event-loop">uvloop: High-Performance Event Loop</h3>

<p>C-based event loop implementation; 2-4x faster than default:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip <span class="nb">install </span>uvloop
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">uvloop</span>

<span class="n">asyncio</span><span class="p">.</span><span class="n">set_event_loop_policy</span><span class="p">(</span><span class="n">uvloop</span><span class="p">.</span><span class="n">EventLoopPolicy</span><span class="p">())</span>
<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<hr />

<h2 id="13-common-pitfalls-and-best-practices">13. Common Pitfalls and Best Practices</h2>

<h3 id="-pitfall-1-using-sync-blocking-calls-in-coroutines">❌ Pitfall 1: Using Sync Blocking Calls in Coroutines</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">time</span>
<span class="kn">import</span> <span class="nn">requests</span>  <span class="c1"># Sync library
</span>
<span class="c1"># ❌ Wrong: Blocks entire event loop
</span><span class="k">async</span> <span class="k">def</span> <span class="nf">bad_fetch</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
    <span class="n">time</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>              <span class="c1"># Blocks!
</span>    <span class="k">return</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>   <span class="c1"># Blocks!
</span>
<span class="c1"># ✅ Correct: Use async library
</span><span class="k">async</span> <span class="k">def</span> <span class="nf">good_fetch</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">aiohttp</span><span class="p">.</span><span class="n">ClientSession</span><span class="p">()</span> <span class="k">as</span> <span class="n">s</span><span class="p">:</span>
        <span class="k">async</span> <span class="k">with</span> <span class="n">s</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">as</span> <span class="n">r</span><span class="p">:</span>
            <span class="k">return</span> <span class="k">await</span> <span class="n">r</span><span class="p">.</span><span class="n">text</span><span class="p">()</span>
</code></pre></div></div>

<h3 id="-pitfall-2-forgetting-await">❌ Pitfall 2: Forgetting await</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># ❌ Wrong: coro() just creates coroutine object, never runs
</span><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">result</span> <span class="o">=</span> <span class="n">some_async_func</span><span class="p">()</span>   <span class="c1"># Missing await!
</span>    <span class="k">print</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>                <span class="c1"># Prints coroutine object
</span>
<span class="c1"># ✅ Correct
</span><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">some_async_func</span><span class="p">()</span>
    <span class="k">print</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</code></pre></div></div>

<p>Python will emit: <code class="language-plaintext highlighter-rouge">RuntimeWarning: coroutine 'xxx' was never awaited</code></p>

<h3 id="-pitfall-3-sequential-await-instead-of-concurrent">❌ Pitfall 3: Sequential await Instead of Concurrent</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># ❌ Wrong: Sequential, no concurrency
</span><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">r1</span> <span class="o">=</span> <span class="k">await</span> <span class="n">fetch</span><span class="p">(</span><span class="s">"url1"</span><span class="p">)</span>  <span class="c1"># Wait this
</span>    <span class="n">r2</span> <span class="o">=</span> <span class="k">await</span> <span class="n">fetch</span><span class="p">(</span><span class="s">"url2"</span><span class="p">)</span>  <span class="c1"># Then this
</span>    <span class="n">r3</span> <span class="o">=</span> <span class="k">await</span> <span class="n">fetch</span><span class="p">(</span><span class="s">"url3"</span><span class="p">)</span>  <span class="c1"># Total time = sum of all
</span>    <span class="c1"># Total time: 3×delay
</span>
<span class="c1"># ✅ Correct: True concurrency
</span><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">r1</span><span class="p">,</span> <span class="n">r2</span><span class="p">,</span> <span class="n">r3</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span>
        <span class="n">fetch</span><span class="p">(</span><span class="s">"url1"</span><span class="p">),</span>
        <span class="n">fetch</span><span class="p">(</span><span class="s">"url2"</span><span class="p">),</span>
        <span class="n">fetch</span><span class="p">(</span><span class="s">"url3"</span><span class="p">),</span>
    <span class="p">)</span>
    <span class="c1"># Total time: max(delays)
</span></code></pre></div></div>

<h3 id="-pitfall-4-calling-asynciorun-inside-existing-event-loop">❌ Pitfall 4: Calling asyncio.run() Inside Existing Event Loop</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># ❌ Fails in Jupyter Notebook or existing event loop
</span><span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>

<span class="c1"># ✅ Jupyter: directly await
</span><span class="k">await</span> <span class="n">main</span><span class="p">()</span>

<span class="c1"># Or use nest_asyncio
</span><span class="kn">import</span> <span class="nn">nest_asyncio</span>
<span class="n">nest_asyncio</span><span class="p">.</span><span class="nb">apply</span><span class="p">()</span>
<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<h3 id="-best-practices-summary">✅ Best Practices Summary</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 1. Reuse session; don't create per request
</span><span class="k">async</span> <span class="k">with</span> <span class="n">aiohttp</span><span class="p">.</span><span class="n">ClientSession</span><span class="p">()</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="n">fetch</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="n">url</span><span class="p">)</span> <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">urls</span><span class="p">])</span>

<span class="c1"># 2. Use Semaphore to limit concurrency, prevent overwhelming server
</span><span class="n">sem</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">Semaphore</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">safe_fetch</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">sem</span><span class="p">:</span>
        <span class="k">return</span> <span class="k">await</span> <span class="n">fetch</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>

<span class="c1"># 3. Always handle exceptions; prevent silent task failures
</span><span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">,</span> <span class="n">return_exceptions</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>

<span class="c1"># 4. Set timeouts
</span><span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">wait_for</span><span class="p">(</span><span class="n">coro</span><span class="p">(),</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">30</span><span class="p">)</span>

<span class="c1"># 5. Cancel unwanted tasks
</span><span class="n">task</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">coro</span><span class="p">())</span>
<span class="n">task</span><span class="p">.</span><span class="n">cancel</span><span class="p">()</span>
<span class="k">try</span><span class="p">:</span>
    <span class="k">await</span> <span class="n">task</span>
<span class="k">except</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">CancelledError</span><span class="p">:</span>
    <span class="k">pass</span>  <span class="c1"># Normal cancellation
</span></code></pre></div></div>

<hr />

<h2 id="14-performance-comparison">14. Performance Comparison</h2>

<h3 id="experiment-fetching-50-urls">Experiment: Fetching 50 URLs</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">import</span> <span class="nn">aiohttp</span>
<span class="kn">import</span> <span class="nn">requests</span>
<span class="kn">import</span> <span class="nn">time</span>

<span class="n">URLS</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s">"https://httpbin.org/delay/1"</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">)]</span>

<span class="c1"># ① Sync version
</span><span class="k">def</span> <span class="nf">sync_main</span><span class="p">():</span>
    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span>
    <span class="n">results</span> <span class="o">=</span> <span class="p">[</span><span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">).</span><span class="n">status_code</span> <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">URLS</span><span class="p">]</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Sync elapsed: </span><span class="si">{</span><span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="si">:</span><span class="p">.</span><span class="mi">1</span><span class="n">f</span><span class="si">}</span><span class="s">s"</span><span class="p">)</span>

<span class="c1"># ② Async version
</span><span class="k">async</span> <span class="k">def</span> <span class="nf">async_main</span><span class="p">():</span>
    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">aiohttp</span><span class="p">.</span><span class="n">ClientSession</span><span class="p">()</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
        <span class="k">async</span> <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
            <span class="k">async</span> <span class="k">with</span> <span class="n">session</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">as</span> <span class="n">r</span><span class="p">:</span>
                <span class="k">return</span> <span class="n">r</span><span class="p">.</span><span class="n">status</span>
        <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="n">fetch</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">URLS</span><span class="p">])</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Async elapsed: </span><span class="si">{</span><span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="si">:</span><span class="p">.</span><span class="mi">1</span><span class="n">f</span><span class="si">}</span><span class="s">s"</span><span class="p">)</span>

<span class="c1"># ③ Rate-limited async
</span><span class="k">async</span> <span class="k">def</span> <span class="nf">async_limited</span><span class="p">():</span>
    <span class="n">sem</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">Semaphore</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">aiohttp</span><span class="p">.</span><span class="n">ClientSession</span><span class="p">()</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
        <span class="k">async</span> <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
            <span class="k">async</span> <span class="k">with</span> <span class="n">sem</span><span class="p">:</span>
                <span class="k">async</span> <span class="k">with</span> <span class="n">session</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">as</span> <span class="n">r</span><span class="p">:</span>
                    <span class="k">return</span> <span class="n">r</span><span class="p">.</span><span class="n">status</span>
        <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="n">fetch</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">URLS</span><span class="p">])</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Rate-limited async elapsed: </span><span class="si">{</span><span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="si">:</span><span class="p">.</span><span class="mi">1</span><span class="n">f</span><span class="si">}</span><span class="s">s"</span><span class="p">)</span>

<span class="n">sync_main</span><span class="p">()</span>               <span class="c1"># Sync elapsed: 10.x s
</span><span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">async_main</span><span class="p">())</span> <span class="c1"># Async elapsed: 1.x s
</span><span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">async_limited</span><span class="p">())</span> <span class="c1"># Rate-limited: 2.x s
</span></code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>Method</th>
      <th>Time (10 requests, 1s delay each)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Sync</td>
      <td>≈ 10s</td>
    </tr>
    <tr>
      <td>Async (unlimited)</td>
      <td>≈ 1s</td>
    </tr>
    <tr>
      <td>Async (rate-limited 5)</td>
      <td>≈ 2s</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="appendix-quick-reference">Appendix: Quick Reference</h2>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Run coroutine
</span><span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">coro</span><span class="p">())</span>

<span class="c1"># Concurrent wait, collect results
</span><span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span><span class="n">coro1</span><span class="p">(),</span> <span class="n">coro2</span><span class="p">(),</span> <span class="n">coro3</span><span class="p">())</span>

<span class="c1"># Create task (schedule immediately)
</span><span class="n">task</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">coro</span><span class="p">())</span>

<span class="c1"># Timeout
</span><span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">wait_for</span><span class="p">(</span><span class="n">coro</span><span class="p">(),</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span>

<span class="c1"># Wait for first completion
</span><span class="n">done</span><span class="p">,</span> <span class="n">pending</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">wait</span><span class="p">(</span><span class="n">tasks</span><span class="p">,</span> <span class="n">return_when</span><span class="o">=</span><span class="n">asyncio</span><span class="p">.</span><span class="n">FIRST_COMPLETED</span><span class="p">)</span>

<span class="c1"># Rate limiting
</span><span class="n">sem</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">Semaphore</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
<span class="k">async</span> <span class="k">with</span> <span class="n">sem</span><span class="p">:</span> <span class="p">...</span>

<span class="c1"># Sync to async (thread pool)
</span><span class="k">await</span> <span class="n">loop</span><span class="p">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="bp">None</span><span class="p">,</span> <span class="n">sync_func</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">)</span>

<span class="c1"># Get event loop
</span><span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">get_running_loop</span><span class="p">()</span>

<span class="c1"># Async sleep
</span><span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</code></pre></div></div>

<hr />]]></content><author><name></name></author><summary type="html"><![CDATA[Async is not “faster,” but “smarter waiting.”]]></summary></entry><entry xml:lang="zh"><title type="html">Python 异步 IO 学习笔记</title><link href="https://ryviuszero.github.io/zh/posts/python-async-io-notes/" rel="alternate" type="text/html" title="Python 异步 IO 学习笔记" /><published>2024-05-18T08:34:01+00:00</published><updated>2024-05-18T08:34:01+00:00</updated><id>https://ryviuszero.github.io/zh/posts/Python%E5%BC%82%E6%AD%A5IO%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0</id><content type="html" xml:base="https://ryviuszero.github.io/zh/posts/python-async-io-notes/"><![CDATA[<blockquote>
  <p>异步不是”更快”，而是”更聪明地等待”。</p>
</blockquote>

<hr />

<h2 id="目录">目录</h2>

<ol>
  <li><a href="#1-为什么需要异步-io">为什么需要异步 IO？</a></li>
  <li><a href="#2-核心概念">核心概念</a></li>
  <li><a href="#3-asyncio-基础">asyncio 基础</a></li>
  <li><a href="#4-task-与并发">Task 与并发</a></li>
  <li><a href="#5-异步上下文管理器与迭代器">异步上下文管理器与迭代器</a></li>
  <li><a href="#6-asyncio-同步原语">asyncio 同步原语</a></li>
  <li><a href="#7-queue生产者消费者模式">Queue：生产者消费者模式</a></li>
  <li><a href="#8-异步网络-ioaiohttp">异步网络 IO：aiohttp</a></li>
  <li><a href="#9-异步文件-ioaiofiles">异步文件 IO：aiofiles</a></li>
  <li><a href="#10-异步数据库">异步数据库</a></li>
  <li><a href="#11-在同步代码中调用异步">在同步代码中调用异步</a></li>
  <li><a href="#12-事件循环进阶">事件循环进阶</a></li>
  <li><a href="#13-常见陷阱与最佳实践">常见陷阱与最佳实践</a></li>
  <li><a href="#14-性能对比实验">性能对比实验</a></li>
</ol>

<hr />

<h2 id="1-为什么需要异步-io">1. 为什么需要异步 IO？</h2>

<h3 id="三种并发模型对比">三种并发模型对比</h3>

<table>
  <thead>
    <tr>
      <th>模型</th>
      <th>原理</th>
      <th>适合场景</th>
      <th>缺点</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>多进程</strong></td>
      <td>多个 CPU 核心并行</td>
      <td>CPU 密集型（计算、压缩）</td>
      <td>内存占用大，进程切换开销高</td>
    </tr>
    <tr>
      <td><strong>多线程</strong></td>
      <td>操作系统调度线程</td>
      <td>IO 密集型（但受 GIL 限制）</td>
      <td>GIL、竞态条件、死锁</td>
    </tr>
    <tr>
      <td><strong>异步 IO</strong></td>
      <td>单线程事件循环</td>
      <td>IO 密集型（网络、文件）</td>
      <td>不适合 CPU 密集型</td>
    </tr>
  </tbody>
</table>

<h3 id="核心问题等待的浪费">核心问题：等待的浪费</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>同步模式（一个厨师只做一道菜）：
[请求A 发出] → [等待 200ms] → [处理结果] → [请求B 发出] → [等待 200ms] → ...
总耗时：200ms × N

异步模式（一个厨师同时照看多道菜）：
[请求A 发出] ↘
[请求B 发出]  → [等待中...] → [A 返回，处理] → [B 返回，处理]
[请求C 发出] ↗
总耗时：≈ 200ms（最长单个请求时间）
</code></pre></div></div>

<p><strong>结论：</strong> 异步 IO 的本质是在 <strong>等待 IO 的时候去做其他事</strong>，而不是真正的并行计算。</p>

<hr />

<h2 id="2-核心概念">2. 核心概念</h2>

<h3 id="协程coroutine">协程（Coroutine）</h3>

<p>用 <code class="language-plaintext highlighter-rouge">async def</code> 定义的函数，调用后返回一个协程对象，<strong>不会立即执行</strong>：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">say_hello</span><span class="p">():</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"Hello"</span><span class="p">)</span>

<span class="c1"># 调用不会执行！只是创建了协程对象
</span><span class="n">coro</span> <span class="o">=</span> <span class="n">say_hello</span><span class="p">()</span>
<span class="k">print</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">coro</span><span class="p">))</span>  <span class="c1"># &lt;class 'coroutine'&gt;
</span>
<span class="c1"># 必须通过事件循环运行
</span><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">say_hello</span><span class="p">())</span>  <span class="c1"># 这才会打印 Hello
</span></code></pre></div></div>

<h3 id="事件循环event-loop">事件循环（Event Loop）</h3>

<p>异步程序的”调度中心”，负责：</p>
<ul>
  <li>运行协程</li>
  <li>监听 IO 事件</li>
  <li>调度回调</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌─────────────────────────────────────┐
│              Event Loop              │
│                                     │
│  Task A ──► await IO ──► 挂起       │
│  Task B ◄── IO 完成 ◄── 唤醒       │
│  Task C ──► await IO ──► 挂起       │
│  Task A ◄── IO 完成 ◄── 唤醒       │
└─────────────────────────────────────┘
</code></pre></div></div>

<h3 id="await-关键字">await 关键字</h3>

<p><code class="language-plaintext highlighter-rouge">await</code> 只能在 <code class="language-plaintext highlighter-rouge">async def</code> 函数内使用，作用是：</p>
<ol>
  <li>暂停当前协程，把控制权交回事件循环</li>
  <li>等待被 await 的对象完成</li>
  <li>完成后恢复执行，获取返回值</li>
</ol>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">fetch</span><span class="p">():</span>
    <span class="c1"># 暂停在这里，事件循环去运行其他任务
</span>    <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">some_async_operation</span><span class="p">()</span>
    <span class="c1"># IO 完成后从这里继续
</span>    <span class="k">return</span> <span class="n">result</span>
</code></pre></div></div>

<h3 id="可等待对象awaitable">可等待对象（Awaitable）</h3>

<p>可以被 <code class="language-plaintext highlighter-rouge">await</code> 的对象有三类：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 1. 协程（Coroutine）
</span><span class="k">async</span> <span class="k">def</span> <span class="nf">coro</span><span class="p">():</span> <span class="p">...</span>
<span class="k">await</span> <span class="n">coro</span><span class="p">()</span>

<span class="c1"># 2. Task（asyncio.create_task 创建）
</span><span class="n">task</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">coro</span><span class="p">())</span>
<span class="k">await</span> <span class="n">task</span>

<span class="c1"># 3. Future（底层接口，通常不直接使用）
</span><span class="n">future</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">get_event_loop</span><span class="p">().</span><span class="n">create_future</span><span class="p">()</span>
<span class="k">await</span> <span class="n">future</span>
</code></pre></div></div>

<hr />

<h2 id="3-asyncio-基础">3. asyncio 基础</h2>

<h3 id="asynciorun程序入口">asyncio.run()：程序入口</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"开始"</span><span class="p">)</span>
    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>  <span class="c1"># 模拟 IO 等待
</span>    <span class="k">print</span><span class="p">(</span><span class="s">"结束"</span><span class="p">)</span>

<span class="c1"># Python 3.7+ 标准入口
</span><span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<h3 id="asynciosleep">asyncio.sleep()</h3>

<p>异步版 <code class="language-plaintext highlighter-rouge">time.sleep()</code>，等待期间<strong>不阻塞事件循环</strong>：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">import</span> <span class="nn">time</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">task</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">delay</span><span class="p">):</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s"> 开始"</span><span class="p">)</span>
    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="n">delay</span><span class="p">)</span>  <span class="c1"># ✅ 异步等待
</span>    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s"> 完成"</span><span class="p">)</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span>
    <span class="c1"># 顺序执行（总耗时 = 1 + 2 = 3s）
</span>    <span class="k">await</span> <span class="n">task</span><span class="p">(</span><span class="s">"A"</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="k">await</span> <span class="n">task</span><span class="p">(</span><span class="s">"B"</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"耗时：</span><span class="si">{</span><span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="si">:</span><span class="p">.</span><span class="mi">1</span><span class="n">f</span><span class="si">}</span><span class="s">s"</span><span class="p">)</span>

<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
<span class="c1"># 耗时：3.0s
</span></code></pre></div></div>

<hr />

<h2 id="4-task-与并发">4. Task 与并发</h2>

<h3 id="asynciocreate_task真正并发">asyncio.create_task()：真正并发</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">import</span> <span class="nn">time</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">task</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">delay</span><span class="p">):</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s"> 开始"</span><span class="p">)</span>
    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="n">delay</span><span class="p">)</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s"> 完成"</span><span class="p">)</span>
    <span class="k">return</span> <span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s"> 的结果"</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span>

    <span class="c1"># 创建 Task，立即调度（但还未运行）
</span>    <span class="n">task_a</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">task</span><span class="p">(</span><span class="s">"A"</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span>
    <span class="n">task_b</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">task</span><span class="p">(</span><span class="s">"B"</span><span class="p">,</span> <span class="mi">2</span><span class="p">))</span>
    <span class="n">task_c</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">task</span><span class="p">(</span><span class="s">"C"</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span>

    <span class="c1"># 等待所有 Task 完成
</span>    <span class="n">result_a</span> <span class="o">=</span> <span class="k">await</span> <span class="n">task_a</span>
    <span class="n">result_b</span> <span class="o">=</span> <span class="k">await</span> <span class="n">task_b</span>
    <span class="n">result_c</span> <span class="o">=</span> <span class="k">await</span> <span class="n">task_c</span>

    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"耗时：</span><span class="si">{</span><span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="si">:</span><span class="p">.</span><span class="mi">1</span><span class="n">f</span><span class="si">}</span><span class="s">s"</span><span class="p">)</span>
    <span class="c1"># 耗时：2.0s（并发执行，总耗时 = 最长的单个任务）
</span>
<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<h3 id="asynciogather批量并发">asyncio.gather()：批量并发</h3>

<p>最常用的并发方式，等待所有协程完成并收集结果：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>  <span class="c1"># 模拟网络请求
</span>    <span class="k">return</span> <span class="sa">f</span><span class="s">"来自 </span><span class="si">{</span><span class="n">url</span><span class="si">}</span><span class="s"> 的数据"</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">urls</span> <span class="o">=</span> <span class="p">[</span><span class="s">"url1"</span><span class="p">,</span> <span class="s">"url2"</span><span class="p">,</span> <span class="s">"url3"</span><span class="p">,</span> <span class="s">"url4"</span><span class="p">,</span> <span class="s">"url5"</span><span class="p">]</span>

    <span class="c1"># 并发执行所有请求
</span>    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span>
        <span class="o">*</span><span class="p">[</span><span class="n">fetch</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">urls</span><span class="p">]</span>
    <span class="p">)</span>

    <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="n">r</span><span class="p">)</span>

<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
<span class="c1"># 5 个请求同时发出，总耗时约 1s
</span></code></pre></div></div>

<p><strong>gather 的异常处理：</strong></p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 默认：任何一个出错会立刻抛出异常
</span><span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span><span class="n">coro1</span><span class="p">(),</span> <span class="n">coro2</span><span class="p">(),</span> <span class="n">coro3</span><span class="p">())</span>

<span class="c1"># return_exceptions=True：异常作为结果返回，不中断其他任务
</span><span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span>
    <span class="n">coro1</span><span class="p">(),</span> <span class="n">coro2</span><span class="p">(),</span> <span class="n">coro3</span><span class="p">(),</span>
    <span class="n">return_exceptions</span><span class="o">=</span><span class="bp">True</span>
<span class="p">)</span>
<span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">:</span>
    <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="nb">Exception</span><span class="p">):</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"出错：</span><span class="si">{</span><span class="n">r</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"成功：</span><span class="si">{</span><span class="n">r</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="asynciowait更精细的控制">asyncio.wait()：更精细的控制</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">task</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">n</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">asyncio</span><span class="p">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">task</span><span class="p">(</span><span class="n">i</span><span class="p">))</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="p">[</span><span class="mi">3</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">]]</span>

    <span class="c1"># 等待第一个完成就继续
</span>    <span class="n">done</span><span class="p">,</span> <span class="n">pending</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">wait</span><span class="p">(</span>
        <span class="n">tasks</span><span class="p">,</span>
        <span class="n">return_when</span><span class="o">=</span><span class="n">asyncio</span><span class="p">.</span><span class="n">FIRST_COMPLETED</span>
    <span class="p">)</span>

    <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">done</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"完成：</span><span class="si">{</span><span class="n">t</span><span class="p">.</span><span class="n">result</span><span class="p">()</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>

    <span class="c1"># 取消剩余任务
</span>    <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">pending</span><span class="p">:</span>
        <span class="n">t</span><span class="p">.</span><span class="n">cancel</span><span class="p">()</span>

<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th><code class="language-plaintext highlighter-rouge">return_when</code> 参数</th>
      <th>含义</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ALL_COMPLETED</code>（默认）</td>
      <td>全部完成才返回</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">FIRST_COMPLETED</code></td>
      <td>第一个完成就返回</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">FIRST_EXCEPTION</code></td>
      <td>第一个出错就返回</td>
    </tr>
  </tbody>
</table>

<h3 id="asynciotimeout超时控制python-311">asyncio.timeout()：超时控制（Python 3.11+）</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="k">async</span> <span class="k">with</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">timeout</span><span class="p">(</span><span class="mf">5.0</span><span class="p">):</span>
            <span class="k">await</span> <span class="n">long_running_task</span><span class="p">()</span>
    <span class="k">except</span> <span class="nb">TimeoutError</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"超时了！"</span><span class="p">)</span>

<span class="c1"># Python 3.10 及以下用 wait_for
</span><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">wait_for</span><span class="p">(</span>
            <span class="n">long_running_task</span><span class="p">(),</span>
            <span class="n">timeout</span><span class="o">=</span><span class="mf">5.0</span>
        <span class="p">)</span>
    <span class="k">except</span> <span class="n">asyncio</span><span class="p">.</span><span class="nb">TimeoutError</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"超时了！"</span><span class="p">)</span>
</code></pre></div></div>

<hr />

<h2 id="5-异步上下文管理器与迭代器">5. 异步上下文管理器与迭代器</h2>

<h3 id="异步上下文管理器">异步上下文管理器</h3>

<p>实现 <code class="language-plaintext highlighter-rouge">__aenter__</code> 和 <code class="language-plaintext highlighter-rouge">__aexit__</code>：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">AsyncDBConnection</span><span class="p">:</span>
    <span class="k">async</span> <span class="k">def</span> <span class="nf">__aenter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"连接数据库"</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">conn</span> <span class="o">=</span> <span class="k">await</span> <span class="n">create_connection</span><span class="p">()</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">conn</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">__aexit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">exc_type</span><span class="p">,</span> <span class="n">exc</span><span class="p">,</span> <span class="n">tb</span><span class="p">):</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"关闭连接"</span><span class="p">)</span>
        <span class="k">await</span> <span class="bp">self</span><span class="p">.</span><span class="n">conn</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">AsyncDBConnection</span><span class="p">()</span> <span class="k">as</span> <span class="n">conn</span><span class="p">:</span>
        <span class="k">await</span> <span class="n">conn</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span><span class="s">"SELECT 1"</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="异步迭代器">异步迭代器</h3>

<p>实现 <code class="language-plaintext highlighter-rouge">__aiter__</code> 和 <code class="language-plaintext highlighter-rouge">__anext__</code>：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">AsyncRange</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">n</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">n</span> <span class="o">=</span> <span class="n">n</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span>

    <span class="k">def</span> <span class="nf">__aiter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="bp">self</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">__anext__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">i</span> <span class="o">&gt;=</span> <span class="bp">self</span><span class="p">.</span><span class="n">n</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nb">StopAsyncIteration</span>
        <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>  <span class="c1"># 模拟异步操作
</span>        <span class="n">val</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">i</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
        <span class="k">return</span> <span class="n">val</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="k">async</span> <span class="k">for</span> <span class="n">num</span> <span class="ow">in</span> <span class="n">AsyncRange</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
        <span class="k">print</span><span class="p">(</span><span class="n">num</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="异步生成器">异步生成器</h3>

<p>更简洁的写法：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">async_range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
        <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
        <span class="k">yield</span> <span class="n">i</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="k">async</span> <span class="k">for</span> <span class="n">num</span> <span class="ow">in</span> <span class="n">async_range</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
        <span class="k">print</span><span class="p">(</span><span class="n">num</span><span class="p">)</span>

    <span class="c1"># 也可以用列表推导
</span>    <span class="n">results</span> <span class="o">=</span> <span class="p">[</span><span class="n">i</span> <span class="k">async</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">async_range</span><span class="p">(</span><span class="mi">5</span><span class="p">)]</span>
</code></pre></div></div>

<hr />

<h2 id="6-asyncio-同步原语">6. asyncio 同步原语</h2>

<p>多个协程共享资源时需要同步控制（类比多线程的锁）。</p>

<h3 id="lock互斥锁">Lock：互斥锁</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>

<span class="n">lock</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">Lock</span><span class="p">()</span>
<span class="n">shared_resource</span> <span class="o">=</span> <span class="mi">0</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">worker</span><span class="p">(</span><span class="n">name</span><span class="p">):</span>
    <span class="k">global</span> <span class="n">shared_resource</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">lock</span><span class="p">:</span>  <span class="c1"># 获取锁，其他协程等待
</span>        <span class="n">val</span> <span class="o">=</span> <span class="n">shared_resource</span>
        <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>  <span class="c1"># 模拟操作
</span>        <span class="n">shared_resource</span> <span class="o">=</span> <span class="n">val</span> <span class="o">+</span> <span class="mi">1</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s">: </span><span class="si">{</span><span class="n">shared_resource</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="n">worker</span><span class="p">(</span><span class="sa">f</span><span class="s">"W</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s">"</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">)])</span>

<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<h3 id="event事件通知">Event：事件通知</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">waiter</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s"> 等待事件..."</span><span class="p">)</span>
    <span class="k">await</span> <span class="n">event</span><span class="p">.</span><span class="n">wait</span><span class="p">()</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s"> 收到事件，继续执行"</span><span class="p">)</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">setter</span><span class="p">(</span><span class="n">event</span><span class="p">):</span>
    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"触发事件！"</span><span class="p">)</span>
    <span class="n">event</span><span class="p">.</span><span class="nb">set</span><span class="p">()</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">event</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">Event</span><span class="p">()</span>
    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span>
        <span class="n">waiter</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="s">"W1"</span><span class="p">),</span>
        <span class="n">waiter</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="s">"W2"</span><span class="p">),</span>
        <span class="n">setter</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
    <span class="p">)</span>
</code></pre></div></div>

<h3 id="semaphore限制并发数">Semaphore：限制并发数</h3>

<p>最常用！限制同时运行的协程数量：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">semaphore</span><span class="p">):</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">semaphore</span><span class="p">:</span>  <span class="c1"># 最多 10 个同时执行
</span>        <span class="k">async</span> <span class="k">with</span> <span class="n">session</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">as</span> <span class="n">resp</span><span class="p">:</span>
            <span class="k">return</span> <span class="k">await</span> <span class="n">resp</span><span class="p">.</span><span class="n">text</span><span class="p">()</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">semaphore</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">Semaphore</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>  <span class="c1"># 最大并发 10
</span>    <span class="n">urls</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s">"https://example.com/</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s">"</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">)]</span>

    <span class="k">async</span> <span class="k">with</span> <span class="n">aiohttp</span><span class="p">.</span><span class="n">ClientSession</span><span class="p">()</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
        <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">fetch</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">semaphore</span><span class="p">)</span> <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">urls</span><span class="p">]</span>
        <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</code></pre></div></div>

<hr />

<h2 id="7-queue生产者消费者模式">7. Queue：生产者消费者模式</h2>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">producer</span><span class="p">(</span><span class="n">queue</span><span class="p">,</span> <span class="n">n</span><span class="p">):</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
        <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.5</span><span class="p">)</span>  <span class="c1"># 模拟生产耗时
</span>        <span class="k">await</span> <span class="n">queue</span><span class="p">.</span><span class="n">put</span><span class="p">(</span><span class="n">i</span><span class="p">)</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"生产：</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s">，队列大小：</span><span class="si">{</span><span class="n">queue</span><span class="p">.</span><span class="n">qsize</span><span class="p">()</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
    <span class="k">await</span> <span class="n">queue</span><span class="p">.</span><span class="n">put</span><span class="p">(</span><span class="bp">None</span><span class="p">)</span>  <span class="c1"># 结束信号
</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">consumer</span><span class="p">(</span><span class="n">queue</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
    <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
        <span class="n">item</span> <span class="o">=</span> <span class="k">await</span> <span class="n">queue</span><span class="p">.</span><span class="n">get</span><span class="p">()</span>
        <span class="k">if</span> <span class="n">item</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
            <span class="n">queue</span><span class="p">.</span><span class="n">task_done</span><span class="p">()</span>
            <span class="k">break</span>
        <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>  <span class="c1"># 模拟消费耗时
</span>        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"[</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s">] 消费：</span><span class="si">{</span><span class="n">item</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
        <span class="n">queue</span><span class="p">.</span><span class="n">task_done</span><span class="p">()</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">queue</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">Queue</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span>  <span class="c1"># 最大容量 5
</span>
    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span>
        <span class="n">producer</span><span class="p">(</span><span class="n">queue</span><span class="p">,</span> <span class="mi">10</span><span class="p">),</span>
        <span class="n">consumer</span><span class="p">(</span><span class="n">queue</span><span class="p">,</span> <span class="s">"消费者A"</span><span class="p">),</span>
        <span class="n">consumer</span><span class="p">(</span><span class="n">queue</span><span class="p">,</span> <span class="s">"消费者B"</span><span class="p">),</span>
    <span class="p">)</span>

<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<hr />

<h2 id="8-异步网络-ioaiohttp">8. 异步网络 IO：aiohttp</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip <span class="nb">install </span>aiohttp
</code></pre></div></div>

<h3 id="基础-get-请求">基础 GET 请求</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">import</span> <span class="nn">aiohttp</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="n">url</span><span class="p">):</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">session</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">as</span> <span class="n">response</span><span class="p">:</span>
        <span class="k">return</span> <span class="k">await</span> <span class="n">response</span><span class="p">.</span><span class="n">json</span><span class="p">()</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">aiohttp</span><span class="p">.</span><span class="n">ClientSession</span><span class="p">()</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
        <span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">fetch</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="s">"https://api.github.com/users/octocat"</span><span class="p">)</span>
        <span class="k">print</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s">"name"</span><span class="p">])</span>

<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<h3 id="并发抓取多个-url">并发抓取多个 URL</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">import</span> <span class="nn">aiohttp</span>
<span class="kn">import</span> <span class="nn">time</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="n">url</span><span class="p">):</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="k">async</span> <span class="k">with</span> <span class="n">session</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="n">aiohttp</span><span class="p">.</span><span class="n">ClientTimeout</span><span class="p">(</span><span class="n">total</span><span class="o">=</span><span class="mi">10</span><span class="p">))</span> <span class="k">as</span> <span class="n">resp</span><span class="p">:</span>
            <span class="k">return</span> <span class="p">{</span><span class="s">"url"</span><span class="p">:</span> <span class="n">url</span><span class="p">,</span> <span class="s">"status"</span><span class="p">:</span> <span class="n">resp</span><span class="p">.</span><span class="n">status</span><span class="p">,</span> <span class="s">"data"</span><span class="p">:</span> <span class="k">await</span> <span class="n">resp</span><span class="p">.</span><span class="n">text</span><span class="p">()}</span>
    <span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
        <span class="k">return</span> <span class="p">{</span><span class="s">"url"</span><span class="p">:</span> <span class="n">url</span><span class="p">,</span> <span class="s">"error"</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)}</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">urls</span> <span class="o">=</span> <span class="p">[</span>
        <span class="s">"https://httpbin.org/delay/1"</span><span class="p">,</span>
        <span class="s">"https://httpbin.org/delay/2"</span><span class="p">,</span>
        <span class="s">"https://httpbin.org/delay/1"</span><span class="p">,</span>
    <span class="p">]</span>

    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">aiohttp</span><span class="p">.</span><span class="n">ClientSession</span><span class="p">()</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
        <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="n">fetch</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="n">url</span><span class="p">)</span> <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">urls</span><span class="p">])</span>

    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"耗时：</span><span class="si">{</span><span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="si">:</span><span class="p">.</span><span class="mi">1</span><span class="n">f</span><span class="si">}</span><span class="s">s"</span><span class="p">)</span>  <span class="c1"># ≈ 2s，而非 4s
</span>    <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="n">r</span><span class="p">[</span><span class="s">"url"</span><span class="p">],</span> <span class="n">r</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"status"</span><span class="p">,</span> <span class="n">r</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"error"</span><span class="p">)))</span>

<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<h3 id="post-请求">POST 请求</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">post_data</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">payload</span><span class="p">):</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">session</span><span class="p">.</span><span class="n">post</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">payload</span><span class="p">)</span> <span class="k">as</span> <span class="n">resp</span><span class="p">:</span>
        <span class="k">return</span> <span class="k">await</span> <span class="n">resp</span><span class="p">.</span><span class="n">json</span><span class="p">()</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">aiohttp</span><span class="p">.</span><span class="n">ClientSession</span><span class="p">()</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
        <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">post_data</span><span class="p">(</span>
            <span class="n">session</span><span class="p">,</span>
            <span class="s">"https://httpbin.org/post"</span><span class="p">,</span>
            <span class="p">{</span><span class="s">"key"</span><span class="p">:</span> <span class="s">"value"</span><span class="p">,</span> <span class="s">"name"</span><span class="p">:</span> <span class="s">"asyncio"</span><span class="p">}</span>
        <span class="p">)</span>
        <span class="k">print</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</code></pre></div></div>

<hr />

<h2 id="9-异步文件-ioaiofiles">9. 异步文件 IO：aiofiles</h2>

<p>标准的 <code class="language-plaintext highlighter-rouge">open()</code> 是同步的，会阻塞事件循环。使用 <code class="language-plaintext highlighter-rouge">aiofiles</code>：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip <span class="nb">install </span>aiofiles
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">import</span> <span class="nn">aiofiles</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">read_file</span><span class="p">(</span><span class="n">path</span><span class="p">):</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">aiofiles</span><span class="p">.</span><span class="nb">open</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="s">"r"</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s">"utf-8"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
        <span class="k">return</span> <span class="k">await</span> <span class="n">f</span><span class="p">.</span><span class="n">read</span><span class="p">()</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">write_file</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">content</span><span class="p">):</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">aiofiles</span><span class="p">.</span><span class="nb">open</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="s">"w"</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s">"utf-8"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
        <span class="k">await</span> <span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="c1"># 并发读取多个文件
</span>    <span class="n">contents</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span>
        <span class="n">read_file</span><span class="p">(</span><span class="s">"file1.txt"</span><span class="p">),</span>
        <span class="n">read_file</span><span class="p">(</span><span class="s">"file2.txt"</span><span class="p">),</span>
        <span class="n">read_file</span><span class="p">(</span><span class="s">"file3.txt"</span><span class="p">),</span>
    <span class="p">)</span>
    <span class="k">for</span> <span class="n">content</span> <span class="ow">in</span> <span class="n">contents</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="n">content</span><span class="p">[:</span><span class="mi">100</span><span class="p">])</span>

<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<hr />

<h2 id="10-异步数据库">10. 异步数据库</h2>

<h3 id="asyncpgpostgresql">asyncpg（PostgreSQL）</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip <span class="nb">install </span>asyncpg
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">import</span> <span class="nn">asyncpg</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="c1"># 创建连接
</span>    <span class="n">conn</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncpg</span><span class="p">.</span><span class="n">connect</span><span class="p">(</span>
        <span class="n">host</span><span class="o">=</span><span class="s">"localhost"</span><span class="p">,</span> <span class="n">database</span><span class="o">=</span><span class="s">"mydb"</span><span class="p">,</span>
        <span class="n">user</span><span class="o">=</span><span class="s">"user"</span><span class="p">,</span> <span class="n">password</span><span class="o">=</span><span class="s">"password"</span>
    <span class="p">)</span>

    <span class="c1"># 查询
</span>    <span class="n">rows</span> <span class="o">=</span> <span class="k">await</span> <span class="n">conn</span><span class="p">.</span><span class="n">fetch</span><span class="p">(</span><span class="s">"SELECT id, name FROM users WHERE active = $1"</span><span class="p">,</span> <span class="bp">True</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">rows</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="s">"id"</span><span class="p">],</span> <span class="n">row</span><span class="p">[</span><span class="s">"name"</span><span class="p">])</span>

    <span class="c1"># 插入
</span>    <span class="k">await</span> <span class="n">conn</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span>
        <span class="s">"INSERT INTO users(name, email) VALUES($1, $2)"</span><span class="p">,</span>
        <span class="s">"张三"</span><span class="p">,</span> <span class="s">"zhangsan@example.com"</span>
    <span class="p">)</span>

    <span class="k">await</span> <span class="n">conn</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>

<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<h3 id="连接池生产环境必用">连接池（生产环境必用）</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">pool</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncpg</span><span class="p">.</span><span class="n">create_pool</span><span class="p">(</span>
        <span class="n">host</span><span class="o">=</span><span class="s">"localhost"</span><span class="p">,</span> <span class="n">database</span><span class="o">=</span><span class="s">"mydb"</span><span class="p">,</span>
        <span class="n">user</span><span class="o">=</span><span class="s">"user"</span><span class="p">,</span> <span class="n">password</span><span class="o">=</span><span class="s">"password"</span><span class="p">,</span>
        <span class="n">min_size</span><span class="o">=</span><span class="mi">5</span><span class="p">,</span> <span class="n">max_size</span><span class="o">=</span><span class="mi">20</span>
    <span class="p">)</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">query</span><span class="p">(</span><span class="n">user_id</span><span class="p">):</span>
        <span class="k">async</span> <span class="k">with</span> <span class="n">pool</span><span class="p">.</span><span class="n">acquire</span><span class="p">()</span> <span class="k">as</span> <span class="n">conn</span><span class="p">:</span>
            <span class="k">return</span> <span class="k">await</span> <span class="n">conn</span><span class="p">.</span><span class="n">fetchrow</span><span class="p">(</span><span class="s">"SELECT * FROM users WHERE id = $1"</span><span class="p">,</span> <span class="n">user_id</span><span class="p">)</span>

    <span class="c1"># 并发查询
</span>    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="n">query</span><span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">11</span><span class="p">)])</span>

    <span class="k">await</span> <span class="n">pool</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>
</code></pre></div></div>

<h3 id="aiosqlitesqlite">aiosqlite（SQLite）</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip <span class="nb">install </span>aiosqlite
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">aiosqlite</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">aiosqlite</span><span class="p">.</span><span class="n">connect</span><span class="p">(</span><span class="s">"test.db"</span><span class="p">)</span> <span class="k">as</span> <span class="n">db</span><span class="p">:</span>
        <span class="k">await</span> <span class="n">db</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span><span class="s">"""
            CREATE TABLE IF NOT EXISTS users (
                id INTEGER PRIMARY KEY,
                name TEXT
            )
        """</span><span class="p">)</span>
        <span class="k">await</span> <span class="n">db</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span><span class="s">"INSERT INTO users(name) VALUES(?)"</span><span class="p">,</span> <span class="p">(</span><span class="s">"张三"</span><span class="p">,))</span>
        <span class="k">await</span> <span class="n">db</span><span class="p">.</span><span class="n">commit</span><span class="p">()</span>

        <span class="k">async</span> <span class="k">with</span> <span class="n">db</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span><span class="s">"SELECT * FROM users"</span><span class="p">)</span> <span class="k">as</span> <span class="n">cursor</span><span class="p">:</span>
            <span class="k">async</span> <span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">cursor</span><span class="p">:</span>
                <span class="k">print</span><span class="p">(</span><span class="n">row</span><span class="p">)</span>
</code></pre></div></div>

<hr />

<h2 id="11-在同步代码中调用异步">11. 在同步代码中调用异步</h2>

<p>有时需要在同步代码（如普通函数、Django 视图）中调用异步函数：</p>

<h3 id="asynciorun最简单">asyncio.run()（最简单）</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">sync_function</span><span class="p">():</span>
    <span class="n">result</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">async_function</span><span class="p">())</span>
    <span class="k">return</span> <span class="n">result</span>
</code></pre></div></div>

<blockquote>
  <p>注意：<code class="language-plaintext highlighter-rouge">asyncio.run()</code> 会创建新的事件循环，<strong>不能在已有事件循环中调用</strong>。</p>
</blockquote>

<h3 id="looprun_until_complete">loop.run_until_complete()</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">sync_function</span><span class="p">():</span>
    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">new_event_loop</span><span class="p">()</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">result</span> <span class="o">=</span> <span class="n">loop</span><span class="p">.</span><span class="n">run_until_complete</span><span class="p">(</span><span class="n">async_function</span><span class="p">())</span>
    <span class="k">finally</span><span class="p">:</span>
        <span class="n">loop</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>
    <span class="k">return</span> <span class="n">result</span>
</code></pre></div></div>

<h3 id="在异步中运行同步阻塞函数">在异步中运行同步阻塞函数</h3>

<p>CPU 密集或遗留的同步阻塞代码，用 <code class="language-plaintext highlighter-rouge">run_in_executor</code> 放到线程池：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span>

<span class="k">def</span> <span class="nf">blocking_io</span><span class="p">():</span>
    <span class="kn">import</span> <span class="nn">time</span>
    <span class="n">time</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>      <span class="c1"># 同步阻塞操作
</span>    <span class="k">return</span> <span class="s">"done"</span>

<span class="k">def</span> <span class="nf">cpu_heavy</span><span class="p">():</span>
    <span class="k">return</span> <span class="nb">sum</span><span class="p">(</span><span class="n">i</span> <span class="o">*</span> <span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="o">**</span><span class="mi">7</span><span class="p">))</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">get_event_loop</span><span class="p">()</span>

    <span class="c1"># 放到线程池（适合 IO 阻塞）
</span>    <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">loop</span><span class="p">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="bp">None</span><span class="p">,</span> <span class="n">blocking_io</span><span class="p">)</span>

    <span class="c1"># 放到进程池（适合 CPU 密集）
</span>    <span class="k">with</span> <span class="n">ProcessPoolExecutor</span><span class="p">()</span> <span class="k">as</span> <span class="n">pool</span><span class="p">:</span>
        <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">loop</span><span class="p">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="n">pool</span><span class="p">,</span> <span class="n">cpu_heavy</span><span class="p">)</span>
</code></pre></div></div>

<hr />

<h2 id="12-事件循环进阶">12. 事件循环进阶</h2>

<h3 id="获取当前事件循环">获取当前事件循环</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">get_event_loop</span><span class="p">()</span>       <span class="c1"># 获取当前循环
</span>    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">get_running_loop</span><span class="p">()</span>     <span class="c1"># 只在协程中使用（推荐）
</span></code></pre></div></div>

<h3 id="调度回调">调度回调</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">get_running_loop</span><span class="p">()</span>

    <span class="c1"># 下一次迭代时执行（非协程，是普通函数）
</span>    <span class="n">loop</span><span class="p">.</span><span class="n">call_soon</span><span class="p">(</span><span class="k">print</span><span class="p">,</span> <span class="s">"马上执行"</span><span class="p">)</span>

    <span class="c1"># 延迟执行
</span>    <span class="n">loop</span><span class="p">.</span><span class="n">call_later</span><span class="p">(</span><span class="mf">2.0</span><span class="p">,</span> <span class="k">print</span><span class="p">,</span> <span class="s">"2秒后执行"</span><span class="p">)</span>

    <span class="c1"># 指定时间戳执行
</span>    <span class="n">loop</span><span class="p">.</span><span class="n">call_at</span><span class="p">(</span><span class="n">loop</span><span class="p">.</span><span class="n">time</span><span class="p">()</span> <span class="o">+</span> <span class="mf">5.0</span><span class="p">,</span> <span class="k">print</span><span class="p">,</span> <span class="s">"5秒后执行"</span><span class="p">)</span>

    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="uvloop高性能事件循环">uvloop：高性能事件循环</h3>

<p>用 C 实现的事件循环，性能比默认提升 2~4 倍：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip <span class="nb">install </span>uvloop
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">uvloop</span>

<span class="n">asyncio</span><span class="p">.</span><span class="n">set_event_loop_policy</span><span class="p">(</span><span class="n">uvloop</span><span class="p">.</span><span class="n">EventLoopPolicy</span><span class="p">())</span>
<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<hr />

<h2 id="13-常见陷阱与最佳实践">13. 常见陷阱与最佳实践</h2>

<h3 id="-陷阱-1在协程中使用同步阻塞调用">❌ 陷阱 1：在协程中使用同步阻塞调用</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">time</span>
<span class="kn">import</span> <span class="nn">requests</span>  <span class="c1"># 同步库
</span>
<span class="c1"># ❌ 错误：阻塞整个事件循环
</span><span class="k">async</span> <span class="k">def</span> <span class="nf">bad_fetch</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
    <span class="n">time</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>              <span class="c1"># 阻塞！
</span>    <span class="k">return</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>   <span class="c1"># 阻塞！
</span>
<span class="c1"># ✅ 正确：使用异步库
</span><span class="k">async</span> <span class="k">def</span> <span class="nf">good_fetch</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">aiohttp</span><span class="p">.</span><span class="n">ClientSession</span><span class="p">()</span> <span class="k">as</span> <span class="n">s</span><span class="p">:</span>
        <span class="k">async</span> <span class="k">with</span> <span class="n">s</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">as</span> <span class="n">r</span><span class="p">:</span>
            <span class="k">return</span> <span class="k">await</span> <span class="n">r</span><span class="p">.</span><span class="n">text</span><span class="p">()</span>
</code></pre></div></div>

<h3 id="-陷阱-2忘记-await">❌ 陷阱 2：忘记 await</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># ❌ 错误：coro() 只是创建了协程对象，根本没有运行
</span><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">result</span> <span class="o">=</span> <span class="n">some_async_func</span><span class="p">()</span>   <span class="c1"># 漏掉了 await！
</span>    <span class="k">print</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>                <span class="c1"># 打印的是协程对象
</span>
<span class="c1"># ✅ 正确
</span><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">some_async_func</span><span class="p">()</span>
    <span class="k">print</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</code></pre></div></div>

<p>Python 会发出 <code class="language-plaintext highlighter-rouge">RuntimeWarning: coroutine 'xxx' was never awaited</code> 警告。</p>

<h3 id="-陷阱-3顺序-await-而非并发">❌ 陷阱 3：顺序 await 而非并发</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># ❌ 错误：顺序执行，没有并发
</span><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">r1</span> <span class="o">=</span> <span class="k">await</span> <span class="n">fetch</span><span class="p">(</span><span class="s">"url1"</span><span class="p">)</span>  <span class="c1"># 等这个完成
</span>    <span class="n">r2</span> <span class="o">=</span> <span class="k">await</span> <span class="n">fetch</span><span class="p">(</span><span class="s">"url2"</span><span class="p">)</span>  <span class="c1"># 再执行这个
</span>    <span class="n">r3</span> <span class="o">=</span> <span class="k">await</span> <span class="n">fetch</span><span class="p">(</span><span class="s">"url3"</span><span class="p">)</span>  <span class="c1"># 总耗时 = 三者之和
</span>
<span class="c1"># ✅ 正确：真正并发
</span><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">r1</span><span class="p">,</span> <span class="n">r2</span><span class="p">,</span> <span class="n">r3</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span>
        <span class="n">fetch</span><span class="p">(</span><span class="s">"url1"</span><span class="p">),</span>
        <span class="n">fetch</span><span class="p">(</span><span class="s">"url2"</span><span class="p">),</span>
        <span class="n">fetch</span><span class="p">(</span><span class="s">"url3"</span><span class="p">),</span>
    <span class="p">)</span>
</code></pre></div></div>

<h3 id="-陷阱-4在已有事件循环中调用-asynciorun">❌ 陷阱 4：在已有事件循环中调用 asyncio.run()</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># ❌ 在 Jupyter Notebook 或已有事件循环的环境中会报错
</span><span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>

<span class="c1"># ✅ Jupyter 中直接 await
</span><span class="k">await</span> <span class="n">main</span><span class="p">()</span>

<span class="c1"># 或使用 nest_asyncio
</span><span class="kn">import</span> <span class="nn">nest_asyncio</span>
<span class="n">nest_asyncio</span><span class="p">.</span><span class="nb">apply</span><span class="p">()</span>
<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<h3 id="-最佳实践总结">✅ 最佳实践总结</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 1. Session 复用，不要每次请求都创建
</span><span class="k">async</span> <span class="k">with</span> <span class="n">aiohttp</span><span class="p">.</span><span class="n">ClientSession</span><span class="p">()</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="n">fetch</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="n">url</span><span class="p">)</span> <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">urls</span><span class="p">])</span>

<span class="c1"># 2. 用 Semaphore 控制并发量，避免打垮服务器
</span><span class="n">sem</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">Semaphore</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">safe_fetch</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">sem</span><span class="p">:</span>
        <span class="k">return</span> <span class="k">await</span> <span class="n">fetch</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>

<span class="c1"># 3. 总是处理异常，避免 Task 静默失败
</span><span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">,</span> <span class="n">return_exceptions</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>

<span class="c1"># 4. 设置超时
</span><span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">wait_for</span><span class="p">(</span><span class="n">coro</span><span class="p">(),</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">30</span><span class="p">)</span>

<span class="c1"># 5. 取消不需要的 Task
</span><span class="n">task</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">coro</span><span class="p">())</span>
<span class="n">task</span><span class="p">.</span><span class="n">cancel</span><span class="p">()</span>
<span class="k">try</span><span class="p">:</span>
    <span class="k">await</span> <span class="n">task</span>
<span class="k">except</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">CancelledError</span><span class="p">:</span>
    <span class="k">pass</span>  <span class="c1"># 正常取消
</span></code></pre></div></div>

<hr />

<h2 id="14-性能对比实验">14. 性能对比实验</h2>

<h3 id="实验抓取-50-个-url">实验：抓取 50 个 URL</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">import</span> <span class="nn">aiohttp</span>
<span class="kn">import</span> <span class="nn">requests</span>
<span class="kn">import</span> <span class="nn">time</span>

<span class="n">URLS</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s">"https://httpbin.org/delay/1"</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">)]</span>

<span class="c1"># ① 同步版本
</span><span class="k">def</span> <span class="nf">sync_main</span><span class="p">():</span>
    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span>
    <span class="n">results</span> <span class="o">=</span> <span class="p">[</span><span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">).</span><span class="n">status_code</span> <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">URLS</span><span class="p">]</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"同步耗时：</span><span class="si">{</span><span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="si">:</span><span class="p">.</span><span class="mi">1</span><span class="n">f</span><span class="si">}</span><span class="s">s"</span><span class="p">)</span>

<span class="c1"># ② 异步版本
</span><span class="k">async</span> <span class="k">def</span> <span class="nf">async_main</span><span class="p">():</span>
    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">aiohttp</span><span class="p">.</span><span class="n">ClientSession</span><span class="p">()</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
        <span class="k">async</span> <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
            <span class="k">async</span> <span class="k">with</span> <span class="n">session</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">as</span> <span class="n">r</span><span class="p">:</span>
                <span class="k">return</span> <span class="n">r</span><span class="p">.</span><span class="n">status</span>
        <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="n">fetch</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">URLS</span><span class="p">])</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"异步耗时：</span><span class="si">{</span><span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="si">:</span><span class="p">.</span><span class="mi">1</span><span class="n">f</span><span class="si">}</span><span class="s">s"</span><span class="p">)</span>

<span class="c1"># ③ 限制并发的异步版本
</span><span class="k">async</span> <span class="k">def</span> <span class="nf">async_limited</span><span class="p">():</span>
    <span class="n">sem</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">Semaphore</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">aiohttp</span><span class="p">.</span><span class="n">ClientSession</span><span class="p">()</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
        <span class="k">async</span> <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
            <span class="k">async</span> <span class="k">with</span> <span class="n">sem</span><span class="p">:</span>
                <span class="k">async</span> <span class="k">with</span> <span class="n">session</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">as</span> <span class="n">r</span><span class="p">:</span>
                    <span class="k">return</span> <span class="n">r</span><span class="p">.</span><span class="n">status</span>
        <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="n">fetch</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">URLS</span><span class="p">])</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"限流异步耗时：</span><span class="si">{</span><span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="si">:</span><span class="p">.</span><span class="mi">1</span><span class="n">f</span><span class="si">}</span><span class="s">s"</span><span class="p">)</span>

<span class="n">sync_main</span><span class="p">()</span>               <span class="c1"># 同步耗时：10.x s
</span><span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">async_main</span><span class="p">())</span> <span class="c1"># 异步耗时：1.x s
</span><span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">async_limited</span><span class="p">())</span> <span class="c1"># 限流异步：2.x s
</span></code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>方式</th>
      <th>耗时（10个各延迟1s的请求）</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>同步</td>
      <td>≈ 10s</td>
    </tr>
    <tr>
      <td>异步（无限制）</td>
      <td>≈ 1s</td>
    </tr>
    <tr>
      <td>异步（并发限 5）</td>
      <td>≈ 2s</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="附录速查表">附录：速查表</h2>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 运行协程
</span><span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">coro</span><span class="p">())</span>

<span class="c1"># 并发等待，收集结果
</span><span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span><span class="n">coro1</span><span class="p">(),</span> <span class="n">coro2</span><span class="p">(),</span> <span class="n">coro3</span><span class="p">())</span>

<span class="c1"># 创建 Task（立即调度）
</span><span class="n">task</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">coro</span><span class="p">())</span>

<span class="c1"># 超时
</span><span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">wait_for</span><span class="p">(</span><span class="n">coro</span><span class="p">(),</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span>

<span class="c1"># 等待第一个完成
</span><span class="n">done</span><span class="p">,</span> <span class="n">pending</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">wait</span><span class="p">(</span><span class="n">tasks</span><span class="p">,</span> <span class="n">return_when</span><span class="o">=</span><span class="n">asyncio</span><span class="p">.</span><span class="n">FIRST_COMPLETED</span><span class="p">)</span>

<span class="c1"># 限流
</span><span class="n">sem</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">Semaphore</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
<span class="k">async</span> <span class="k">with</span> <span class="n">sem</span><span class="p">:</span> <span class="p">...</span>

<span class="c1"># 同步转异步（线程池）
</span><span class="k">await</span> <span class="n">loop</span><span class="p">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="bp">None</span><span class="p">,</span> <span class="n">sync_func</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">)</span>

<span class="c1"># 获取事件循环
</span><span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">get_running_loop</span><span class="p">()</span>

<span class="c1"># 异步 sleep
</span><span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</code></pre></div></div>

<hr />]]></content><author><name></name></author><summary type="html"><![CDATA[异步不是”更快”，而是”更聪明地等待”。]]></summary></entry></feed>