2026-02-06 03:34:50 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="demo-container">
|
|
|
|
|
|
<h4>async/await 机制演示</h4>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="controls">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<el-button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
:disabled="isRunning"
|
|
|
|
|
|
@click="runExample"
|
|
|
|
|
|
>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
{{ isRunning ? '运行中...' : '运行示例' }}
|
|
|
|
|
|
</el-button>
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<el-button
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
@click="reset"
|
|
|
|
|
|
>
|
|
|
|
|
|
重置
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-checkbox
|
|
|
|
|
|
v-model="showDetails"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
>
|
|
|
|
|
|
显示详细信息
|
|
|
|
|
|
</el-checkbox>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="code-section">
|
|
|
|
|
|
<div class="code-block">
|
|
|
|
|
|
<div class="code-header">
|
|
|
|
|
|
<span class="code-title">Python asyncio 示例</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<pre class="code-content"><code><span class="keyword">import</span> asyncio
|
|
|
|
|
|
|
|
|
|
|
|
<span class="keyword">async def</span> <span class="function">fetch_data</span>(url):
|
|
|
|
|
|
<span class="comment"># await 挂起,让出 CPU</span>
|
|
|
|
|
|
response = <span class="keyword">await</span> aiohttp.get(url)
|
|
|
|
|
|
<span class="comment"># I/O 完成后继续执行</span>
|
|
|
|
|
|
<span class="keyword">return</span> response.json()
|
|
|
|
|
|
|
|
|
|
|
|
<span class="keyword">async def</span> <span class="function">main</span>():
|
|
|
|
|
|
<span class="comment"># 并发执行</span>
|
|
|
|
|
|
tasks = [fetch_data(url) <span class="keyword">for</span> url <span class="keyword">in</span> urls]
|
|
|
|
|
|
results = <span class="keyword">await</span> asyncio.gather(*tasks)</code></pre>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="visualization">
|
|
|
|
|
|
<div class="timeline-container">
|
|
|
|
|
|
<h5>执行时间线</h5>
|
|
|
|
|
|
<div class="timeline">
|
|
|
|
|
|
<div class="time-axis">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="axis-label">
|
|
|
|
|
|
0ms
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="axis-label">
|
|
|
|
|
|
50ms
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="axis-label">
|
|
|
|
|
|
100ms
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="axis-label">
|
|
|
|
|
|
150ms
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="axis-label">
|
|
|
|
|
|
200ms
|
|
|
|
|
|
</div>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="thread-rows">
|
|
|
|
|
|
<div class="thread-row">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="row-label">
|
|
|
|
|
|
事件循环
|
|
|
|
|
|
</div>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
<div class="row-track">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div
|
|
|
|
|
|
class="execution-segment event-loop"
|
|
|
|
|
|
style="width: 100%;"
|
|
|
|
|
|
>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
调度中
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div
|
|
|
|
|
|
v-for="(task, idx) in tasks"
|
|
|
|
|
|
:key="task.id"
|
|
|
|
|
|
class="thread-row"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="row-label">
|
|
|
|
|
|
任务 {{ task.id }}
|
|
|
|
|
|
</div>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
<div class="row-track">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<template
|
|
|
|
|
|
v-for="(segment, sidx) in task.segments"
|
|
|
|
|
|
:key="sidx"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="execution-segment"
|
2026-02-06 03:34:50 +08:00
|
|
|
|
:class="{ 'active': segment.type === 'active', 'io': segment.type === 'io' }"
|
2026-02-18 17:38:10 +08:00
|
|
|
|
:style="{ left: segment.start + '%', width: segment.width + '%', backgroundColor: segment.color }"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span
|
|
|
|
|
|
v-if="segment.width > 8"
|
|
|
|
|
|
class="segment-label"
|
|
|
|
|
|
>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
{{ segment.type === 'active' ? '执行' : 'I/O' }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="stats-grid">
|
|
|
|
|
|
<div class="stat-card">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="stat-title">
|
|
|
|
|
|
并发任务数
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="stat-value">
|
|
|
|
|
|
{{ tasks.length }}
|
|
|
|
|
|
</div>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="stat-card">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="stat-title">
|
|
|
|
|
|
总执行时间
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="stat-value">
|
|
|
|
|
|
{{ totalTime }}ms
|
|
|
|
|
|
</div>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="stat-card">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="stat-title">
|
|
|
|
|
|
I/O 等待时间
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="stat-value">
|
|
|
|
|
|
{{ ioWaitTime }}ms
|
|
|
|
|
|
</div>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="stat-card">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="stat-title">
|
|
|
|
|
|
CPU 利用率
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="stat-value">
|
|
|
|
|
|
{{ cpuUtilization }}%
|
|
|
|
|
|
</div>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="explanation">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<el-alert
|
|
|
|
|
|
title="async/await 的优势"
|
|
|
|
|
|
type="success"
|
2026-02-06 03:34:50 +08:00
|
|
|
|
description="当一个任务遇到 I/O 操作(如网络请求)时,await 会让出 CPU,事件循环调度其他任务执行。I/O 完成后,任务从断点恢复。这种方式让单个线程可以并发处理数千个任务。"
|
2026-02-18 17:38:10 +08:00
|
|
|
|
show-icon
|
|
|
|
|
|
:closable="false"
|
|
|
|
|
|
/>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
|
import { ref, computed, watch } from 'vue'
|
|
|
|
|
|
|
|
|
|
|
|
const comparisonMode = ref('memory')
|
|
|
|
|
|
const coroutineCount = ref(1000)
|
|
|
|
|
|
const isRunning = ref(false)
|
|
|
|
|
|
const showDetails = ref(false)
|
|
|
|
|
|
|
|
|
|
|
|
const tasks = ref([])
|
|
|
|
|
|
|
|
|
|
|
|
// 颜色
|
|
|
|
|
|
const colors = ['#409eff', '#67c23a', '#e6a23c', '#f56c6c', '#909399']
|
|
|
|
|
|
|
|
|
|
|
|
// 计算统计数据
|
|
|
|
|
|
const totalTime = computed(() => {
|
|
|
|
|
|
if (tasks.value.length === 0) return 0
|
|
|
|
|
|
// 模拟总时间
|
|
|
|
|
|
return Math.round(50 + tasks.value.length * 10)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const ioWaitTime = computed(() => {
|
|
|
|
|
|
return Math.round(totalTime.value * 0.6)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const cpuUtilization = computed(() => {
|
|
|
|
|
|
return Math.round(100 - (ioWaitTime.value / totalTime.value) * 100)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 生成任务数据
|
|
|
|
|
|
function generateTasks() {
|
|
|
|
|
|
const count = Math.min(Math.floor(coroutineCount.value / 200), 5)
|
|
|
|
|
|
const newTasks = []
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
|
|
|
|
const segments = []
|
|
|
|
|
|
let currentPos = 5
|
|
|
|
|
|
|
|
|
|
|
|
// 生成交替的执行和I/O段
|
|
|
|
|
|
for (let j = 0; j < 3; j++) {
|
|
|
|
|
|
// 执行段
|
|
|
|
|
|
const execWidth = 10 + Math.random() * 10
|
|
|
|
|
|
segments.push({
|
|
|
|
|
|
type: 'active',
|
|
|
|
|
|
start: currentPos,
|
|
|
|
|
|
width: execWidth,
|
|
|
|
|
|
color: colors[i % colors.length]
|
|
|
|
|
|
})
|
|
|
|
|
|
currentPos += execWidth
|
|
|
|
|
|
|
|
|
|
|
|
// I/O段
|
|
|
|
|
|
const ioWidth = 15 + Math.random() * 10
|
|
|
|
|
|
segments.push({
|
|
|
|
|
|
type: 'io',
|
|
|
|
|
|
start: currentPos,
|
|
|
|
|
|
width: ioWidth,
|
|
|
|
|
|
color: '#dcdfe6'
|
|
|
|
|
|
})
|
|
|
|
|
|
currentPos += ioWidth
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
newTasks.push({
|
|
|
|
|
|
id: i + 1,
|
|
|
|
|
|
segments,
|
|
|
|
|
|
state: 'ready'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
tasks.value = newTasks
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 运行示例
|
|
|
|
|
|
function runExample() {
|
|
|
|
|
|
isRunning.value = true
|
|
|
|
|
|
generateTasks()
|
|
|
|
|
|
|
|
|
|
|
|
// 模拟运行
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
isRunning.value = false
|
|
|
|
|
|
}, 2000)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重置
|
|
|
|
|
|
function reset() {
|
|
|
|
|
|
tasks.value = []
|
|
|
|
|
|
isRunning.value = false
|
|
|
|
|
|
coroutineCount.value = 1000
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 监听协程数量变化
|
|
|
|
|
|
watch(coroutineCount, () => {
|
|
|
|
|
|
if (tasks.value.length > 0) {
|
|
|
|
|
|
generateTasks()
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化
|
|
|
|
|
|
generateTasks()
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.demo-container {
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
background: #f5f7fa;
|
2026-02-14 20:23:34 +08:00
|
|
|
|
border-radius: 6px;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
h4 {
|
|
|
|
|
|
margin: 0 0 16px 0;
|
|
|
|
|
|
color: #303133;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.controls {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.slider-label {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #606266;
|
|
|
|
|
|
min-width: 100px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.code-section {
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.code-block {
|
|
|
|
|
|
background: #282c34;
|
2026-02-14 20:23:34 +08:00
|
|
|
|
border-radius: 6px;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.code-header {
|
|
|
|
|
|
background: #21252b;
|
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
|
border-bottom: 1px solid #181a1f;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.code-title {
|
|
|
|
|
|
color: #abb2bf;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.code-content {
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
overflow-x: auto;
|
|
|
|
|
|
font-family: 'Fira Code', 'Consolas', monospace;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.keyword {
|
|
|
|
|
|
color: #c678dd;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.function {
|
|
|
|
|
|
color: #61afef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.comment {
|
|
|
|
|
|
color: #5c6370;
|
|
|
|
|
|
font-style: italic;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.visualization {
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.timeline-container {
|
|
|
|
|
|
background: white;
|
2026-02-14 20:23:34 +08:00
|
|
|
|
border-radius: 6px;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
padding: 16px;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.timeline-container h5 {
|
|
|
|
|
|
margin: 0 0 12px 0;
|
|
|
|
|
|
color: #303133;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.timeline {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.time-axis {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
padding: 8px 0;
|
|
|
|
|
|
border-bottom: 1px solid #e4e7ed;
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.axis-label {
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.thread-rows {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.thread-row {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: 100px 1fr;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.row-label {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #606266;
|
|
|
|
|
|
text-align: right;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.row-track {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
height: 24px;
|
|
|
|
|
|
background: #f5f7fa;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.execution-segment {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.execution-segment.io {
|
|
|
|
|
|
background: #dcdfe6 !important;
|
|
|
|
|
|
color: #606266;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.execution-segment.event-loop {
|
|
|
|
|
|
background: #409eff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.current-indicator {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
width: 2px;
|
|
|
|
|
|
background: #f56c6c;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.stats-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(4, 1fr);
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.stat-card {
|
|
|
|
|
|
background: white;
|
2026-02-14 20:23:34 +08:00
|
|
|
|
border-radius: 6px;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
padding: 12px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.stat-title {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.stat-value {
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
color: #303133;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.explanation {
|
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
|
.stats-grid {
|
|
|
|
|
|
grid-template-columns: repeat(2, 1fr);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.controls {
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: stretch;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.thread-row {
|
|
|
|
|
|
grid-template-columns: 60px 1fr;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|