-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
186 lines (173 loc) · 64.3 KB
/
index.html
File metadata and controls
186 lines (173 loc) · 64.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
<!doctype html>
<html lang="zh"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"><meta><title>Python异步编程:从多线程到协程 - 面向问题编程</title><link rel="manifest" href="/manifest.json"><meta name="application-name" content="面向问题编程"><meta name="msapplication-TileImage" content="/img/favicon.svg"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-title" content="面向问题编程"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="description" content="摘要C10K问题 仍是软件开发者致力于解决的一个难题。通常,我们通过thread、epoll或kqueue处理大量 I&#x2F;O操作,以避免软件阻塞在一些耗时的IO操作上。然而,由于数据共享和任务依赖性,开发可读且无错误的并发代码具有挑战性。尽管一些强大的工具,例如 Valgrind,帮助开发人员检测死锁或其他异步问题,当软件规模变大时,解决这些问题可能会很耗时。因此,许多编程语言(例如 Py"><meta property="og:type" content="blog"><meta property="og:title" content="Python异步编程:从多线程到协程"><meta property="og:url" content="https://www.heapoverflow.cn/pages/python-async/"><meta property="og:site_name" content="面向问题编程"><meta property="og:description" content="摘要C10K问题 仍是软件开发者致力于解决的一个难题。通常,我们通过thread、epoll或kqueue处理大量 I&#x2F;O操作,以避免软件阻塞在一些耗时的IO操作上。然而,由于数据共享和任务依赖性,开发可读且无错误的并发代码具有挑战性。尽管一些强大的工具,例如 Valgrind,帮助开发人员检测死锁或其他异步问题,当软件规模变大时,解决这些问题可能会很耗时。因此,许多编程语言(例如 Py"><meta property="og:locale" content="zh_CN"><meta property="og:image" content="https://www.heapoverflow.cn/img/my/event-loop-vs-thread.png"><meta property="article:published_time" content="2022-04-05T03:22:02.614Z"><meta property="article:modified_time" content="2022-04-05T03:27:01.922Z"><meta property="article:author" content="面向问题编程"><meta property="article:tag" content="Python"><meta property="twitter:card" content="summary"><meta property="twitter:image" content="/img/my/event-loop-vs-thread.png"><script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","mainEntityOfPage":{"@type":"WebPage","@id":"https://www.heapoverflow.cn/pages/python-async/"},"headline":"Python异步编程:从多线程到协程","image":["https://www.heapoverflow.cn/img/my/event-loop-vs-thread.png"],"datePublished":"2022-04-05T03:22:02.614Z","dateModified":"2022-04-05T03:27:01.922Z","author":{"@type":"Person","name":"面向问题编程"},"publisher":{"@type":"Organization","name":"面向问题编程","logo":{"@type":"ImageObject","url":"https://www.heapoverflow.cn/img/logo.svg"}},"description":"摘要C10K问题 仍是软件开发者致力于解决的一个难题。通常,我们通过thread、epoll或kqueue处理大量 I/O操作,以避免软件阻塞在一些耗时的IO操作上。然而,由于数据共享和任务依赖性,开发可读且无错误的并发代码具有挑战性。尽管一些强大的工具,例如 Valgrind,帮助开发人员检测死锁或其他异步问题,当软件规模变大时,解决这些问题可能会很耗时。因此,许多编程语言(例如 Py"}</script><link rel="canonical" href="https://www.heapoverflow.cn/pages/python-async/"><link rel="icon" href="/img/favicon.svg"><link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.2/css/all.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/styles/atom-one-light.css"><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Ubuntu:wght@400;600&family=Source+Code+Pro"><link rel="stylesheet" href="/css/default.css"><style>body>.footer,body>.navbar,body>.section{opacity:0}</style><!--!--><!--!--><!--!--><!--!--><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/build/cookieconsent.min.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/lightgallery.min.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/justifiedGallery.min.css"><script src="https://www.googletagmanager.com/gtag/js?id=G-ETGL163DQH" async></script><script>window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-ETGL163DQH');</script><!--!--><!--!--><style>.pace{-webkit-pointer-events:none;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.pace-inactive{display:none}.pace .pace-progress{background:#3273dc;position:fixed;z-index:2000;top:0;right:100%;width:100%;height:2px}</style><script src="https://cdn.jsdelivr.net/npm/[email protected]/pace.min.js"></script><!--!--><!--!--><script data-ad-client="ca-pub-6757924689439593" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js" async></script><!-- hexo injector head_end start --><script>
(function () {
function switchTab() {
if (!location.hash) {
return;
}
Array
.from(document.querySelectorAll('.tab-content'))
.forEach($tab => {
$tab.classList.add('is-hidden');
});
Array
.from(document.querySelectorAll('.tabs li'))
.forEach($tab => {
$tab.classList.remove('is-active');
});
const $activeTab = document.querySelector(location.hash);
if ($activeTab) {
$activeTab.classList.remove('is-hidden');
}
const $tabMenu = document.querySelector(`a[href="${location.hash}"]`);
if ($tabMenu) {
$tabMenu.parentElement.classList.add('is-active');
}
}
switchTab();
window.addEventListener('hashchange', switchTab, false);
})();
</script><!-- hexo injector head_end end --><meta name="generator" content="Hexo 6.0.0"></head><body class="is-2-column"><nav class="navbar navbar-main"><div class="container"><div class="navbar-brand justify-content-center"><a class="navbar-item navbar-logo" href="/"><img src="/img/logo.svg" alt="面向问题编程" height="28"></a></div><div class="navbar-menu"><div class="navbar-start"><a class="navbar-item" href="/">首页</a><a class="navbar-item" href="/categories/%E9%97%AE%E9%A2%98/">问题</a><a class="navbar-item" href="/categories/%E6%8A%80%E6%9C%AF%E6%9E%B6%E6%9E%84/">技术架构</a><a class="navbar-item" href="/categories/%E4%B8%80%E6%96%87%E4%BA%86%E8%A7%A3/">一文了解</a></div><div class="navbar-end"><a class="navbar-item" rel="noopener" title="分类" href="/categories">分类</a><a class="navbar-item" rel="noopener" title="标签" href="/tags">标签</a><a class="navbar-item" rel="noopener" title="归档" href="/archives">归档</a><a class="navbar-item" rel="noopener" title="Knative中文网" href="https://www.heapoverflow.cn/knative/">Knative中文网</a><a class="navbar-item" rel="noopener" title="时间序列分析中文网" href="https://www.heapoverflow.cn/timeseries/">时间序列分析中文网</a><a class="navbar-item search" title="搜索" href="javascript:;"><i class="fas fa-search"></i></a></div></div></div></nav><section class="section"><div class="container"><div class="columns"><div class="column order-2 column-main is-8-tablet is-8-desktop is-9-widescreen"><div class="card"><div class="card-image"><span class="image is-7by3"><img class="fill" src="/img/my/event-loop-vs-thread.png" alt="Python异步编程:从多线程到协程"></span></div><article class="card-content article" role="article"><div class="article-meta is-size-7 is-uppercase level is-mobile"><div class="level-left"><span class="level-item"><time dateTime="2022-04-05T03:22:02.614Z" title="2022/4/5 上午11:22:02">2022-04-05</time>发表</span><span class="level-item"><time dateTime="2022-04-05T03:27:01.922Z" title="2022/4/5 上午11:27:01">2022-04-05</time>更新</span><span class="level-item">24 分钟读完 (大约3650个字)</span></div></div><h1 class="title is-3 is-size-4-mobile">Python异步编程:从多线程到协程</h1><div class="content"><h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p><a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/C10k_problem">C10K问题</a>
仍是软件开发者致力于解决的一个难题。通常,我们通过thread、epoll或kqueue处理大量 I/O操作,以避免软件阻塞在一些耗时的IO操作上。然而,由于数据共享和任务依赖性,开发可读且无错误的并发代码具有挑战性。尽管一些强大的工具,例如
<a target="_blank" rel="noopener" href="https://valgrind.org/">Valgrind</a>,帮助开发人员检测死锁或其他异步问题,当软件规模变大时,解决这些问题可能会很耗时。因此,许多编程语言(例如
Python、Javascript 或C++)致力于开发更好的库、框架或语法,以帮助程序员正确管理并发任务。本文主要关注异步编程模式背后的设计理念,而不是关注如何使用现代并行API。</p>
<span id="more"></span>
<p>使用线程是开发人员在不阻塞主线程的情况下调度任务的更自然的方式。但是,线程可能会导致性能问题,例如对临界区加锁以执行某些原子操作。尽管在某些情况下使用事件循环可以提高性能,但由于回调问题(例如,回调地狱),编写可读代码具有挑战性。幸运的是,像
Python这样的编程语言引入了一个概念async/await,以帮助开发人员编写易于理解的高性能代码。下图显示了如何像利用线程一样使用async/await处理套接字连接</p>
<p><img src="/img/my/event-loop-vs-thread.png" alt="image"></p>
<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>处理诸如网络连接之类的 I/O 操作是程序中最昂贵的任务之一。以一个简单的TCP 阻塞回显服务器为例(以下代码段)。如果客户端成功连接到服务器
而没有发送任何请求时,它仍会阻塞其他人连接到服务器。此外,服务器对并发请求的处理效率低下,因为它浪费了大量时间等待来自硬件(如网络接口)的
I/O 响应。 因此,并发套接字编程对于管理大量请求变得不可避免。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"></span><br><span class="line">s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, <span class="number">0</span>)</span><br><span class="line">s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, <span class="number">1</span>)</span><br><span class="line">s.bind((<span class="string">"127.0.0.1"</span>, <span class="number">5566</span>))</span><br><span class="line">s.listen(<span class="number">10</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> conn, addr = s.accept()</span><br><span class="line"> msg = conn.recv(<span class="number">1024</span>)</span><br><span class="line"> conn.send(msg)</span><br></pre></td></tr></table></figure>
<p>防止服务器等待 I/O操作的一种可能解决方案是将任务分派到其他线程。下面的例子展示了如何创建线程来同时处理连接。但是,创建大量的线程可能会消耗所有计算能力,而没有高吞吐量。更糟糕的是,应用程序可能会浪费时间等待锁来处理临界区中的任务。尽管使用线程可以解决套接字服务器的阻塞问题,但其他因素(例如
CPU 利用率)对于程序员克服 C10k问题至关重要。因此,在不创建无限线程的情况下,事件循环是另一种管理连接的解决方案。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> threading</span><br><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"></span><br><span class="line">s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)</span><br><span class="line">s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, <span class="number">1</span>)</span><br><span class="line">s.bind((<span class="string">"127.0.0.1"</span>, <span class="number">5566</span>))</span><br><span class="line">s.listen(<span class="number">10240</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">handler</span>(<span class="params">conn</span>):</span><br><span class="line"> <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> msg = conn.recv(<span class="number">65535</span>)</span><br><span class="line"> conn.send(msg)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> conn, addr = s.accept()</span><br><span class="line"> t = threading.Thread(target=handler, args=(conn,))</span><br><span class="line"> t.start()</span><br></pre></td></tr></table></figure>
<p>一个简单的事件驱动套接字服务器包括三个主要组件:I/O 多路复用模块 (例如<a target="_blank" rel="noopener" href="https://docs.python.org/3/library/select.html">select</a>), 一个调度器
(事件循环), 和回调函数(事件) (事件)。 例如,以下服务器在循环中利用I/O多路复用选择器,<a target="_blank" rel="noopener" href="https://docs.python.org/3/library/selectors.html">selectors</a>
不了解selectors,(建议先看python官方文档), 来检查 I/O操作是否准备就绪。如果数据可用于读/写, 则循环获取 I/O事件并执行回调函数accept、read、 或write以完成任务。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> selectors <span class="keyword">import</span> DefaultSelector, EVENT_READ, EVENT_WRITE</span><br><span class="line"><span class="keyword">from</span> functools <span class="keyword">import</span> partial</span><br><span class="line"></span><br><span class="line">s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)</span><br><span class="line">s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, <span class="number">1</span>)</span><br><span class="line">s.bind((<span class="string">"127.0.0.1"</span>, <span class="number">5566</span>))</span><br><span class="line">s.listen(<span class="number">10240</span>)</span><br><span class="line">s.setblocking(<span class="literal">False</span>)</span><br><span class="line"></span><br><span class="line">sel = DefaultSelector()</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">accept</span>(<span class="params">s, mask</span>):</span><br><span class="line"> conn, addr = s.accept() <span class="comment"># read socket</span></span><br><span class="line"> conn.setblocking(<span class="literal">False</span>)</span><br><span class="line"> sel.register(conn, EVENT_READ, read) <span class="comment"># when conn read is available, call read function</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">read</span>(<span class="params">conn, mask</span>):</span><br><span class="line"> msg = conn.recv(<span class="number">65535</span>) <span class="comment"># read conn</span></span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> msg:</span><br><span class="line"> sel.unregister(conn)</span><br><span class="line"> <span class="keyword">return</span> conn.close()</span><br><span class="line"> sel.modify(conn, EVENT_WRITE, partial(write, msg=msg)) <span class="comment"># modify = unregister + register, when conn write is available, call write function</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">write</span>(<span class="params">conn, mask, msg=<span class="literal">None</span></span>):</span><br><span class="line"> <span class="keyword">if</span> msg:</span><br><span class="line"> conn.send(msg)</span><br><span class="line"> sel.modify(conn, EVENT_READ, read) <span class="comment"># when conn read is available, call read function</span></span><br><span class="line"></span><br><span class="line">sel.register(s, EVENT_READ, accept) <span class="comment"># when socket read is available, call accept function</span></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> events = sel.select()</span><br><span class="line"> <span class="keyword">for</span> e, m <span class="keyword">in</span> events:</span><br><span class="line"> cb = e.data</span><br><span class="line"> cb(e.fileobj, m)</span><br></pre></td></tr></table></figure>
<p>尽管通过线程管理连接可能效率不高,但利用事件循环来调度任务的程序并不容易阅读。为了提高代码可读性,包括
Python 在内的许多编程语言引入了抽象概念,例如协程、future或 async/await来处理 I/O多路复用。
为了更好地理解编程术语并正确使用它们,以下部分将讨论这些概念是什么以及它们试图解决什么样的问题。</p>
<ins class="adsbygoogle"
style="display:block; text-align:center;"
data-ad-layout="in-article"
data-ad-format="fluid"
data-ad-client="ca-pub-6757924689439593"
data-ad-slot="2352785969"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>
<h2 id="回调函数"><a href="#回调函数" class="headerlink" title="回调函数"></a>回调函数</h2><p>当事件发生时,回调函数用于在运行时控制数据流。然而,保持当前回调函数的状态是具有挑战性的。例如,如果程序员想要通过
TCP 服务器实现握手,他/她可能需要在某个地方存储以前的状态。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># client should send hello to server before sending other msg</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> selectors <span class="keyword">import</span> DefaultSelector, EVENT_READ, EVENT_WRITE</span><br><span class="line"><span class="keyword">from</span> functools <span class="keyword">import</span> partial</span><br><span class="line"></span><br><span class="line">s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)</span><br><span class="line">s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, <span class="number">1</span>)</span><br><span class="line">s.bind((<span class="string">"127.0.0.1"</span>, <span class="number">5566</span>))</span><br><span class="line">s.listen(<span class="number">10240</span>)</span><br><span class="line">s.setblocking(<span class="literal">False</span>)</span><br><span class="line"></span><br><span class="line">sel = DefaultSelector()</span><br><span class="line">is_hello = {}</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">accept</span>(<span class="params">s, mask</span>):</span><br><span class="line"> conn, addr = s.accept()</span><br><span class="line"> conn.setblocking(<span class="literal">False</span>)</span><br><span class="line"> is_hello[conn] = <span class="literal">False</span>;</span><br><span class="line"> sel.register(conn, EVENT_READ, read)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">read</span>(<span class="params">conn, mask</span>):</span><br><span class="line"> msg = conn.recv(<span class="number">65535</span>)</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> msg:</span><br><span class="line"> sel.unregister(conn)</span><br><span class="line"> <span class="keyword">return</span> conn.close()</span><br><span class="line"></span><br><span class="line"> <span class="comment"># check whether handshake is successful or not</span></span><br><span class="line"> <span class="keyword">if</span> is_hello[conn]:</span><br><span class="line"> sel.modify(conn, EVENT_WRITE, partial(write, msg=msg))</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># do a handshake</span></span><br><span class="line"> <span class="keyword">if</span> msg.decode(<span class="string">"utf-8"</span>).strip() != <span class="string">"hello"</span>:</span><br><span class="line"> sel.unregister(conn)</span><br><span class="line"> <span class="keyword">return</span> conn.close()</span><br><span class="line"></span><br><span class="line"> is_hello[conn] = <span class="literal">True</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">write</span>(<span class="params">conn, mask, msg=<span class="literal">None</span></span>):</span><br><span class="line"> <span class="keyword">if</span> msg:</span><br><span class="line"> conn.send(msg)</span><br><span class="line"> sel.modify(conn, EVENT_READ, read)</span><br><span class="line"></span><br><span class="line">sel.register(s, EVENT_READ, accept)</span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> events = sel.select()</span><br><span class="line"> <span class="keyword">for</span> e, m <span class="keyword">in</span> events:</span><br><span class="line"> cb = e.data</span><br><span class="line"> cb(e.fileobj, m)</span><br></pre></td></tr></table></figure>
<p>尽管该变量 <code>is_hello</code>有助于存储状态以检查握手是否成功,但代码对于程序员来说变得更难理解。其实前面实现的概念很简单。它等于以下代码段(阻塞版本)。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">accept</span>(<span class="params">s</span>):</span><br><span class="line"> conn, addr = s.accept()</span><br><span class="line"> success = handshake(conn)</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> success:</span><br><span class="line"> conn.close()</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">handshake</span>(<span class="params">conn</span>):</span><br><span class="line"> data = conn.recv(<span class="number">65535</span>)</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> data:</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"> <span class="keyword">if</span> data.decode(<span class="string">'utf-8'</span>).strip() != <span class="string">"hello"</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"> conn.send(<span class="string">b"hello"</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">True</span></span><br></pre></td></tr></table></figure>
<p>要将类似的结构从阻塞迁移到非阻塞,函数(或任务)需要在等待 I/O操作时对当前状态进行快照,包括参数、变量和断点。此外,调度程序应该能够在
I/O 操作完成后重新进入该函数并执行剩余的代码。与 C++等其他编程语言不同,Python可以轻松实现上面讨论的概念,
因为它的*<em>生成器可以通过调用内置函数next()来保留所有状态和重新进入。通过使用生成器,可以像前面的代码片段一样处理
I/O 操作,但可以在事件循环内访问一个称为</em>内联回调*的非阻塞形式。</p>
<h2 id="事件循环"><a href="#事件循环" class="headerlink" title="事件循环"></a>事件循环</h2><p>事件循环是一个调度程序,用于管理程序内的任务,而不是依赖于操作系统。下面的代码片段展示了一个简单的事件循环如何异步处理套接字连接。实现原理是将任务追加到一个FIFO作业队列中,并在I/O操作未准备好时注册一个选择器。
此外,生成器保留任务的状态,使其能够在I/O结果可用时无需回调函数即可执行剩余作业。因此,通过观察事件循环的工作原理,将有助于理解Python 生成器确实是一种形式的 <em>协程</em>。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># loop.py</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Loop</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line"> self.sel = DefaultSelector()</span><br><span class="line"> self.queue = []</span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">create_task</span>(<span class="params">self, task</span>):</span><br><span class="line"> self.queue.append(task)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">polling</span>(<span class="params">self</span>):</span><br><span class="line"> <span class="comment"># select(0), wait until some registered file objects become ready,</span></span><br><span class="line"> <span class="comment"># 0 means the call won’t block, and will report the currently ready file objects</span></span><br><span class="line"> <span class="keyword">for</span> e, m <span class="keyword">in</span> self.sel.select(<span class="number">0</span>):</span><br><span class="line"> <span class="comment"># e.data=callback, here is generator main or generator handler</span></span><br><span class="line"> self.queue.append((e.data, <span class="literal">None</span>)) <span class="comment"># e.data等待的event已就绪,将e.data加到队列</span></span><br><span class="line"> self.sel.unregister(e.fileobj) <span class="comment"># 从selector中移除</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">is_registered</span>(<span class="params">self, fileobj</span>):</span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> self.sel.get_key(fileobj)</span><br><span class="line"> <span class="keyword">except</span> KeyError:</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">register</span>(<span class="params">self, t, data</span>):</span><br><span class="line"> <span class="comment"># data = (event_mask, fileobj), when event is ready on fileobj, call task t.</span></span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> data:</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> data[<span class="number">0</span>] == EVENT_READ:</span><br><span class="line"> <span class="keyword">if</span> self.is_registered(data[<span class="number">1</span>]):</span><br><span class="line"> self.sel.modify(data[<span class="number">1</span>], EVENT_READ, t)</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> self.sel.register(data[<span class="number">1</span>], EVENT_READ, t)</span><br><span class="line"> <span class="keyword">elif</span> data[<span class="number">0</span>] == EVENT_WRITE:</span><br><span class="line"> <span class="keyword">if</span> self.is_registered(data[<span class="number">1</span>]):</span><br><span class="line"> self.sel.modify(data[<span class="number">1</span>], EVENT_WRITE, t)</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> self.sel.register(data[<span class="number">1</span>], EVENT_WRITE, t)</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">accept</span>(<span class="params">self, s</span>):</span><br><span class="line"> conn, addr = <span class="literal">None</span>, <span class="literal">None</span></span><br><span class="line"> <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> conn, addr = s.accept()</span><br><span class="line"> <span class="keyword">except</span> BlockingIOError:</span><br><span class="line"> <span class="keyword">yield</span> (EVENT_READ, s) <span class="comment"># not ready, yield, 交出控制权</span></span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="keyword">break</span> <span class="comment"># ready, 返回conn, addr</span></span><br><span class="line"> <span class="keyword">return</span> conn, addr</span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">recv</span>(<span class="params">self, conn, size</span>):</span><br><span class="line"> msg = <span class="literal">None</span></span><br><span class="line"> <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> msg = conn.recv(<span class="number">1024</span>)</span><br><span class="line"> <span class="keyword">except</span> BlockingIOError:</span><br><span class="line"> <span class="keyword">yield</span> (EVENT_READ, conn) <span class="comment"># not ready, yield, 交出控制权</span></span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="keyword">break</span></span><br><span class="line"> <span class="keyword">return</span> msg</span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">send</span>(<span class="params">self, conn, msg</span>):</span><br><span class="line"> size = <span class="number">0</span></span><br><span class="line"> <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> size = conn.send(msg)</span><br><span class="line"> <span class="keyword">except</span> BlockingIOError:</span><br><span class="line"> <span class="keyword">yield</span> (EVENT_WRITE, conn)</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="keyword">break</span></span><br><span class="line"> <span class="keyword">return</span> size</span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">once</span>(<span class="params">self</span>):</span><br><span class="line"> self.polling()</span><br><span class="line"> <span class="keyword">while</span> <span class="built_in">len</span>(self.queue) > <span class="number">0</span>:</span><br><span class="line"> t, data = self.queue.pop(<span class="number">0</span>)</span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> data = t.send(data) <span class="comment"># 返回的data = (event_mask, fileobj)</span></span><br><span class="line"> <span class="keyword">except</span> StopIteration:</span><br><span class="line"> <span class="keyword">continue</span></span><br><span class="line"> self.register(t, data) <span class="comment"># task t 未就绪,将t在等待的(event_mask, fileobj)加到selector</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">run</span>(<span class="params">self</span>):</span><br><span class="line"> <span class="keyword">while</span> self.queue <span class="keyword">or</span> self.sel.get_map():</span><br><span class="line"> self.once()</span><br></pre></td></tr></table></figure>
<p>通过将任务分配到事件循环来处理连接,编程模式类似于使用线程来管理 I/O操作,但使用用户级调度程序。此外, <a target="_blank" rel="noopener" href="https://www.python.org/dev/peps/pep-0380/">PEP 380</a>
启用了生成器委托,这允许生成器可以等待其他生成器完成他们的工作。显然,下面的代码片段比使用回调函数来处理I/O 操作更加直观和可读(P.S. 但上面那个loop可不好读)。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># foo.py</span></span><br><span class="line"><span class="comment"># $ python3 foo.py &</span></span><br><span class="line"><span class="comment"># $ nc localhost 5566</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> selectors <span class="keyword">import</span> EVENT_READ, EVENT_WRITE</span><br><span class="line"></span><br><span class="line"><span class="comment"># import loop.py</span></span><br><span class="line"><span class="keyword">from</span> loop <span class="keyword">import</span> Loop</span><br><span class="line"></span><br><span class="line">s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)</span><br><span class="line">s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, <span class="number">1</span>)</span><br><span class="line">s.bind((<span class="string">"127.0.0.1"</span>, <span class="number">5566</span>))</span><br><span class="line">s.listen(<span class="number">10240</span>)</span><br><span class="line">s.setblocking(<span class="literal">False</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">handler</span>(<span class="params">conn</span>):</span><br><span class="line"> <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> msg = <span class="keyword">yield</span> <span class="keyword">from</span> loop.recv(conn, <span class="number">1024</span>) <span class="comment"># 委托生成器, 右边迭代完时,msg 为 recv 的return</span></span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> msg:</span><br><span class="line"> conn.close()</span><br><span class="line"> <span class="keyword">break</span></span><br><span class="line"> <span class="keyword">yield</span> <span class="keyword">from</span> loop.send(conn, msg) <span class="comment"># 委托生成器</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line"> <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> conn, addr = <span class="keyword">yield</span> <span class="keyword">from</span> loop.accept(s) <span class="comment"># 委托生成器, 右边迭代完时,conn, addr 为 accept 的return</span></span><br><span class="line"> conn.setblocking(<span class="literal">False</span>)</span><br><span class="line"> loop.create_task((handler(conn), <span class="literal">None</span>))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">loop.create_task((main(), <span class="literal">None</span>))</span><br><span class="line">loop.run()</span><br></pre></td></tr></table></figure>
<ul>
<li>初始selector为空,队列 = [(生成器main, None)]</li>
<li>run -> once polling(空) -> once队列循环, send None 到 生成器
main, main为委托生成器,实际send到accept</li>
<li>没有连接时, accept函数 yield (EVENT_READ, socket),
这是once里t.send的返回值,然后将(EVENT_READ, socket,
main)注册到selector.</li>
<li>连接来时,selector中select得到main, 将(main,
None)加到队列,同时将(EVENT_READ, socket, main)从selector中移除。</li>
<li>在下一次once调用时,main.send(None),main函数中得到accept返回的conn,
addr,将(handler(conn),None)加到队列, 再进入main循环,又yield from
accept, accept yield (EVENT_READ, socket), 因此又将(EVENT_READ,
socket, main)注册到selector。同时下次once调用时,处理handler协程</li>
</ul>
<p>使用带有 <code>yield from</code> 语法的事件循环,可以在不阻塞主线程的情况下管理连接,这是Python 3.5 之前<code>asyncio</code> 模块的用法。
然而,使用语法,<code>yield from</code>,是模棱两可的,因为它可能使程序员感到疑惑:为什么添加<code>@asyncio.coroutine</code>使生成器成为协程?
<a target="_blank" rel="noopener" href="https://www.python.org/dev/peps/pep-0492/">PEP 492</a> 建议协程应该成为Python 中的一个独立概念,而不是用yield
from处理异步操作,这就是为何引入新语法<code>async/await</code>,增强异步编程的可读性。</p>
<h2 id="什么是协程"><a href="#什么是协程" class="headerlink" title="什么是协程"></a>什么是协程</h2><p>Python
文档定义协程是子程序的一种广义形式。然而,这个定义是模棱两可的,阻碍了开发人员理解协程是什么。基于前面的讨论,事件循环负责调度生成器执行特定任务,这类似于将任务分派到线程。在这种情况下,生成器就像线程一样负责“日常工作”。显然,协程是一个术语,表示由程序中的事件循环而不是操作系统调度的任务。以下代码段显示了@coroutine是什么。此装饰器主要将函数转换为生成器函数并使用包装器
types.coroutine,以保持向后兼容性。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"><span class="keyword">import</span> inspect</span><br><span class="line"><span class="keyword">import</span> types</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> functools <span class="keyword">import</span> wraps</span><br><span class="line"><span class="keyword">from</span> asyncio.futures <span class="keyword">import</span> Future</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">coroutine</span>(<span class="params">func</span>):</span><br><span class="line"> <span class="string">"""Simple prototype of coroutine"""</span></span><br><span class="line"> <span class="keyword">if</span> inspect.isgeneratorfunction(func):</span><br><span class="line"> <span class="keyword">return</span> types.coroutine(func)</span><br><span class="line"></span><br><span class="line"><span class="meta"> @wraps(<span class="params">func</span>)</span></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">coro</span>(<span class="params">*a, **k</span>):</span><br><span class="line"> res = func(*a, **k)</span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">isinstance</span>(res, Future) <span class="keyword">or</span> inspect.isgenerator(res):</span><br><span class="line"> res = <span class="keyword">yield</span> <span class="keyword">from</span> res</span><br><span class="line"> <span class="keyword">return</span> res</span><br><span class="line"> <span class="keyword">return</span> types.coroutine(coro)</span><br><span class="line"></span><br><span class="line"><span class="meta">@coroutine</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">foo</span>():</span><br><span class="line"> <span class="keyword">yield</span> <span class="keyword">from</span> asyncio.sleep(<span class="number">1</span>)</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Hello Foo"</span>)</span><br><span class="line"></span><br><span class="line">loop = asyncio.get_event_loop()</span><br><span class="line">loop.run_until_complete(loop.create_task(foo()))</span><br><span class="line">loop.close()</span><br></pre></td></tr></table></figure>
<h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>由于现代语法和库的支持,如今通过事件循环进行的异步编程变得更加简单易读。大多数编程语言,包括Python,通过与新语法交互来实现管理任务调度的库。虽然新语法一开始看起来很神秘,但它们为程序员提供了一种在其代码中开发像线程一样的逻辑结构的方法。此外,由于在任务完成后不调用回调函数,程序员无需担心如何将当前任务状态(例如局部变量和参数)传递到其他回调中。因此,程序员将能够专注于开发他们的程序,而不会浪费时间来解决并发问题。</p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol>
<li><a target="_blank" rel="noopener" href="https://github.com/crazyguitar/pysheeet/blob/master/docs/appendix/python-concurrent.rst">pythoncconcurrent</a></li>
<li><a target="_blank" rel="noopener" href="https://docs.python.org/3/library/asyncio.html">asyncio — Asynchronous
I/O</a></li>
<li><a target="_blank" rel="noopener" href="https://www.python.org/dev/peps/pep-0342/">PEP 342 - Coroutines via Enhanced
Generators</a></li>
<li><a target="_blank" rel="noopener" href="https://www.python.org/dev/peps/pep-0380/">PEP 380 - Syntax for Delegating to a
Subgenerator</a></li>
<li><a target="_blank" rel="noopener" href="https://www.python.org/dev/peps/pep-0492/">PEP 492 - Coroutines with async and await
syntax</a></li>
</ol>
</div><div class="article-tags is-size-7 mb-4"><span class="mr-2">#</span><a class="link-muted mr-2" rel="tag" href="/tags/Python/">Python</a></div><!--!--><div><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-6757924689439593" crossOrigin="anonymous"></script><ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-6757924689439593" data-ad-slot="2352785969"></ins><script>(adsbygoogle = window.adsbygoogle || []).push({});</script></div></article></div><!--!--><nav class="post-navigation mt-4 level is-mobile"><div class="level-start"><a class="article-nav-prev level level-item link-muted" href="/pages/practice-xiecheng-k8s-1/"><i class="level-item fas fa-chevron-left"></i><span class="level-item">携程混合云之Kubernetes at AWS揭秘</span></a></div><div class="level-end"><a class="article-nav-next level level-item link-muted" href="/pages/cpython-5/"><span class="level-item">谈Python的执行过程(3):函数的实现</span><i class="level-item fas fa-chevron-right"></i></a></div></nav><!--!--><div style="width:100%"><script src="https://giscus.app/client.js" data-repo="thetechstack/thetechstack.github.io" data-repo-id="R_kgDOG6Ti1Q" data-category="Announcements" data-category-id="DIC_kwDOG6Ti1c4COQtm" data-mapping="pathname" data-reactions-enabled="1" data-emit-metadata="0" data-input-position="top" data-theme="light" data-lang="zh-CN" crossOrigin="anonymous" async></script></div></div><!--!--><div class="column column-right is-4-tablet is-4-desktop is-3-widescreen order-3 is-sticky"><!--!--><div class="card widget" data-type="tags"><div class="card-content"><div class="menu"><h3 class="menu-label">标签</h3><div class="field is-grouped is-grouped-multiline"><div class="control"><a class="tags has-addons" href="/tags/CPython/"><span class="tag">CPython</span><span class="tag">5</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Feed%E6%9C%8D%E5%8A%A1/"><span class="tag">Feed服务</span><span class="tag">3</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Knative/"><span class="tag">Knative</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Kubernetes/"><span class="tag">Kubernetes</span><span class="tag">28</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Linux%E7%BD%91%E7%BB%9C/"><span class="tag">Linux网络</span><span class="tag">10</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Python/"><span class="tag">Python</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/RPC/"><span class="tag">RPC</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Serverless/"><span class="tag">Serverless</span><span class="tag">5</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Yarn/"><span class="tag">Yarn</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%80%E8%87%B4%E6%80%A7/"><span class="tag">分布式一致性</span><span class="tag">2</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%9C%A8%E7%A6%BB%E7%BA%BF%E6%B7%B7%E9%83%A8/"><span class="tag">在离线混部</span><span class="tag">4</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%A4%A7%E6%95%B0%E6%8D%AE/"><span class="tag">大数据</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%97%B6%E9%97%B4%E5%BA%8F%E5%88%97/"><span class="tag">时间序列</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%99%BA%E8%83%BD%E8%BF%90%E7%BB%B4/"><span class="tag">智能运维</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E7%BC%93%E5%AD%98/"><span class="tag">缓存</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86/"><span class="tag">编译原理</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E7%BD%91%E7%BB%9C%E8%99%9A%E6%8B%9F%E5%8C%96/"><span class="tag">网络虚拟化</span><span class="tag">3</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E8%99%9A%E6%8B%9F%E6%9C%BA/"><span class="tag">虚拟机</span><span class="tag">2</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E8%AF%AD%E8%A8%80%E7%89%B9%E6%80%A7/"><span class="tag">语言特性</span><span class="tag">2</span></a></div></div></div></div></div><div class="card widget" data-type="profile"><div class="card-content"><nav class="level"><div class="level-item has-text-centered flex-shrink-1"><div><figure class="image is-128x128 mx-auto mb-2"><img class="avatar" src="/img/avatar.jpg"></figure><p class="is-size-6 is-block">公众号:面向问题编程</p></div></div></nav><nav class="level is-mobile"><div class="level-item has-text-centered is-marginless"><div><p class="heading">文章</p><a href="/archives"><p class="title">75</p></a></div></div><div class="level-item has-text-centered is-marginless"><div><p class="heading">分类</p><a href="/categories"><p class="title">5</p></a></div></div><div class="level-item has-text-centered is-marginless"><div><p class="heading">标签</p><a href="/tags"><p class="title">19</p></a></div></div></nav></div></div><div class="card widget" data-type="categories"><div class="card-content"><div class="menu"><h3 class="menu-label">分类</h3><ul class="menu-list"><li><a class="level is-mobile" href="/categories/%E4%B8%80%E6%96%87%E4%BA%86%E8%A7%A3/"><span class="level-start"><span class="level-item">一文了解</span></span><span class="level-end"><span class="level-item tag">33</span></span></a></li><li><a class="level is-mobile" href="/categories/%E6%8A%80%E6%9C%AF%E5%AE%9E%E8%B7%B5/"><span class="level-start"><span class="level-item">技术实践</span></span><span class="level-end"><span class="level-item tag">11</span></span></a></li><li><a class="level is-mobile" href="/categories/%E6%8A%80%E6%9C%AF%E6%9E%B6%E6%9E%84/"><span class="level-start"><span class="level-item">技术架构</span></span><span class="level-end"><span class="level-item tag">8</span></span></a></li><li><a class="level is-mobile" href="/categories/%E7%BB%BC%E8%BF%B0/"><span class="level-start"><span class="level-item">综述</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/categories/%E9%97%AE%E9%A2%98/"><span class="level-start"><span class="level-item">问题</span></span><span class="level-end"><span class="level-item tag">2</span></span></a></li></ul></div></div></div></div></div></div></section><footer class="footer"><div class="container"><div class="level"><div class="level-start"><a class="footer-logo is-block mb-2" href="/"><img src="/img/logo.svg" alt="面向问题编程" height="28"></a><p class="is-size-7"><span>© 2022 面向问题编程</span></p></div><div class="level-end"><div class="field has-addons"><p class="control"><a class="button is-transparent is-large" target="_blank" rel="noopener" title="Creative Commons" href="https://creativecommons.org/"><i class="fab fa-creative-commons"></i></a></p><p class="control"><a class="button is-transparent is-large" target="_blank" rel="noopener" title="Attribution 4.0 International" href="https://creativecommons.org/licenses/by/4.0/"><i class="fab fa-creative-commons-by"></i></a></p></div></div></div></div></footer><script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script><script src="https://cdn.jsdelivr.net/npm/[email protected]/min/moment-with-locales.min.js"></script><script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/clipboard.min.js" defer></script><script>moment.locale("zh-CN");</script><script>var IcarusThemeSettings = {
article: {
highlight: {
clipboard: true,
fold: 'unfolded'
}
}
};</script><script src="/js/column.js"></script><script src="/js/animation.js"></script><a id="back-to-top" title="回到顶端" href="javascript:;"><i class="fas fa-chevron-up"></i></a><script src="/js/back_to_top.js" defer></script><!--!--><!--!--><!--!--><script src="https://cdn.jsdelivr.net/npm/[email protected]/build/cookieconsent.min.js" defer></script><script>window.addEventListener("load", () => {
window.cookieconsent.initialise({
type: "info",
theme: "edgeless",
static: false,
position: "bottom-left",
content: {
message: "此网站使用Cookie来改善您的体验。",
dismiss: "知道了!",
allow: "允许使用Cookie",
deny: "拒绝",
link: "了解更多",
policy: "Cookie政策",
href: "https://www.cookiesandyou.com/",
},
palette: {
popup: {
background: "#edeff5",
text: "#838391"
},
button: {
background: "#4b81e8"
},
},
});
});</script><script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/lightgallery.min.js" defer></script><script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/lg-zoom.min.js" defer></script><script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/jquery.justifiedGallery.min.js" defer></script><script>window.addEventListener("load", () => {
if (typeof $.fn.lightGallery === 'function') {
$('.article').lightGallery({ selector: '.gallery-item' });
}
if (typeof $.fn.justifiedGallery === 'function') {
if ($('.justified-gallery > p > .gallery-item').length) {
$('.justified-gallery > p > .gallery-item').unwrap();
}
$('.justified-gallery').justifiedGallery();
}
});</script><!--!--><!--!--><script type="text/x-mathjax-config">MathJax.Hub.Config({
'HTML-CSS': {
matchFontHeight: false
},
SVG: {
matchFontHeight: false
},
CommonHTML: {
matchFontHeight: false
},
tex2jax: {
inlineMath: [
['$','$'],
['\\(','\\)']
]
}
});</script><script src="https://cdn.jsdelivr.net/npm/[email protected]/unpacked/MathJax.js?config=TeX-MML-AM_CHTML" defer></script><!--!--><!--!--><!--!--><script src="/js/main.js" defer></script><div class="searchbox"><div class="searchbox-container"><div class="searchbox-header"><div class="searchbox-input-container"><input class="searchbox-input" type="text" placeholder="想要查找什么..."></div><a class="searchbox-close" href="javascript:;">×</a></div><div class="searchbox-body"></div></div></div><script src="/js/insight.js" defer></script><script>document.addEventListener('DOMContentLoaded', function () {
loadInsight({"contentUrl":"/content.json"}, {"hint":"想要查找什么...","untitled":"(无标题)","posts":"文章","pages":"页面","categories":"分类","tags":"标签"});
});</script></body></html>