feat(docs): add interactive demo components for technical appendices
Add placeholder Vue components for visualizing technical concepts across multiple domains including frontend routing, browser rendering, cache design, queue design, database principles, API design, cloud services, and backend evolution. These components provide interactive educational content for the documentation. Update documentation structure to include new appendix sections and enhance existing content with visual components. Remove unused 'codex' dependency from package.json.
This commit is contained in:
@@ -0,0 +1,401 @@
|
||||
<template>
|
||||
<div class="demo-container">
|
||||
<h4>async/await 机制演示</h4>
|
||||
|
||||
<div class="controls">
|
||||
<el-button type="primary" size="small" @click="runExample" :disabled="isRunning">
|
||||
{{ isRunning ? '运行中...' : '运行示例' }}
|
||||
</el-button>
|
||||
<el-button size="small" @click="reset">重置</el-button>
|
||||
<el-checkbox v-model="showDetails" size="small">显示详细信息</el-checkbox>
|
||||
</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">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="thread-rows">
|
||||
<div class="thread-row">
|
||||
<div class="row-label">事件循环</div>
|
||||
<div class="row-track">
|
||||
<div class="execution-segment event-loop" style="width: 100%;">
|
||||
调度中
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-for="(task, idx) in tasks" :key="task.id" class="thread-row">
|
||||
<div class="row-label">任务 {{ task.id }}</div>
|
||||
<div class="row-track">
|
||||
<template v-for="(segment, sidx) in task.segments" :key="sidx">
|
||||
<div class="execution-segment"
|
||||
:class="{ 'active': segment.type === 'active', 'io': segment.type === 'io' }"
|
||||
:style="{ left: segment.start + '%', width: segment.width + '%', backgroundColor: segment.color }">
|
||||
<span v-if="segment.width > 8" class="segment-label">
|
||||
{{ segment.type === 'active' ? '执行' : 'I/O' }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-title">并发任务数</div>
|
||||
<div class="stat-value">{{ tasks.length }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-title">总执行时间</div>
|
||||
<div class="stat-value">{{ totalTime }}ms</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-title">I/O 等待时间</div>
|
||||
<div class="stat-value">{{ ioWaitTime }}ms</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-title">CPU 利用率</div>
|
||||
<div class="stat-value">{{ cpuUtilization }}%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="explanation">
|
||||
<el-alert title="async/await 的优势" type="success"
|
||||
description="当一个任务遇到 I/O 操作(如网络请求)时,await 会让出 CPU,事件循环调度其他任务执行。I/O 完成后,任务从断点恢复。这种方式让单个线程可以并发处理数千个任务。"
|
||||
show-icon :closable="false" />
|
||||
</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;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
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;
|
||||
border-radius: 8px;
|
||||
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;
|
||||
border-radius: 8px;
|
||||
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;
|
||||
border-radius: 8px;
|
||||
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>
|
||||
+481
@@ -0,0 +1,481 @@
|
||||
<template>
|
||||
<div class="demo-container">
|
||||
<h4>并发 (Concurrency) vs 并行 (Parallelism) 演示</h4>
|
||||
|
||||
<div class="controls">
|
||||
<el-radio-group v-model="demoMode" size="small">
|
||||
<el-radio-button label="concurrent">单核并发</el-radio-button>
|
||||
<el-radio-button label="parallel">多核并行</el-radio-button>
|
||||
<el-radio-button label="hybrid">混合模式</el-radio-button>
|
||||
</el-radio-group>
|
||||
|
||||
<el-button type="primary" size="small" @click="startDemo" :disabled="isRunning">
|
||||
{{ isRunning ? '运行中...' : '开始演示' }}
|
||||
</el-button>
|
||||
|
||||
<el-button size="small" @click="reset">重置</el-button>
|
||||
|
||||
<el-slider v-model="workerCount" :min="1" :max="8" :step="1" show-stops style="width: 150px;"
|
||||
v-if="demoMode === 'parallel' || demoMode === 'hybrid'" />
|
||||
</div>
|
||||
|
||||
<div class="demo-grid">
|
||||
<!-- CPU 核心显示 -->
|
||||
<div class="section">
|
||||
<div class="section-title">
|
||||
{{ demoMode === 'concurrent' ? 'CPU 核心 (单核)' : 'CPU 核心 (' + cpuCores.length + '核)' }}
|
||||
</div>
|
||||
|
||||
<div class="cpu-grid" :class="{ 'single-core': demoMode === 'concurrent' }">
|
||||
<div v-for="(core, idx) in cpuCores" :key="idx" class="cpu-core" :class="{
|
||||
'active': core.active,
|
||||
'concurrent-mode': demoMode === 'concurrent',
|
||||
'parallel-mode': demoMode === 'parallel'
|
||||
}"
|
||||
:style="{ backgroundColor: core.active ? core.color : '#f5f7fa' }">
|
||||
<div class="core-number">CPU {{ idx + 1 }}</div>
|
||||
<div class="core-task" v-if="core.task">{{ core.task }}</div>
|
||||
<div class="core-status">{{ core.active ? '运行中' : '空闲' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 任务视图 -->
|
||||
<div class="section">
|
||||
<div class="section-title">任务执行</div>
|
||||
|
||||
<div class="task-timeline">
|
||||
<div v-for="(task, idx) in demoTasks" :key="task.id" class="task-row">
|
||||
<div class="task-info">
|
||||
<div class="task-name">任务 {{ task.id }}</div>
|
||||
<div class="task-duration">{{ task.duration }}ms</div>
|
||||
</div>
|
||||
|
||||
<div class="task-track">
|
||||
<div v-for="(segment, sidx) in task.segments" :key="sidx" class="task-segment"
|
||||
:class="{ 'execution': segment.type === 'execution', 'waiting': segment.type === 'waiting' }"
|
||||
:style="{ left: segment.start + '%', width: segment.width + '%' }">
|
||||
<span v-if="segment.width > 5" class="segment-text">
|
||||
{{ segment.type === 'execution' ? '执行' : '等待' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="comparison-table">
|
||||
<div class="table-header">并发 vs 并行 对比</div>
|
||||
|
||||
<div class="comparison-grid">
|
||||
<div class="comparison-item">
|
||||
<div class="item-icon">🔄</div>
|
||||
<div class="item-title">并发 (Concurrency)</div>
|
||||
<div class="item-desc">多个任务交替执行,宏观上同时推进</div>
|
||||
<div class="item-examples"><strong>例子:</strong> 单核CPU多线程、协程调度、异步I/O</div>
|
||||
</div>
|
||||
|
||||
<div class="comparison-item">
|
||||
<div class="item-icon">⚡</div>
|
||||
<div class="item-title">并行 (Parallelism)</div>
|
||||
<div class="item-desc">多个任务真正同时执行</div>
|
||||
<div class="item-examples"><strong>例子:</strong> 多核CPU计算、GPU并行计算、分布式处理</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="need-table">
|
||||
<div class="need-title">需要什么条件?</div>
|
||||
|
||||
<div class="need-items">
|
||||
<div class="need-item">
|
||||
<span class="need-check">✓</span>
|
||||
<span class="need-text"><strong>并发:</strong> 单核 CPU 即可实现</span>
|
||||
</div>
|
||||
|
||||
<div class="need-item">
|
||||
<span class="need-check need-multi">✓</span>
|
||||
<span class="need-text"><strong>并行:</strong> 需要多核 CPU 或多台机器</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const demoMode = ref('concurrent')
|
||||
const isRunning = ref(false)
|
||||
const workerCount = ref(4)
|
||||
|
||||
// CPU 核心
|
||||
const cpuCores = ref([
|
||||
{ active: false, color: '#409eff', task: null },
|
||||
{ active: false, color: '#67c23a', task: null },
|
||||
{ active: false, color: '#e6a23c', task: null },
|
||||
{ active: false, color: '#f56c6c', task: null },
|
||||
])
|
||||
|
||||
// 演示任务
|
||||
const demoTasks = ref([
|
||||
{ id: 1, duration: 40, segments: [] },
|
||||
{ id: 2, duration: 30, segments: [] },
|
||||
{ id: 3, duration: 50, segments: [] },
|
||||
{ id: 4, duration: 35, segments: [] },
|
||||
])
|
||||
|
||||
function startDemo() {
|
||||
if (isRunning.value) return
|
||||
|
||||
isRunning.value = true
|
||||
|
||||
// 生成任务时间线
|
||||
generateTaskTimeline()
|
||||
|
||||
// 模拟执行
|
||||
setTimeout(() => {
|
||||
isRunning.value = false
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
function generateTaskTimeline() {
|
||||
demoTasks.value.forEach((task, idx) => {
|
||||
const segments = []
|
||||
const mode = demoMode.value
|
||||
const colors = ['#409eff', '#67c23a', '#e6a23c', '#f56c6c']
|
||||
|
||||
if (mode === 'concurrent') {
|
||||
// 单核并发:任务交替执行
|
||||
const baseStart = 5 + idx * 3
|
||||
segments.push({
|
||||
type: 'execution',
|
||||
start: baseStart,
|
||||
width: task.duration / 3,
|
||||
color: colors[idx % colors.length]
|
||||
})
|
||||
segments.push({
|
||||
type: 'waiting',
|
||||
start: baseStart + task.duration / 3,
|
||||
width: 20,
|
||||
color: '#dcdfe6'
|
||||
})
|
||||
segments.push({
|
||||
type: 'execution',
|
||||
start: baseStart + task.duration / 3 + 20,
|
||||
width: task.duration / 3,
|
||||
color: colors[idx % colors.length]
|
||||
})
|
||||
} else if (mode === 'parallel') {
|
||||
// 多核并行:任务同时执行
|
||||
segments.push({
|
||||
type: 'execution',
|
||||
start: 5,
|
||||
width: task.duration,
|
||||
color: colors[idx % colors.length]
|
||||
})
|
||||
} else {
|
||||
// 混合模式
|
||||
if (idx < workerCount.value) {
|
||||
segments.push({
|
||||
type: 'execution',
|
||||
start: 5,
|
||||
width: task.duration,
|
||||
color: colors[idx % colors.length]
|
||||
})
|
||||
} else {
|
||||
const baseStart = 5 + (idx - workerCount.value) * 5
|
||||
segments.push({
|
||||
type: 'execution',
|
||||
start: baseStart,
|
||||
width: task.duration / 2,
|
||||
color: colors[idx % colors.length]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
task.segments = segments
|
||||
})
|
||||
}
|
||||
|
||||
function reset() {
|
||||
isRunning.value = false
|
||||
demoTasks.value.forEach(task => {
|
||||
task.segments = []
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.demo-container {
|
||||
padding: 20px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 0 0 16px 0;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.demo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.cpu-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.cpu-grid.single-core {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.cpu-core {
|
||||
background: #f5f7fa;
|
||||
border: 2px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.cpu-core.active {
|
||||
border-color: currentColor;
|
||||
box-shadow: 0 0 10px currentColor;
|
||||
}
|
||||
|
||||
.cpu-core.concurrent-mode.active {
|
||||
animation: blink 0.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.core-number {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.core-task {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.core-status {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.task-timeline {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.task-row {
|
||||
display: grid;
|
||||
grid-template-columns: 80px 1fr;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.task-info {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.task-name {
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.task-duration {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.task-track {
|
||||
position: relative;
|
||||
height: 20px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.task-segment {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 9px;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.task-segment.waiting {
|
||||
background: #dcdfe6 !important;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.comparison-table {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 2px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.comparison-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.comparison-item {
|
||||
background: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
font-size: 32px;
|
||||
text-align: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
text-align: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.item-desc {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
text-align: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.item-examples {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
background: white;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.need-table {
|
||||
background: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.need-title {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.need-items {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.need-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: white;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.need-check {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: #67c23a;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.need-check.need-multi {
|
||||
background: #409eff;
|
||||
}
|
||||
|
||||
.need-text {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.demo-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.comparison-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.need-items {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.cpu-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+417
@@ -0,0 +1,417 @@
|
||||
<template>
|
||||
<div class="demo-container">
|
||||
<h4>协程轻量级对比演示</h4>
|
||||
|
||||
<div class="controls">
|
||||
<el-radio-group v-model="comparisonMode" size="small">
|
||||
<el-radio-button label="memory">内存占用对比</el-radio-button>
|
||||
<el-radio-button label="switch">切换开销对比</el-radio-button>
|
||||
<el-radio-button label="creation">创建速度对比</el-radio-button>
|
||||
</el-radio-group>
|
||||
|
||||
<el-slider v-model="coroutineCount" :min="100" :max="10000" :step="100" show-input style="width: 300px;" />
|
||||
<span class="slider-label">{{ coroutineCount }} 个协程</span>
|
||||
</div>
|
||||
|
||||
<div class="comparison-view">
|
||||
<div class="comparison-column">
|
||||
<h5>线程模型</h5>
|
||||
<div class="resource-visualization">
|
||||
<div class="resource-bar">
|
||||
<div class="bar-label">内存占用</div>
|
||||
<div class="bar-container">
|
||||
<div class="bar-fill thread-bar" :style="{ width: threadMemoryPercent + '%', backgroundColor: '#e6a23c' }">
|
||||
{{ threadMemory }} MB
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="resource-bar">
|
||||
<div class="bar-label">创建时间</div>
|
||||
<div class="bar-container">
|
||||
<div class="bar-fill thread-bar" :style="{ width: threadCreationPercent + '%', backgroundColor: '#e6a23c' }">
|
||||
{{ threadCreationTime }} ms
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="resource-bar">
|
||||
<div class="bar-label">上下文切换</div>
|
||||
<div class="bar-container">
|
||||
<div class="bar-fill thread-bar" :style="{ width: 100 + '%', backgroundColor: '#e6a23c' }">
|
||||
~1-10 μs
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="thread-visualization">
|
||||
<div class="memory-blocks">
|
||||
<div v-for="n in Math.min(coroutineCount / 100, 50)" :key="n" class="thread-block"
|
||||
:style="{ backgroundColor: '#e6a23c', opacity: 0.6 + Math.random() * 0.4 }">
|
||||
</div>
|
||||
<div v-if="coroutineCount / 100 > 50" class="more-indicator">
|
||||
+{{ Math.floor(coroutineCount / 100 - 50) }} 更多...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vs-divider">
|
||||
<div class="vs-circle">VS</div>
|
||||
</div>
|
||||
|
||||
<div class="comparison-column">
|
||||
<h5>协程模型</h5>
|
||||
<div class="resource-visualization">
|
||||
<div class="resource-bar">
|
||||
<div class="bar-label">内存占用</div>
|
||||
<div class="bar-container">
|
||||
<div class="bar-fill coroutine-bar"
|
||||
:style="{ width: Math.max(coroutineMemoryPercent, 5) + '%', backgroundColor: '#67c23a' }">
|
||||
{{ coroutineMemory }} MB
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="resource-bar">
|
||||
<div class="bar-label">创建时间</div>
|
||||
<div class="bar-container">
|
||||
<div class="bar-fill coroutine-bar"
|
||||
:style="{ width: Math.max(coroutineCreationPercent, 5) + '%', backgroundColor: '#67c23a' }">
|
||||
{{ coroutineCreationTime }} ms
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="resource-bar">
|
||||
<div class="bar-label">上下文切换</div>
|
||||
<div class="bar-container">
|
||||
<div class="bar-fill coroutine-bar" :style="{ width: 15 + '%', backgroundColor: '#67c23a' }">
|
||||
~100 ns
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="coroutine-visualization">
|
||||
<div class="coroutine-grid">
|
||||
<div v-for="n in Math.min(coroutineCount / 10, 100)" :key="n" class="coroutine-cell"
|
||||
:style="{ backgroundColor: '#67c23a', opacity: 0.5 + Math.random() * 0.5 }">
|
||||
</div>
|
||||
<div v-if="coroutineCount / 10 > 100" class="more-indicator">
|
||||
+{{ Math.floor(coroutineCount / 10 - 100) }} 更多...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="efficiency-badge" v-if="coroutineCount >= 1000">
|
||||
<el-tag type="success" effect="dark" size="large">
|
||||
🚀 节省 {{ savingsPercent }}% 内存
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="insight-panel">
|
||||
<el-alert :title="insightTitle" :type="insightType" :description="insightDescription" show-icon
|
||||
:closable="false" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const comparisonMode = ref('memory')
|
||||
const coroutineCount = ref(1000)
|
||||
|
||||
// 基础参数
|
||||
const THREAD_STACK_SIZE = 1024 * 1024 // 1MB 线程栈
|
||||
const COROUTINE_STACK_SIZE = 2 * 1024 // 2KB 协程栈
|
||||
const THREAD_CREATION_TIME = 100 // 10us * 10000 = 100ms
|
||||
const COROUTINE_CREATION_TIME = 10 // 10ns * 10000 = 100us
|
||||
|
||||
// 计算值
|
||||
const threadMemory = computed(() => {
|
||||
return Math.round((coroutineCount.value * THREAD_STACK_SIZE) / (1024 * 1024))
|
||||
})
|
||||
|
||||
const coroutineMemory = computed(() => {
|
||||
return Math.round((coroutineCount.value * COROUTINE_STACK_SIZE) / (1024))
|
||||
})
|
||||
|
||||
const threadCreationTime = computed(() => {
|
||||
return Math.round((coroutineCount.value * THREAD_CREATION_TIME) / 1000)
|
||||
})
|
||||
|
||||
const coroutineCreationTime = computed(() => {
|
||||
return Math.round((coroutineCount.value * COROUTINE_CREATION_TIME) / 1000)
|
||||
})
|
||||
|
||||
// 百分比计算
|
||||
const threadMemoryPercent = computed(() => {
|
||||
const max = threadMemory.value
|
||||
return max > 0 ? 100 : 0
|
||||
})
|
||||
|
||||
const coroutineMemoryPercent = computed(() => {
|
||||
if (threadMemory.value === 0) return 0
|
||||
return (coroutineMemory.value / threadMemory.value) * 100
|
||||
})
|
||||
|
||||
const threadCreationPercent = computed(() => {
|
||||
const max = threadCreationTime.value
|
||||
return max > 0 ? 100 : 0
|
||||
})
|
||||
|
||||
const coroutineCreationPercent = computed(() => {
|
||||
if (threadCreationTime.value === 0) return 0
|
||||
return (coroutineCreationTime.value / threadCreationTime.value) * 100
|
||||
})
|
||||
|
||||
const savingsPercent = computed(() => {
|
||||
if (threadMemory.value === 0) return 0
|
||||
return Math.round((1 - coroutineMemory.value / threadMemory.value) * 100)
|
||||
})
|
||||
|
||||
// 洞察信息
|
||||
const insightTitle = computed(() => {
|
||||
if (coroutineCount.value < 100) {
|
||||
return '小规模场景'
|
||||
} else if (coroutineCount.value < 5000) {
|
||||
return '中等规模场景'
|
||||
} else {
|
||||
return '大规模高并发场景'
|
||||
}
|
||||
})
|
||||
|
||||
const insightType = computed(() => {
|
||||
if (coroutineCount.value >= 5000) return 'success'
|
||||
if (coroutineCount.value >= 1000) return 'warning'
|
||||
return 'info'
|
||||
})
|
||||
|
||||
const insightDescription = computed(() => {
|
||||
const savings = savingsPercent.value
|
||||
const memSaved = threadMemory.value - coroutineMemory.value
|
||||
|
||||
if (coroutineCount.value < 100) {
|
||||
return `当前 ${coroutineCount.value} 个并发单元,线程和协程的差别还不明显。建议增加到 1000+ 来观察显著差异。`
|
||||
} else if (coroutineCount.value < 5000) {
|
||||
return `使用协程可以节省 ${savings}% 的内存(约 ${memSaved}MB),创建速度快 ${Math.round(threadCreationTime.value / coroutineCreationTime.value)} 倍。`
|
||||
} else {
|
||||
return `🚀 在高并发场景下,协程优势巨大!节省 ${savings}% 内存(${memSaved}MB),${threadMemory.value}MB vs ${coroutineMemory.value}MB。这是 C10K/C10M 问题的关键解决方案。`
|
||||
}
|
||||
})
|
||||
|
||||
// 方法
|
||||
function addThread() {
|
||||
// 模拟添加线程
|
||||
}
|
||||
|
||||
function killThread() {
|
||||
// 模拟结束线程
|
||||
}
|
||||
|
||||
function simulateCrash() {
|
||||
// 模拟崩溃
|
||||
}
|
||||
|
||||
function reset() {
|
||||
coroutineCount.value = 1000
|
||||
}
|
||||
|
||||
function startSimulation() {
|
||||
// 开始模拟
|
||||
}
|
||||
|
||||
function toggleSimulation() {
|
||||
// 切换模拟状态
|
||||
}
|
||||
|
||||
function pauseSimulation() {
|
||||
// 暂停模拟
|
||||
}
|
||||
|
||||
function runSimulation() {
|
||||
// 运行模拟
|
||||
}
|
||||
|
||||
function stateText(state) {
|
||||
const texts = {
|
||||
ready: '就绪',
|
||||
running: '运行中',
|
||||
blocked: '阻塞',
|
||||
completed: '已完成'
|
||||
}
|
||||
return texts[state] || state
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.demo-container {
|
||||
padding: 20px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
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: 80px;
|
||||
}
|
||||
|
||||
.comparison-view {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.comparison-column {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.comparison-column h5 {
|
||||
margin: 0 0 16px 0;
|
||||
color: #303133;
|
||||
text-align: center;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.vs-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.vs-circle {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background: #409eff;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.resource-visualization {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.resource-bar {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.bar-label {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.bar-container {
|
||||
height: 24px;
|
||||
background: #e4e7ed;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bar-fill {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding-right: 8px;
|
||||
color: white;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.thread-visualization,
|
||||
.coroutine-visualization {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.memory-blocks {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px;
|
||||
padding: 8px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
min-height: 80px;
|
||||
align-content: flex-start;
|
||||
}
|
||||
|
||||
.thread-block {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.coroutine-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(10, 1fr);
|
||||
gap: 2px;
|
||||
padding: 8px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.coroutine-cell {
|
||||
aspect-ratio: 1;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.more-indicator {
|
||||
grid-column: 1 / -1;
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.efficiency-badge {
|
||||
text-align: center;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.insight-panel {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.comparison-view {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.vs-divider {
|
||||
order: -1;
|
||||
}
|
||||
|
||||
.vs-circle {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,505 @@
|
||||
<template>
|
||||
<div class="demo-container">
|
||||
<h4>事件循环 (Event Loop) 演示</h4>
|
||||
|
||||
<div class="controls">
|
||||
<el-button type="primary" size="small" @click="startSimulation" :disabled="isRunning">
|
||||
{{ isRunning ? '运行中...' : '开始模拟' }}
|
||||
</el-button>
|
||||
<el-button size="small" @click="addTask" :disabled="tasks.length >= 10">
|
||||
添加任务
|
||||
</el-button>
|
||||
<el-button size="small" @click="addMicrotask" :disabled="microtasks.length >= 5">
|
||||
添加微任务
|
||||
</el-button>
|
||||
<el-button size="small" @click="reset">重置</el-button>
|
||||
|
||||
<el-select v-model="simulationSpeed" size="small" style="width: 120px;">
|
||||
<el-option :value="1" label="慢速" />
|
||||
<el-option :value="2" label="正常" />
|
||||
<el-option :value="3" label="快速" />
|
||||
<el-option :value="4" label="极快" />
|
||||
<el-option :value="5" label="即时" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<div class="event-loop-container">
|
||||
<!-- 调用栈 -->
|
||||
<div class="section">
|
||||
<div class="section-title">调用栈 (Call Stack)</div>
|
||||
<div class="stack-container">
|
||||
<div v-for="(frame, idx) in callStack" :key="idx" class="stack-frame" :class="{ active: idx === 0 }"
|
||||
:style="{ animationDelay: idx * 0.1 + 's' }">
|
||||
<div class="frame-name">{{ frame.name }}</div>
|
||||
<div class="frame-line" v-if="frame.line">第 {{ frame.line }} 行</div>
|
||||
</div>
|
||||
<div v-if="callStack.length === 0" class="empty-stack">
|
||||
栈为空
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 事件循环 -->
|
||||
<div class="section event-loop">
|
||||
<div class="section-title">事件循环 (Event Loop)</div>
|
||||
<div class="loop-container">
|
||||
<div class="loop-arrow" :class="{ rotating: isRunning }">
|
||||
<svg viewBox="0 0 100 100" class="loop-svg">
|
||||
<circle cx="50" cy="50" r="40" fill="none" stroke="#409eff" stroke-width="4" stroke-dasharray="200" stroke-linecap="round"
|
||||
class="loop-circle" />
|
||||
<polygon points="85,50 75,45 75,55" fill="#409eff" class="loop-arrowhead" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="loop-label">检查</div>
|
||||
</div>
|
||||
|
||||
<div class="loop-description">
|
||||
<div class="step" :class="{ active: currentStep === 1 }">
|
||||
<span class="step-num">1</span>
|
||||
<span class="step-text">执行调用栈中的同步代码</span>
|
||||
</div>
|
||||
<div class="step" :class="{ active: currentStep === 2 }">
|
||||
<span class="step-num">2</span>
|
||||
<span class="step-text">执行所有微任务 (microtasks)</span>
|
||||
</div>
|
||||
<div class="step" :class="{ active: currentStep === 3 }">
|
||||
<span class="step-num">3</span>
|
||||
<span class="step-text">渲染 UI (如果需要)</span>
|
||||
</div>
|
||||
<div class="step" :class="{ active: currentStep === 4 }">
|
||||
<span class="step-num">4</span>
|
||||
<span class="step-text">执行宏任务 (macrotask)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 任务队列 -->
|
||||
<div class="section">
|
||||
<div class="section-title">任务队列</div>
|
||||
|
||||
<div class="queue microtask-queue">
|
||||
<div class="queue-title">微任务队列 (Microtasks)</div>
|
||||
<div class="queue-items">
|
||||
<div v-for="(task, idx) in microtasks" :key="task.id" class="queue-item microtask"
|
||||
:style="{ animationDelay: idx * 0.1 + 's' }">
|
||||
<span class="task-name">{{ task.name }}</span>
|
||||
<span class="task-priority">高优先级</span>
|
||||
</div>
|
||||
<div v-if="microtasks.length === 0" class="empty-queue">
|
||||
队列为空
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="queue macrotask-queue">
|
||||
<div class="queue-title">宏任务队列 (Macrotasks)</div>
|
||||
<div class="queue-items">
|
||||
<div v-for="(task, idx) in tasks" :key="task.id" class="queue-item macrotask"
|
||||
:style="{ animationDelay: idx * 0.15 + 's' }">
|
||||
<span class="task-name">{{ task.name }}</span>
|
||||
<span class="task-type">{{ task.type }}</span>
|
||||
</div>
|
||||
<div v-if="tasks.length === 0" class="empty-queue">
|
||||
队列为空
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const isRunning = ref(false)
|
||||
const simulationSpeed = ref(2)
|
||||
const currentStep = ref(1)
|
||||
const callStack = ref([])
|
||||
const microtasks = ref([])
|
||||
const tasks = ref([])
|
||||
let taskIdCounter = 1
|
||||
let microtaskIdCounter = 1
|
||||
|
||||
function addTask() {
|
||||
if (tasks.value.length >= 10) return
|
||||
const types = ['setTimeout', 'setInterval', 'I/O', 'DOM事件']
|
||||
tasks.value.push({
|
||||
id: taskIdCounter++,
|
||||
name: `任务 ${taskIdCounter - 1}`,
|
||||
type: types[Math.floor(Math.random() * types.length)]
|
||||
})
|
||||
}
|
||||
|
||||
function addMicrotask() {
|
||||
if (microtasks.value.length >= 5) return
|
||||
microtasks.value.push({
|
||||
id: microtaskIdCounter++,
|
||||
name: `微任务 ${microtaskIdCounter - 1}`
|
||||
})
|
||||
}
|
||||
|
||||
function startSimulation() {
|
||||
if (isRunning.value) return
|
||||
|
||||
// 确保有任务
|
||||
if (tasks.value.length === 0) {
|
||||
addTask()
|
||||
addTask()
|
||||
}
|
||||
if (microtasks.value.length === 0) {
|
||||
addMicrotask()
|
||||
}
|
||||
|
||||
isRunning.value = true
|
||||
currentStep.value = 1
|
||||
|
||||
// 模拟执行过程
|
||||
runSimulationStep()
|
||||
}
|
||||
|
||||
function runSimulationStep() {
|
||||
if (!isRunning.value) return
|
||||
|
||||
// 模拟步骤执行
|
||||
const speed = 6 - simulationSpeed.value // 转换为延迟
|
||||
const delay = speed * 200
|
||||
|
||||
setTimeout(() => {
|
||||
if (!isRunning.value) return
|
||||
|
||||
currentStep.value = (currentStep.value % 4) + 1
|
||||
|
||||
// 更新调用栈
|
||||
updateCallStack()
|
||||
|
||||
// 消耗任务
|
||||
if (currentStep.value === 2 && microtasks.value.length > 0) {
|
||||
microtasks.value.shift()
|
||||
} else if (currentStep.value === 4 && tasks.value.length > 0) {
|
||||
tasks.value.shift()
|
||||
}
|
||||
|
||||
// 检查是否完成
|
||||
if (tasks.value.length === 0 && microtasks.value.length === 0) {
|
||||
isRunning.value = false
|
||||
callStack.value = []
|
||||
} else {
|
||||
runSimulationStep()
|
||||
}
|
||||
}, delay)
|
||||
}
|
||||
|
||||
function updateCallStack() {
|
||||
const stack = []
|
||||
|
||||
switch (currentStep.value) {
|
||||
case 1:
|
||||
stack.push({ name: 'main()', line: 1 })
|
||||
stack.push({ name: 'executeSync()', line: 15 })
|
||||
break
|
||||
case 2:
|
||||
stack.push({ name: 'main()', line: 1 })
|
||||
stack.push({ name: 'processMicrotask()', line: 25 })
|
||||
break
|
||||
case 3:
|
||||
stack.push({ name: 'main()', line: 1 })
|
||||
stack.push({ name: 'render()', line: 35 })
|
||||
break
|
||||
case 4:
|
||||
stack.push({ name: 'main()', line: 1 })
|
||||
stack.push({ name: 'processMacrotask()', line: 45 })
|
||||
break
|
||||
}
|
||||
|
||||
callStack.value = stack
|
||||
}
|
||||
|
||||
function reset() {
|
||||
isRunning.value = false
|
||||
currentStep.value = 1
|
||||
callStack.value = []
|
||||
microtasks.value = []
|
||||
tasks.value = []
|
||||
taskIdCounter = 1
|
||||
microtaskIdCounter = 1
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.demo-container {
|
||||
padding: 20px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 0 0 16px 0;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.event-loop-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.2fr 1fr;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 12px;
|
||||
font-size: 14px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.stack-container {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.stack-frame {
|
||||
background: #f5f7fa;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.stack-frame.active {
|
||||
background: #ecf5ff;
|
||||
border-color: #409eff;
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.frame-name {
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.frame-line {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.empty-stack {
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
padding: 40px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.event-loop {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.loop-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.loop-arrow {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.loop-arrow.rotating .loop-svg {
|
||||
animation: rotate 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.loop-circle {
|
||||
animation: dash 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes dash {
|
||||
0% {
|
||||
stroke-dashoffset: 200;
|
||||
}
|
||||
100% {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.loop-label {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.loop-description {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.step {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
margin-bottom: 4px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.step.active {
|
||||
background: #ecf5ff;
|
||||
border-left: 3px solid #409eff;
|
||||
}
|
||||
|
||||
.step-num {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: #dcdfe6;
|
||||
color: #606266;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.step.active .step-num {
|
||||
background: #409eff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.step-text {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.step.active .step-text {
|
||||
color: #303133;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.queue {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.queue-title {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #606266;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.queue-title::before {
|
||||
content: '';
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.microtask-queue .queue-title::before {
|
||||
background: #f56c6c;
|
||||
}
|
||||
|
||||
.macrotask-queue .queue-title::before {
|
||||
background: #e6a23c;
|
||||
}
|
||||
|
||||
.queue-items {
|
||||
min-height: 60px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.queue-item {
|
||||
background: white;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
animation: slideIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.queue-item.microtask {
|
||||
border-left: 3px solid #f56c6c;
|
||||
}
|
||||
|
||||
.queue-item.macrotask {
|
||||
border-left: 3px solid #e6a23c;
|
||||
}
|
||||
|
||||
.task-name {
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.task-type {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.empty-queue {
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
padding: 20px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.event-loop-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+439
@@ -0,0 +1,439 @@
|
||||
<template>
|
||||
<div class="demo-container">
|
||||
<h4>Go 协程 (Goroutine) 与 GMP 调度演示</h4>
|
||||
|
||||
<div class="controls">
|
||||
<el-radio-group v-model="viewMode" size="small">
|
||||
<el-radio-button label="overview">整体视图</el-radio-button>
|
||||
<el-radio-button label="gmp">GMP 调度</el-radio-button>
|
||||
<el-radio-button label="channel">Channel 通信</el-radio-button>
|
||||
</el-radio-group>
|
||||
|
||||
<el-button type="primary" size="small" @click="startDemo" :disabled="isRunning">
|
||||
{{ isRunning ? '运行中...' : '开始演示' }}
|
||||
</el-button>
|
||||
|
||||
<el-button size="small" @click="addGoroutine" :disabled="goroutines.length >= 20">
|
||||
+Goroutine
|
||||
</el-button>
|
||||
|
||||
<el-button size="small" @click="reset">重置</el-button>
|
||||
</div>
|
||||
|
||||
<!-- GMP 调度视图 -->
|
||||
<div v-if="viewMode === 'gmp' || viewMode === 'overview'" class="gmp-view">
|
||||
<div class="gmp-container">
|
||||
<!-- Global Queue -->
|
||||
<div class="queue-section global-queue">
|
||||
<div class="queue-header">
|
||||
<span class="queue-name">Global Queue (G)</span>
|
||||
<el-tag size="small" type="info">{{ globalQueue.length }}</el-tag>
|
||||
</div>
|
||||
<div class="queue-content">
|
||||
<div v-for="g in globalQueue" :key="g.id" class="g-item"
|
||||
:style="{ backgroundColor: g.color }">
|
||||
G{{ g.id }}
|
||||
</div>
|
||||
<div v-if="globalQueue.length === 0" class="empty-queue">空</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- P (Processors) -->
|
||||
<div class="processors-section">
|
||||
<div class="section-header">
|
||||
<span class="section-name">P (Processors) - {{ processors.length }} 个</span>
|
||||
</div>
|
||||
|
||||
<div class="processors-grid">
|
||||
<div v-for="(p, idx) in processors" :key="idx" class="processor"
|
||||
:class="{ 'active': p.active }"
|
||||
:style="{ borderColor: p.active ? p.color : '#e4e7ed' }">
|
||||
<div class="processor-header">
|
||||
<span class="processor-name">P{{ idx }}</span>
|
||||
<span class="processor-status" :class="{ 'running': p.active }">{{ p.active ? '运行中' : '空闲' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="local-queue">
|
||||
<div class="queue-label">本地队列</div>
|
||||
<div class="local-g-list">
|
||||
<div v-for="g in p.localQueue" :key="g.id" class="local-g-item"
|
||||
:style="{ backgroundColor: g.color }">
|
||||
G{{ g.id }}
|
||||
</div>
|
||||
<div v-if="p.localQueue.length === 0" class="empty-local">-</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="m-binding" v-if="p.m">
|
||||
<span class="m-label">绑定 M{{ p.m.id }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- M (Machine Threads) -->
|
||||
<div class="machines-section">
|
||||
<div class="section-header">
|
||||
<span class="section-name">M (Machine Threads) - {{ machines.length }} 个</span>
|
||||
</div>
|
||||
|
||||
<div class="machines-list">
|
||||
<div v-for="m in machines" :key="m.id" class="machine-item"
|
||||
:class="{ 'active': m.active }"
|
||||
:style="{ borderColor: m.active ? '#67c23a' : '#e4e7ed' }">
|
||||
<span class="machine-id">M{{ m.id }}</span>
|
||||
<span class="machine-status">{{ m.active ? '运行中' : '休眠' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="explanation">
|
||||
<el-alert title="GMP 调度模型" type="success"
|
||||
:description="gmpDescription"
|
||||
show-icon :closable="false" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
|
||||
const gmpDescription = 'G (Goroutine): 待执行的任务。M (Machine): 操作系统线程,执行 G 的载体。P (Processor): 逻辑处理器,提供执行上下文。G 先放入 P 的本地队列,P 与 M 绑定后,M 从 P 获取 G 执行。当本地队列空时,会从全局队列或其他 P 偷任务。'
|
||||
|
||||
const viewMode = ref('gmp')
|
||||
const isRunning = ref(false)
|
||||
const goroutines = ref([])
|
||||
|
||||
// GMP 数据结构
|
||||
const globalQueue = ref([])
|
||||
const processors = ref([])
|
||||
const machines = ref([])
|
||||
|
||||
// 初始化数据
|
||||
function initGMP() {
|
||||
// 创建一些 Goroutines
|
||||
const colors = ['#409eff', '#67c23a', '#e6a23c', '#f56c6c', '#909399', '#b3d8ff', '#c2e7b0', '#f5dab1']
|
||||
const goroutinesData = []
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
goroutinesData.push({
|
||||
id: i + 1,
|
||||
color: colors[i % colors.length],
|
||||
status: 'waiting'
|
||||
})
|
||||
}
|
||||
|
||||
goroutines.value = goroutinesData
|
||||
|
||||
// 分配全局队列
|
||||
globalQueue.value = goroutinesData.slice(0, 3)
|
||||
|
||||
// 初始化 Processors (P)
|
||||
processors.value = [
|
||||
{ id: 0, active: true, color: '#409eff', localQueue: goroutinesData.slice(3, 6), m: { id: 0 } },
|
||||
{ id: 1, active: false, color: '#67c23a', localQueue: goroutinesData.slice(6, 9), m: null },
|
||||
{ id: 2, active: false, color: '#e6a23c', localQueue: goroutinesData.slice(9, 12), m: null },
|
||||
{ id: 3, active: false, color: '#f56c6c', localQueue: [], m: null }
|
||||
]
|
||||
|
||||
// 初始化 Machines (M)
|
||||
machines.value = [
|
||||
{ id: 0, active: true },
|
||||
{ id: 1, active: false },
|
||||
{ id: 2, active: false },
|
||||
{ id: 3, active: false }
|
||||
]
|
||||
}
|
||||
|
||||
// 添加 Goroutine
|
||||
function addGoroutine() {
|
||||
if (goroutines.value.length >= 20) return
|
||||
|
||||
const colors = ['#409eff', '#67c23a', '#e6a23c', '#f56c6c', '#909399', '#b3d8ff']
|
||||
const id = goroutines.value.length + 1
|
||||
|
||||
const newG = {
|
||||
id,
|
||||
color: colors[id % colors.length],
|
||||
status: 'waiting'
|
||||
}
|
||||
|
||||
goroutines.value.push(newG)
|
||||
|
||||
// 添加到第一个有空位的 P
|
||||
for (const p of processors.value) {
|
||||
if (p.localQueue.length < 5) {
|
||||
p.localQueue.push(newG)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 开始演示
|
||||
function startDemo() {
|
||||
isRunning.value = true
|
||||
|
||||
// 模拟调度过程
|
||||
let step = 0
|
||||
const interval = setInterval(() => {
|
||||
step++
|
||||
|
||||
// 轮流激活 P
|
||||
processors.value.forEach((p, idx) => {
|
||||
p.active = (idx === step % processors.value.length)
|
||||
})
|
||||
|
||||
// 对应的 M 也激活
|
||||
machines.value.forEach((m, idx) => {
|
||||
m.active = processors.value[idx]?.active || false
|
||||
})
|
||||
|
||||
if (step >= 20) {
|
||||
clearInterval(interval)
|
||||
isRunning.value = false
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// 重置
|
||||
function reset() {
|
||||
isRunning.value = false
|
||||
initGMP()
|
||||
}
|
||||
|
||||
// 监听视图模式变化
|
||||
watch(viewMode, () => {
|
||||
if (viewMode.value === 'gmp') {
|
||||
initGMP()
|
||||
}
|
||||
})
|
||||
|
||||
// 初始化
|
||||
initGMP()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.demo-container {
|
||||
padding: 20px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 0 0 16px 0;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.gmp-view {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.gmp-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.queue-section {
|
||||
background: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.queue-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.queue-name {
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.queue-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
min-height: 40px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.g-item {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.empty-queue {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.processors-section,
|
||||
.machines-section {
|
||||
background: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.section-name {
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.processors-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.processor {
|
||||
background: white;
|
||||
border: 2px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.processor.active {
|
||||
box-shadow: 0 0 10px currentColor;
|
||||
}
|
||||
|
||||
.processor-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.processor-name {
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.processor-status {
|
||||
font-size: 10px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
background: #dcdfe6;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.processor-status.running {
|
||||
background: #67c23a;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.local-queue {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.queue-label {
|
||||
font-size: 10px;
|
||||
color: #909399;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.local-g-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.local-g-item {
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
color: white;
|
||||
font-size: 9px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.empty-local {
|
||||
font-size: 10px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.m-binding {
|
||||
font-size: 10px;
|
||||
color: #409eff;
|
||||
background: #ecf5ff;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.m-label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.machines-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.machine-item {
|
||||
background: white;
|
||||
border: 2px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.machine-item.active {
|
||||
border-color: #67c23a;
|
||||
background: #f0f9eb;
|
||||
}
|
||||
|
||||
.machine-id {
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.machine-status {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.machine-item.active .machine-status {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.explanation {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.processors-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+324
@@ -0,0 +1,324 @@
|
||||
<template>
|
||||
<div class="demo-container">
|
||||
<h4>进程内存隔离演示</h4>
|
||||
|
||||
<div class="controls">
|
||||
<el-button type="primary" size="small" @click="addProcess" :disabled="processes.length >= 4">
|
||||
创建进程
|
||||
</el-button>
|
||||
<el-button type="danger" size="small" @click="killProcess" :disabled="processes.length === 0">
|
||||
结束进程
|
||||
</el-button>
|
||||
<el-button size="small" @click="simulateCrash">
|
||||
模拟进程崩溃
|
||||
</el-button>
|
||||
<el-button size="small" @click="reset">
|
||||
重置
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="memory-view">
|
||||
<div class="memory-label">系统内存</div>
|
||||
<div class="memory-blocks">
|
||||
<div
|
||||
v-for="process in processes"
|
||||
:key="process.id"
|
||||
class="process-block"
|
||||
:class="{ crashed: process.crashed, active: process.active }"
|
||||
:style="{ width: process.size + '%', backgroundColor: process.color }"
|
||||
>
|
||||
<div class="process-header">
|
||||
<span class="process-name">进程 {{ process.id }}</span>
|
||||
<span class="process-pid">PID: {{ process.pid }}</span>
|
||||
</div>
|
||||
<div class="process-memory">
|
||||
<div class="memory-section code">
|
||||
<span class="section-label">代码段</span>
|
||||
<span class="section-size">{{ process.codeSize }}MB</span>
|
||||
</div>
|
||||
<div class="memory-section data">
|
||||
<span class="section-label">数据段</span>
|
||||
<span class="section-size">{{ process.dataSize }}MB</span>
|
||||
</div>
|
||||
<div class="memory-section heap">
|
||||
<span class="section-label">堆</span>
|
||||
<span class="section-size">{{ process.heapSize }}MB</span>
|
||||
</div>
|
||||
<div class="memory-section stack">
|
||||
<span class="section-label">栈</span>
|
||||
<span class="section-size">{{ process.stackSize }}MB</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="process.crashed" class="crash-overlay">
|
||||
<span class="crash-text">💥 已崩溃</span>
|
||||
<span class="crash-info">不影响其他进程</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="shared-memory" v-if="showSharedMemory">
|
||||
<div class="shared-label">共享内存区域 (IPC)</div>
|
||||
<div class="shared-content">
|
||||
<div v-for="process in processes" :key="process.id" class="shared-access">
|
||||
<span class="access-indicator" :style="{ backgroundColor: process.color }"></span>
|
||||
<span>进程 {{ process.id }} 可以访问</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-panel">
|
||||
<el-alert
|
||||
:title="currentInfo.title"
|
||||
:type="currentInfo.type"
|
||||
:description="currentInfo.description"
|
||||
show-icon
|
||||
:closable="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const processes = ref([])
|
||||
const showSharedMemory = ref(false)
|
||||
const colors = ['#409eff', '#67c23a', '#e6a23c', '#f56c6c']
|
||||
let pidCounter = 1000
|
||||
|
||||
const currentInfo = computed(() => {
|
||||
if (processes.value.length === 0) {
|
||||
return {
|
||||
title: '进程隔离',
|
||||
type: 'info',
|
||||
description: '每个进程拥有独立的虚拟地址空间,一个进程崩溃不会影响其他进程。点击"创建进程"开始演示。'
|
||||
}
|
||||
}
|
||||
|
||||
const crashed = processes.value.filter(p => p.crashed).length
|
||||
if (crashed > 0) {
|
||||
return {
|
||||
title: '隔离性验证',
|
||||
type: 'success',
|
||||
description: `进程已崩溃但其他进程正常运行,证明进程间内存隔离有效。崩溃的进程会被操作系统回收资源。`
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
title: '内存布局',
|
||||
type: 'info',
|
||||
description: `当前有 ${processes.value.length} 个进程在运行。每个进程的内存分为代码段、数据段、堆和栈,相互隔离不可访问。`
|
||||
}
|
||||
})
|
||||
|
||||
function createProcess() {
|
||||
if (processes.value.length >= 4) return
|
||||
|
||||
const id = processes.value.length + 1
|
||||
const size = 20 + Math.random() * 10
|
||||
|
||||
processes.value.push({
|
||||
id,
|
||||
pid: pidCounter++,
|
||||
size,
|
||||
color: colors[id - 1],
|
||||
codeSize: Math.floor(size * 0.15),
|
||||
dataSize: Math.floor(size * 0.1),
|
||||
heapSize: Math.floor(size * 0.6),
|
||||
stackSize: Math.floor(size * 0.15),
|
||||
crashed: false,
|
||||
active: true
|
||||
})
|
||||
}
|
||||
|
||||
function killProcess() {
|
||||
if (processes.value.length === 0) return
|
||||
processes.value.pop()
|
||||
}
|
||||
|
||||
function simulateCrash() {
|
||||
if (processes.value.length === 0) return
|
||||
|
||||
// 随机让一个未崩溃的进程崩溃
|
||||
const candidates = processes.value.filter(p => !p.crashed)
|
||||
if (candidates.length > 0) {
|
||||
const victim = candidates[Math.floor(Math.random() * candidates.length)]
|
||||
victim.crashed = true
|
||||
victim.active = false
|
||||
}
|
||||
}
|
||||
|
||||
function reset() {
|
||||
processes.value = []
|
||||
showSharedMemory.value = false
|
||||
pidCounter = 1000
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.demo-container {
|
||||
padding: 20px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 0 0 16px 0;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.memory-view {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.memory-label {
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.memory-blocks {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.process-block {
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
color: white;
|
||||
transition: all 0.3s;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.process-block.crashed {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.process-block.active {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.process-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.process-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.process-pid {
|
||||
opacity: 0.8;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.process-memory {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.memory-section {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
opacity: 0.7;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.section-size {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.crash-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.crash-text {
|
||||
font-size: 24px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.crash-info {
|
||||
font-size: 12px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.shared-memory {
|
||||
margin-top: 16px;
|
||||
padding: 12px;
|
||||
background: #f4f4f5;
|
||||
border-radius: 6px;
|
||||
border: 2px dashed #c0c4cc;
|
||||
}
|
||||
|
||||
.shared-label {
|
||||
font-weight: bold;
|
||||
color: #606266;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.shared-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.shared-access {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.access-indicator {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.info-panel {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.process-memory {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.cpu-cores {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+308
@@ -0,0 +1,308 @@
|
||||
<template>
|
||||
<div class="demo-container">
|
||||
<h4>进程 / 线程 / 协程 对比演示</h4>
|
||||
|
||||
<div class="controls">
|
||||
<el-radio-group v-model="model" size="small">
|
||||
<el-radio-button label="process">多进程</el-radio-button>
|
||||
<el-radio-button label="thread">多线程</el-radio-button>
|
||||
<el-radio-button label="coroutine">协程</el-radio-button>
|
||||
</el-radio-group>
|
||||
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="startSimulation"
|
||||
:disabled="isRunning"
|
||||
>
|
||||
{{ isRunning ? '运行中...' : '开始模拟' }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="stats-bar">
|
||||
<el-statistic title="内存占用" :value="memoryUsage" suffix="MB" />
|
||||
<el-statistic title="上下文切换" :value="contextSwitches" />
|
||||
<el-statistic title="完成任务" :value="completedTasks" />
|
||||
<el-statistic title="耗时" :value="elapsedTime" suffix="ms" />
|
||||
</div>
|
||||
|
||||
<div class="visualization">
|
||||
<div class="cpu-cores">
|
||||
<div
|
||||
v-for="(core, idx) in cpuCores"
|
||||
:key="idx"
|
||||
class="core"
|
||||
:class="{ active: core.active, type: core.type }"
|
||||
>
|
||||
<span class="core-label">CPU {{ idx + 1 }}</span>
|
||||
<div class="task-indicator" v-if="core.task">
|
||||
{{ core.task }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="task-queue">
|
||||
<h5>任务队列</h5>
|
||||
<div class="queue-items">
|
||||
<div
|
||||
v-for="(task, idx) in pendingTasks"
|
||||
:key="task.id"
|
||||
class="queue-item"
|
||||
:style="{ animationDelay: `${idx * 0.1}s` }"
|
||||
>
|
||||
Task {{ task.id }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="explanation">
|
||||
<el-alert
|
||||
:title="explanationTitle"
|
||||
:type="explanationType"
|
||||
:description="explanationText"
|
||||
show-icon
|
||||
:closable="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
|
||||
const model = ref('process')
|
||||
const isRunning = ref(false)
|
||||
const memoryUsage = ref(0)
|
||||
const contextSwitches = ref(0)
|
||||
const completedTasks = ref(0)
|
||||
const elapsedTime = ref(0)
|
||||
|
||||
const cpuCores = ref([
|
||||
{ active: false, type: 'process', task: null },
|
||||
{ active: false, type: 'process', task: null },
|
||||
{ active: false, type: 'process', task: null },
|
||||
{ active: false, type: 'process', task: null },
|
||||
])
|
||||
|
||||
const pendingTasks = ref([])
|
||||
|
||||
const explanationTitle = computed(() => {
|
||||
const titles = {
|
||||
process: '多进程模型',
|
||||
thread: '多线程模型',
|
||||
coroutine: '协程模型'
|
||||
}
|
||||
return titles[model.value]
|
||||
})
|
||||
|
||||
const explanationType = computed(() => {
|
||||
const types = {
|
||||
process: 'success',
|
||||
thread: 'warning',
|
||||
coroutine: 'info'
|
||||
}
|
||||
return types[model.value]
|
||||
})
|
||||
|
||||
const explanationText = computed(() => {
|
||||
const texts = {
|
||||
process: '每个进程拥有独立的内存空间,隔离性强但开销大。进程间通信需要 IPC 机制。适合需要强隔离的场景,如浏览器标签页、沙箱程序。',
|
||||
thread: '线程共享进程内存,切换开销较小,但需要同步机制保护共享数据。适合 CPU 密集型任务和需要共享数据的场景。',
|
||||
coroutine: '用户态轻量级线程,由运行时调度,切换极快。适合 I/O 密集型高并发场景,如 Web 服务器、网关、长连接服务。'
|
||||
}
|
||||
return texts[model.value]
|
||||
})
|
||||
|
||||
watch(model, () => {
|
||||
// 重置状态
|
||||
resetSimulation()
|
||||
})
|
||||
|
||||
function resetSimulation() {
|
||||
isRunning.value = false
|
||||
memoryUsage.value = model.value === 'process' ? 400 : model.value === 'thread' ? 100 : 20
|
||||
contextSwitches.value = 0
|
||||
completedTasks.value = 0
|
||||
elapsedTime.value = 0
|
||||
cpuCores.value.forEach(core => {
|
||||
core.active = false
|
||||
core.type = model.value
|
||||
core.task = null
|
||||
})
|
||||
pendingTasks.value = Array.from({ length: 16 }, (_, i) => ({ id: i + 1 }))
|
||||
}
|
||||
|
||||
async function startSimulation() {
|
||||
if (isRunning.value) return
|
||||
isRunning.value = true
|
||||
|
||||
const startTime = Date.now()
|
||||
const taskCount = pendingTasks.value.length
|
||||
const baseSwitchCost = model.value === 'process' ? 10 : model.value === 'thread' ? 2 : 1
|
||||
|
||||
// 模拟任务处理
|
||||
while (pendingTasks.value.length > 0 && isRunning.value) {
|
||||
// 分配任务到 CPU 核心
|
||||
for (let i = 0; i < cpuCores.value.length; i++) {
|
||||
if (!cpuCores.value[i].active && pendingTasks.value.length > 0) {
|
||||
const task = pendingTasks.value.shift()
|
||||
cpuCores.value[i].active = true
|
||||
cpuCores.value[i].task = task.id
|
||||
|
||||
// 模拟任务执行时间
|
||||
setTimeout(() => {
|
||||
if (isRunning.value) {
|
||||
cpuCores.value[i].active = false
|
||||
cpuCores.value[i].task = null
|
||||
completedTasks.value++
|
||||
contextSwitches.value += baseSwitchCost
|
||||
}
|
||||
}, 300 + Math.random() * 200)
|
||||
}
|
||||
}
|
||||
|
||||
elapsedTime.value = Date.now() - startTime
|
||||
await new Promise(resolve => setTimeout(resolve, 50))
|
||||
}
|
||||
|
||||
isRunning.value = false
|
||||
}
|
||||
|
||||
// 初始化
|
||||
resetSimulation()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.demo-container {
|
||||
padding: 20px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.stats-bar {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.visualization {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.cpu-cores {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.core {
|
||||
background: white;
|
||||
border: 2px solid #dcdfe6;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.core.active {
|
||||
border-color: #409eff;
|
||||
background: #ecf5ff;
|
||||
}
|
||||
|
||||
.core.active.process {
|
||||
border-color: #67c23a;
|
||||
background: #f0f9eb;
|
||||
}
|
||||
|
||||
.core.active.thread {
|
||||
border-color: #e6a23c;
|
||||
background: #fdf6ec;
|
||||
}
|
||||
|
||||
.core.active.coroutine {
|
||||
border-color: #909399;
|
||||
background: #f4f4f5;
|
||||
}
|
||||
|
||||
.core-label {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.task-indicator {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.task-queue {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.task-queue h5 {
|
||||
margin: 0 0 12px 0;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.queue-items {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.queue-item {
|
||||
background: #409eff;
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.explanation {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.stats-bar {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.visualization {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.cpu-cores {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+518
@@ -0,0 +1,518 @@
|
||||
<template>
|
||||
<div class="demo-container">
|
||||
<h4>线程调度演示</h4>
|
||||
|
||||
<div class="controls">
|
||||
<el-radio-group v-model="schedulingPolicy" size="small">
|
||||
<el-radio-button label="fifo">FIFO (先来先服务)</el-radio-button>
|
||||
<el-radio-button label="roundrobin">时间片轮转</el-radio-button>
|
||||
<el-radio-button label="priority">优先级调度</el-radio-button>
|
||||
</el-radio-group>
|
||||
|
||||
<el-button type="primary" size="small" @click="addThread" :disabled="threads.length >= 6">
|
||||
添加线程
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
@click="toggleSimulation"
|
||||
>
|
||||
{{ isRunning ? '暂停' : '开始调度' }}
|
||||
</el-button>
|
||||
|
||||
<el-button size="small" @click="reset">
|
||||
重置
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="timeline-container">
|
||||
<div class="timeline-header">
|
||||
<span class="timeline-label">时间轴</span>
|
||||
<div class="time-marker">0ms</div>
|
||||
<div class="time-marker">100ms</div>
|
||||
<div class="time-marker">200ms</div>
|
||||
<div class="time-marker">300ms</div>
|
||||
<div class="time-marker">400ms</div>
|
||||
<div class="time-marker">500ms</div>
|
||||
</div>
|
||||
|
||||
<div class="thread-rows">
|
||||
<div
|
||||
v-for="thread in threads"
|
||||
:key="thread.id"
|
||||
class="thread-row"
|
||||
>
|
||||
<div class="thread-info">
|
||||
<div class="thread-name" :style="{ color: thread.color }">
|
||||
{{ thread.name }}
|
||||
</div>
|
||||
<div class="thread-details">
|
||||
<el-tag size="small" :type="thread.state === 'running' ? 'success' : thread.state === 'ready' ? 'warning' : 'info'">
|
||||
{{ stateText(thread.state) }}
|
||||
</el-tag>
|
||||
<span class="priority">优先级: {{ thread.priority }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="execution-track">
|
||||
<div
|
||||
v-for="(slot, idx) in thread.executionSlots"
|
||||
:key="idx"
|
||||
class="execution-slot"
|
||||
:class="{ running: slot.state === 'running', blocked: slot.state === 'blocked' }"
|
||||
:style="{ left: slot.start + '%', width: slot.width + '%', backgroundColor: slot.state === 'running' ? thread.color : '#dcdfe6' }"
|
||||
>
|
||||
<span v-if="slot.state === 'running'" class="slot-label">运行</span>
|
||||
<span v-else class="slot-label">等待</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="thread.state === 'running'"
|
||||
class="current-indicator"
|
||||
:style="{ left: currentTime + '%', backgroundColor: thread.color }"
|
||||
>
|
||||
<div class="indicator-arrow"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-panel">
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ completedThreads }}</div>
|
||||
<div class="stat-label">已完成线程</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ contextSwitches }}</div>
|
||||
<div class="stat-label">上下文切换</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ avgWaitTime }}ms</div>
|
||||
<div class="stat-label">平均等待时间</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ throughput }}</div>
|
||||
<div class="stat-label">吞吐量 (线程/秒)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="algorithm-info">
|
||||
<h5>当前调度算法: {{ algorithmName }}</h5>
|
||||
<p>{{ algorithmDescription }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
const schedulingPolicy = ref('roundrobin')
|
||||
const threads = ref([])
|
||||
const isRunning = ref(false)
|
||||
const currentTime = ref(0)
|
||||
const completedThreads = ref(0)
|
||||
const contextSwitches = ref(0)
|
||||
const totalWaitTime = ref(0)
|
||||
const startTime = ref(null)
|
||||
|
||||
let animationId = null
|
||||
let currentThreadIndex = 0
|
||||
let timeQuantum = 50 // 时间片长度
|
||||
|
||||
const colors = ['#409eff', '#67c23a', '#e6a23c', '#f56c6c', '#909399', '#b3d8ff']
|
||||
|
||||
const algorithmName = computed(() => {
|
||||
const names = {
|
||||
fifo: 'FIFO (First In First Out)',
|
||||
roundrobin: 'Round Robin (时间片轮转)',
|
||||
priority: 'Priority Scheduling (优先级调度)'
|
||||
}
|
||||
return names[schedulingPolicy.value]
|
||||
})
|
||||
|
||||
const algorithmDescription = computed(() => {
|
||||
const descriptions = {
|
||||
fifo: '按照线程到达的先后顺序执行,直到当前线程完成才执行下一个。简单公平但可能导致短任务等待长任务。',
|
||||
roundrobin: '每个线程轮流执行一个时间片,时间片用完就切换到下一个线程。响应性好,适合交互式系统。',
|
||||
priority: '根据线程优先级决定执行顺序,高优先级线程优先执行。需要处理优先级反转和饥饿问题。'
|
||||
}
|
||||
return descriptions[schedulingPolicy.value]
|
||||
})
|
||||
|
||||
const avgWaitTime = computed(() => {
|
||||
if (completedThreads.value === 0) return 0
|
||||
return Math.round(totalWaitTime.value / completedThreads.value)
|
||||
})
|
||||
|
||||
const throughput = computed(() => {
|
||||
if (!startTime.value) return 0
|
||||
const elapsed = (Date.now() - startTime.value) / 1000
|
||||
if (elapsed === 0) return 0
|
||||
return (completedThreads.value / elapsed).toFixed(2)
|
||||
})
|
||||
|
||||
const stateText = (state) => {
|
||||
const map = {
|
||||
running: '运行中',
|
||||
ready: '就绪',
|
||||
blocked: '阻塞',
|
||||
completed: '完成'
|
||||
}
|
||||
return map[state] || state
|
||||
}
|
||||
|
||||
function addThread() {
|
||||
if (threads.value.length >= 6) return
|
||||
|
||||
const id = threads.value.length + 1
|
||||
const priority = Math.floor(Math.random() * 10) + 1
|
||||
const workAmount = 30 + Math.floor(Math.random() * 50) // 30-80% 的工作量
|
||||
|
||||
threads.value.push({
|
||||
id,
|
||||
name: `Thread-${id}`,
|
||||
color: colors[id - 1],
|
||||
priority,
|
||||
state: 'ready',
|
||||
progress: 0,
|
||||
workAmount,
|
||||
executionSlots: [],
|
||||
startTime: null,
|
||||
endTime: null
|
||||
})
|
||||
}
|
||||
|
||||
function reset() {
|
||||
threads.value = []
|
||||
isRunning.value = false
|
||||
currentTime.value = 0
|
||||
completedThreads.value = 0
|
||||
contextSwitches.value = 0
|
||||
totalWaitTime.value = 0
|
||||
startTime.value = null
|
||||
currentThreadIndex = 0
|
||||
if (animationId) {
|
||||
cancelAnimationFrame(animationId)
|
||||
animationId = null
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSimulation() {
|
||||
if (isRunning.value) {
|
||||
pauseSimulation()
|
||||
} else {
|
||||
startSimulation()
|
||||
}
|
||||
}
|
||||
|
||||
function startSimulation() {
|
||||
if (threads.value.length === 0) {
|
||||
// 自动创建一些线程
|
||||
for (let i = 0; i < 3; i++) {
|
||||
addThread()
|
||||
}
|
||||
}
|
||||
|
||||
isRunning.value = true
|
||||
if (!startTime.value) {
|
||||
startTime.value = Date.now()
|
||||
}
|
||||
|
||||
// 初始化所有线程的开始时间
|
||||
threads.value.forEach(thread => {
|
||||
if (!thread.startTime) {
|
||||
thread.startTime = Date.now()
|
||||
}
|
||||
})
|
||||
|
||||
runSimulation()
|
||||
}
|
||||
|
||||
function pauseSimulation() {
|
||||
isRunning.value = false
|
||||
if (animationId) {
|
||||
cancelAnimationFrame(animationId)
|
||||
animationId = null
|
||||
}
|
||||
}
|
||||
|
||||
function runSimulation() {
|
||||
if (!isRunning.value) return
|
||||
|
||||
// 根据调度策略选择下一个线程
|
||||
let nextThread = null
|
||||
let nextIndex = -1
|
||||
|
||||
switch (schedulingPolicy.value) {
|
||||
case 'fifo':
|
||||
// FIFO: 找到第一个未完成的线程
|
||||
for (let i = 0; i < threads.value.length; i++) {
|
||||
if (threads.value[i].progress < threads.value[i].workAmount) {
|
||||
nextThread = threads.value[i]
|
||||
nextIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case 'roundrobin':
|
||||
// Round Robin: 轮流执行
|
||||
let attempts = 0
|
||||
while (attempts < threads.value.length) {
|
||||
const idx = currentThreadIndex % threads.value.length
|
||||
if (threads.value[idx].progress < threads.value[idx].workAmount) {
|
||||
nextThread = threads.value[idx]
|
||||
nextIndex = idx
|
||||
currentThreadIndex = (idx + 1) % threads.value.length
|
||||
break
|
||||
}
|
||||
currentThreadIndex++
|
||||
attempts++
|
||||
}
|
||||
break
|
||||
|
||||
case 'priority':
|
||||
// Priority: 选择优先级最高的就绪线程
|
||||
let highestPriority = -1
|
||||
for (let i = 0; i < threads.value.length; i++) {
|
||||
const thread = threads.value[i]
|
||||
if (thread.progress < thread.workAmount && thread.priority > highestPriority) {
|
||||
highestPriority = thread.priority
|
||||
nextThread = thread
|
||||
nextIndex = i
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// 执行选中的线程
|
||||
if (nextThread) {
|
||||
// 记录状态变化
|
||||
if (nextThread.state !== 'running') {
|
||||
contextSwitches.value++
|
||||
nextThread.state = 'running'
|
||||
}
|
||||
|
||||
// 其他线程设为就绪状态
|
||||
threads.value.forEach((thread, idx) => {
|
||||
if (idx !== nextIndex && thread.state === 'running') {
|
||||
thread.state = 'ready'
|
||||
}
|
||||
})
|
||||
|
||||
// 记录执行槽
|
||||
const lastSlot = nextThread.executionSlots[nextThread.executionSlots.length - 1]
|
||||
if (!lastSlot || lastSlot.state !== 'running') {
|
||||
nextThread.executionSlots.push({
|
||||
start: nextThread.progress,
|
||||
width: 0,
|
||||
state: 'running'
|
||||
})
|
||||
} else {
|
||||
lastSlot.width = 2
|
||||
}
|
||||
|
||||
// 增加进度
|
||||
const increment = schedulingPolicy.value === 'roundrobin' ? 5 : 3
|
||||
nextThread.progress = Math.min(nextThread.progress + increment, nextThread.workAmount)
|
||||
|
||||
// 检查是否完成
|
||||
if (nextThread.progress >= nextThread.workAmount) {
|
||||
nextThread.state = 'completed'
|
||||
nextThread.endTime = Date.now()
|
||||
completedThreads.value++
|
||||
totalWaitTime.value += (nextThread.endTime - nextThread.startTime)
|
||||
}
|
||||
|
||||
// 更新时间显示
|
||||
currentTime.value = nextThread.progress
|
||||
}
|
||||
|
||||
// 检查是否所有线程都完成
|
||||
const allCompleted = threads.value.every(t => t.progress >= t.workAmount)
|
||||
if (allCompleted) {
|
||||
isRunning.value = false
|
||||
} else {
|
||||
animationId = requestAnimationFrame(runSimulation)
|
||||
}
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
// 自动创建初始线程
|
||||
for (let i = 0; i < 3; i++) {
|
||||
addThread()
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (animationId) {
|
||||
cancelAnimationFrame(animationId)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.demo-container {
|
||||
padding: 20px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 0 0 16px 0;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.memory-view {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.memory-label {
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.memory-blocks {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.process-block {
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
color: white;
|
||||
transition: all 0.3s;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.process-block.crashed {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.process-block.active {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.process-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.process-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.process-pid {
|
||||
opacity: 0.8;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.process-memory {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.memory-section {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
opacity: 0.7;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.section-size {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.crash-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.crash-text {
|
||||
font-size: 24px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.crash-info {
|
||||
font-size: 12px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.shared-memory {
|
||||
margin-top: 16px;
|
||||
padding: 12px;
|
||||
background: #f4f4f5;
|
||||
border-radius: 6px;
|
||||
border: 2px dashed #c0c4cc;
|
||||
}
|
||||
|
||||
.shared-label {
|
||||
font-weight: bold;
|
||||
color: #606266;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.shared-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.shared-access {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.access-indicator {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.info-panel {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.process-memory {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user