安装这两个才能协程composer require workerman/workerman ^5.0
composer require revolt/event-loop
php server.php start第一步
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
use Workerman\Timer;
use Workerman\Connection\TcpConnection;
use Workerman\Protocols\Http\Request;
use Workerman\Protocols\Http\Response;
use Revolt\EventLoop;
// 创建 HTTP Worker
$worker = new Worker('http://0.0.0.0:8080');
$worker->count = 4;
$worker->name = 'CoroutineDemo';
$worker->onWorkerStart = function($worker) {
echo "Workerman 协程服务器启动成功!\n";
echo "访问: http://127.0.0.1:8080\n";
};
$worker->onMessage = function(TcpConnection $connection, Request $request) {
$path = $request->path();
switch ($path) {
case '/':
$connection->send(createResponse(
"<h1>Workerman 5.x 协程示例</h1>
<ul>
<li><a href='/async-tasks'>多个异步任务并发</a></li>
<li><a href='/sequential'>顺序执行异步任务</a></li>
<li><a href='/timer'>定时器协程</a></li>
<li><a href='/mock-db'>模拟数据库查询</a></li>
</ul>"
));
break;
case '/async-tasks':
handleAsyncTasks($connection);
break;
case '/sequential':
handleSequential($connection);
break;
case '/timer':
handleTimer($connection);
break;
case '/mock-db':
handleMockDatabase($connection);
break;
default:
// 处理静态文件
$filePath = __DIR__ . $path;
// 如果请求根目录,尝试加载 index.html
if ($path === '/') {
$filePath = __DIR__ . '/index.html';
}
// 调试日志
echo "请求路径: $path\n";
echo "文件路径: $filePath\n";
echo "文件存在: " . (file_exists($filePath) ? '是' : '否') . "\n";
echo "是文件: " . (is_file($filePath) ? '是' : '否') . "\n\n";
// 检查文件是否存在
if (file_exists($filePath) && is_file($filePath)) {
$extension = pathinfo($filePath, PATHINFO_EXTENSION);
$mimeTypes = [
'html' => 'text/html',
'css' => 'text/css',
'js' => 'application/javascript',
'json' => 'application/json',
'png' => 'image/png',
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'gif' => 'image/gif',
'svg' => 'image/svg+xml',
];
$contentType = $mimeTypes[$extension] ?? 'application/octet-stream';
$connection->send(new Response(200, [
'Content-Type' => $contentType . '; charset=utf-8',
'Server' => 'Workerman/5.0'
], file_get_contents($filePath)));
} else {
$connection->send(createResponse('<h1>404 Not Found</h1>', 404));
}
}
};
// 示例1: 并发执行多个异步任务
function handleAsyncTasks(TcpConnection $connection) {
$startTime = microtime(true);
// 使用 EventLoop 的协程特性
$results = [];
$completed = 0;
$total = 3;
// 异步任务1: 模拟 HTTP 请求
EventLoop::delay(1.0, function() use (&$results, &$completed, $total, $connection, $startTime) {
$results['http'] = '模拟 HTTP 请求完成';
$completed++;
checkAndSend($connection, $results, $completed, $total, $startTime);
});
// 异步任务2: 模拟数据库查询
EventLoop::delay(1.5, function() use (&$results, &$completed, $total, $connection, $startTime) {
$results['database'] = '模拟数据库查询完成';
$completed++;
checkAndSend($connection, $results, $completed, $total, $startTime);
});
// 异步任务3: 模拟 Redis 操作
EventLoop::delay(0.8, function() use (&$results, &$completed, $total, $connection, $startTime) {
$results['redis'] = '模拟 Redis 操作完成';
$completed++;
checkAndSend($connection, $results, $completed, $total, $startTime);
});
}
function checkAndSend($connection, $results, $completed, $total, $startTime) {
if ($completed === $total) {
$duration = round((microtime(true) - $startTime) * 1000, 2);
$html = "<h1>并发异步任务示例</h1>";
$html .= "<p><strong>执行时间: {$duration}ms</strong></p>";
$html .= "<h3>结果:</h3><ul>";
foreach ($results as $key => $value) {
$html .= "<li><strong>{$key}:</strong> {$value}</li>";
}
$html .= "</ul>";
$html .= "<p><a href='/'>返回首页</a></p>";
$connection->send(createResponse($html));
}
}
// 示例2: 顺序执行异步任务
function handleSequential(TcpConnection $connection) {
$startTime = microtime(true);
// 第一步
EventLoop::delay(0.5, function() use ($connection, $startTime) {
$step1 = '第一步: 验证用户';
// 第二步
EventLoop::delay(0.5, function() use ($connection, $startTime, $step1) {
$step2 = '第二步: 查询数据';
// 第三步
EventLoop::delay(0.5, function() use ($connection, $startTime, $step1, $step2) {
$step3 = '第三步: 返回结果';
$duration = round((microtime(true) - $startTime) * 1000, 2);
$html = "<h1>顺序执行异步任务</h1>";
$html .= "<p><strong>总执行时间: {$duration}ms</strong></p>";
$html .= "<ol>";
$html .= "<li>{$step1} ✓</li>";
$html .= "<li>{$step2} ✓</li>";
$html .= "<li>{$step3} ✓</li>";
$html .= "</ol>";
$html .= "<p><a href='/'>返回首页</a></p>";
$connection->send(createResponse($html));
});
});
});
}
// 示例3: 定时器协程
function handleTimer(TcpConnection $connection) {
$count = 0;
$maxCount = 5;
$results = [];
$timerId = Timer::add(0.3, function() use (&$count, $maxCount, &$results, &$timerId, $connection) {
$count++;
$results[] = "定时任务执行第 {$count} 次 - " . date('H:i:s');
if ($count >= $maxCount) {
Timer::del($timerId);
$html = "<h1>定时器协程示例</h1>";
$html .= "<p>每 300ms 执行一次,共执行 {$maxCount} 次</p>";
$html .= "<h3>执行记录:</h3><ol>";
foreach ($results as $result) {
$html .= "<li>{$result}</li>";
}
$html .= "</ol>";
$html .= "<p><a href='/'>返回首页</a></p>";
$connection->send(createResponse($html));
}
});
}
// 示例4: 模拟数据库查询
function handleMockDatabase(TcpConnection $connection) {
$startTime = microtime(true);
// 模拟复杂的数据库查询流程
// 1. 查询用户信息
EventLoop::delay(0.3, function() use ($connection, $startTime) {
$user = [
'id' => 1,
'name' => '张三',
'email' => 'zhangsan@example.com'
];
// 2. 根据用户ID查询订单
EventLoop::delay(0.4, function() use ($connection, $startTime, $user) {
$orders = [
['id' => 101, 'product' => '商品A', 'price' => 99.00],
['id' => 102, 'product' => '商品B', 'price' => 199.00],
];
// 3. 查询用户积分
EventLoop::delay(0.2, function() use ($connection, $startTime, $user, $orders) {
$points = 1580;
$duration = round((microtime(true) - $startTime) * 1000, 2);
$html = "<h1>模拟数据库查询</h1>";
$html .= "<p><strong>总执行时间: {$duration}ms</strong></p>";
$html .= "<h3>用户信息:</h3>";
$html .= "<ul>";
$html .= "<li>ID: {$user['id']}</li>";
$html .= "<li>姓名: {$user['name']}</li>";
$html .= "<li>邮箱: {$user['email']}</li>";
$html .= "</ul>";
$html .= "<h3>订单列表:</h3><ul>";
foreach ($orders as $order) {
$html .= "<li>订单#{$order['id']}: {$order['product']} - ¥{$order['price']}</li>";
}
$html .= "</ul>";
$html .= "<h3>用户积分:</h3>";
$html .= "<p>{$points} 积分</p>";
$html .= "<p><a href='/'>返回首页</a></p>";
$connection->send(createResponse($html));
});
});
});
}
// 创建 HTTP 响应
function createResponse(string $body, int $status = 200): Response {
return new Response($status, [
'Content-Type' => 'text/html; charset=utf-8',
'Server' => 'Workerman/5.0'
], "<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>Workerman 协程示例</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
h1 { color: #333; }
a { color: #0066cc; text-decoration: none; }
a:hover { text-decoration: underline; }
ul, ol { line-height: 1.8; }
code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; }
</style>
</head>
<body>
{$body}
</body>
</html>");
}
// 运行所有 Worker
Worker::runAll();
我用超级通俗的方式给你讲解整个 server.php 文件!
依赖和初始化 (第1-14行)
<?php
require_once __DIR__ . '/vendor/autoload.php'; // 加载Composer安装的库
use Workerman\Worker; // 引入Workerman核心类
use Workerman\Timer; // 引入定时器功能
use Workerman\Connection\TcpConnection; // 引入连接管理
use Workerman\Protocols\Http\Request; // 引入HTTP请求处理
use Workerman\Protocols\Http\Response; // 引入HTTP响应处理
use Revolt\EventLoop; // 引入事件循环(协程的核心)
// 创建 HTTP Worker
$worker = new Worker('http://0.0.0.0:8080'); // 创建HTTP服务器,监听8080端口
$worker->count = 4; // 启动4个进程(利用多核CPU)
$worker->name = 'CoroutineDemo'; // 给服务器取个名字
餐厅,先准备好各种工具(引入库)
创建服务器)
)来服务客人
启动回调 (第16-19行)
$worker->onWorkerStart = function($worker) {
echo "Workerman 协程服务器启动成功!\n";
echo "访问: http://127.0.0.1:8080\n";
喊一声"开始营业啦!"
21-93行)
,决定不同的URL请求该怎么处理!
$worker->onMessage = function(TcpConnection $connection, Request $request) {
$path = $request->path(); // 获取访问的路径,比如 /async-tasks
switch ($path) {
case '/': // 访问首页
// 返回一个简单的导航页面
break;
case '/async-tasks': // 访问 /async-tasks
handleAsyncTasks($connection); 用并发执行函数
break;
case '/sequential': // 访问 /sequential
handleSequential($connection); // 调用顺序执行函数
break;
case '/timer':
handleTimer($connection); // 调用定时器函数
break;
case '/mock-db':
handleMockDatabase($connection); // 调用数据库查询函数
break;
default: // 其他路径(处理静态文件)
// 第53-91行:处理HTML、CSS、JS等静态文件
// 如果文件存在就返回,不存在就返回404
}
单,客人点什么菜,就做什么菜
asks → 做"并发执行"这道菜
al → 做"顺序执行"这道菜
x.html → 给你看菜单(静态文件)
行示例 (第95-124行)
function handleAsyncTasks(TcpConnection $connection) {
$startTime = microtime(true); // 记录开始时间
$results = []; // 存储结果
$completed = 0; // 已完成任务数
$total = 3; // 总任务数
// 🌐 任务1:模拟HTTP请求(1秒后完成)
EventLoop::delay(1.0, function() use (&$results, &$completed, ...) {
$results['http'] = '模拟 HTTP 请求完成';
$completed++;
checkAndSend(...); // 检查是否全部完成
});
// 💾 任务2:模拟数据库查询(1.5秒后完成)
EventLoop::delay(1.5, function() use (&$results, &$completed, ...) {
$results['database'] = '模拟数据库查询完成';
$completed++;
checkAndSend(...);
});
// ⚡ 任务3:模拟Redis操作(0.8秒后完成)
EventLoop::delay(0.8, function() use (&$results, &$completed, ...) {
$results['redis'] = '模拟 Redis 操作完成';
$completed++;
checkAndSend(...);
});
传统方式(顺序执行):
窗口1:👨 办业务1秒 → 👩 办业务1.5秒 → 👴 办业务0.8秒
总时间 = 1 + 1.5 + 0.8 = 3.3秒
协程方式(并发执行):
窗口1:👨 办业务(1秒) }
窗口2:👩 办业务(1.5秒) } 三个人同时开始!
窗口3:👴 办业务(0.8秒) }
delay(1.0, function() {...})
数"
续执行下一行代码
非阻塞!
并发送结果 (第126-139行)
function checkAndSend($connection, $results, $completed, $total, $startTime) {
if ($completed === $total) { // 如果3个任务都完成了
$duration = round((microtime(true) - $startTime) * 1000, 2);
// 计算总耗时(毫秒)
// 生成HTML响应
$html = "<h1>并发异步任务示例</h1>";
$html .= "<p><strong>执行时间: {$duration}ms</strong></p>";
// ... 显示所有结果
$connection->send(createResponse($html)); // 返回给浏览器
}
就像银行柜台,等3个窗口都办完了,统一告诉客户"全部办完了,用了XX时间"
行示例 (第141-170行)
function handleSequential(TcpConnection $connection) {
$startTime = microtime(true);
// 第一步(0.5秒后执行)
EventLoop::delay(0.5, function() use (...) {
$step1 = '第一步: 验证用户';
// 第二步(再过0.5秒执行,嵌套在第一步里面)
EventLoop::delay(0.5, function() use (...) {
$step2 = '第二步: 查询数据';
// 第三步(再过0.5秒执行,嵌套在第二步里面)
EventLoop::delay(0.5, function() use (...) {
$step3 = '第三步: 返回结果';
// 全部完成,返回结果
});
});
});
像排队办事,必须等前一个人办完,下一个人才能开始
执行
0.5 = 1.5秒
例 (第172-196行)
function handleTimer(TcpConnection $connection) {
$count = 0;
$maxCount = 5; // 执行5次
$results = [];
// 创建定时器,每300毫秒执行一次
$timerId = Timer::add(0.3, function() use (&$count, ...) {
$count++;
$results[] = "定时任务执行第 {$count} 次 - " . date('H:i:s');
if ($count >= $maxCount) {
Timer::del($timerId); // 删除定时器
// 返回结果
}
});
响一次
数据库查询 (第198-247行)
function handleMockDatabase(TcpConnection $connection) {
$startTime = microtime(true);
// 1. 先查用户信息(0.3秒)
EventLoop::delay(0.3, function() use (...) {
$user = ['id' => 1, 'name' => '张三', ...];
// 2. 再查订单(0.4秒)
EventLoop::delay(0.4, function() use (...) {
$orders = [['id' => 101, ...], ...];
// 3. 最后查积分(0.2秒)
EventLoop::delay(0.2, function() use (...) {
$points = 1580;
// 返回所有结果
// 总时间 = 0.3 + 0.4 + 0.2 = 0.9秒
});
});
});
实场景:先查用户 → 再查订单 → 最后查积分
三个查询依次进行(因为订单需要用户ID,积分需要订单信息)
TTP响应 (第249-272行)
function createResponse(string $body, int $status = 200): Response {
return new Response($status, [
'Content-Type' => 'text/html; charset=utf-8',
'Server' => 'Workerman/5.0'
], "<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>Workerman 协程示例</title>
<style>...</style>
</head>
<body>
{$body} <!-- 这里插入动态内容 -->
</body>
</html>");
就像给菜品装盘,把结果包装成漂亮的HTML页面返回给浏览器
务器 (第274-275行)
r::runAll(); // 启动所有Worker,开始监听请求
,开门营业!
循环,不断监听客户端请求
传统PHP(同步阻塞):
任务1 → 等待 → 任务2 → 等待 → 任务3
执行时间 = 累加
协程(异步非阻塞):
任务1 ↘
任务2 → 同时执行
任务3 ↗
EventLoop::delay(1.0, function() {
echo "1秒后执行";
});
echo "立即执行,不等待!"; // 先输出这个
// 输出顺序:
// 1. "立即执行,不等待!"
时调用多个第三方接口
表
件
息
| 场景 | 顺序执行 | 并发执行 | 提升 |
|---|---|---|---|
| 3个任务(1s+1.5s+0.8s) | 3.3秒 | 1.5秒 | 54% |
| 10个网络请求(各1s) | 10秒 | 1秒 | 90% |
然后http://127.0.0.1:8080/easy-demo.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>协程到底有多快?一看就懂!</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 40px;
}
h1 {
font-size: 3em;
margin-bottom: 20px;
text-shadow: 3px 3px 6px rgba(0,0,0,0.3);
}
.subtitle {
font-size: 1.5em;
margin-bottom: 15px;
}
.highlight {
background: rgba(255,215,0,0.3);
padding: 5px 15px;
border-radius: 10px;
color: #ffd700;
font-weight: bold;
}
/* 比喻说明卡片 */
.explanation-card {
background: white;
border-radius: 20px;
padding: 40px;
margin-bottom: 30px;
box-shadow: 0 15px 50px rgba(0,0,0,0.3);
}
.explanation-title {
font-size: 2em;
color: #667eea;
margin-bottom: 25px;
text-align: center;
}
.comparison-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin-bottom: 30px;
}
.comparison-box {
padding: 25px;
border-radius: 15px;
text-align: center;
}
.box-sequential {
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%);
color: white;
}
.box-concurrent {
background: linear-gradient(135deg, #51cf66 0%, #37b24d 100%);
color: white;
}
.comparison-box h3 {
font-size: 1.8em;
margin-bottom: 15px;
}
.comparison-box p {
font-size: 1.2em;
line-height: 1.8;
margin-bottom: 15px;
}
.time-display {
font-size: 3em;
font-weight: bold;
margin: 20px 0;
padding: 20px;
background: rgba(255,255,255,0.2);
border-radius: 15px;
}
.emoji {
font-size: 3em;
margin-bottom: 15px;
display: block;
}
/* 实际演示区域 */
.demo-section {
background: white;
border-radius: 20px;
padding: 40px;
margin-bottom: 30px;
box-shadow: 0 15px 50px rgba(0,0,0,0.3);
}
.demo-title {
font-size: 2.2em;
color: #667eea;
margin-bottom: 30px;
text-align: center;
}
.button-container {
display: flex;
gap: 20px;
justify-content: center;
margin-bottom: 40px;
flex-wrap: wrap;
}
.demo-button {
padding: 25px 50px;
font-size: 1.5em;
font-weight: bold;
border: none;
border-radius: 20px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 5px 20px rgba(0,0,0,0.2);
position: relative;
overflow: hidden;
}
.btn-fast {
background: linear-gradient(135deg, #51cf66 0%, #37b24d 100%);
color: white;
}
.btn-slow {
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%);
color: white;
}
.demo-button:hover:not(:disabled) {
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
.demo-button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.demo-button::before {
content: attr(data-emoji);
font-size: 1.2em;
margin-right: 10px;
}
/* 可视化区域 */
.visual-area {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin-bottom: 30px;
}
.visual-panel {
background: #f8f9fa;
border-radius: 15px;
padding: 30px;
min-height: 400px;
}
.panel-header {
text-align: center;
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 3px solid #dee2e6;
}
.panel-title {
font-size: 1.8em;
margin-bottom: 10px;
}
.panel-subtitle {
color: #666;
font-size: 1.1em;
}
/* 任务卡片 */
.task-card {
background: white;
border-radius: 12px;
padding: 20px;
margin-bottom: 15px;
box-shadow: 0 3px 10px rgba(0,0,0,0.1);
position: relative;
overflow: hidden;
}
.task-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.task-name {
font-size: 1.2em;
font-weight: bold;
color: #333;
}
.task-status {
padding: 5px 15px;
border-radius: 20px;
font-size: 0.9em;
font-weight: bold;
}
.status-waiting {
background: #e9ecef;
color: #6c757d;
}
.status-running {
background: #4dabf7;
color: white;
animation: blink 1s infinite;
}
.status-done {
background: #51cf66;
color: white;
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
.task-progress {
height: 8px;
background: #e9ecef;
border-radius: 10px;
overflow: hidden;
margin-bottom: 10px;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
width: 0%;
transition: width 0.3s ease;
border-radius: 10px;
}
.task-time {
color: #666;
font-size: 0.95em;
}
/* 结果面板 */
.result-panel {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 15px;
padding: 25px;
text-align: center;
margin-top: 20px;
}
.result-label {
font-size: 1.1em;
margin-bottom: 10px;
opacity: 0.9;
}
.result-time {
font-size: 3em;
font-weight: bold;
}
/* 最终对比结果 */
.final-result {
background: white;
border-radius: 20px;
padding: 40px;
box-shadow: 0 15px 50px rgba(0,0,0,0.3);
display: none;
margin-bottom: 30px;
}
.final-result.show {
display: block;
animation: slideUp 0.6s ease;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(50px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.final-title {
font-size: 2.5em;
color: #667eea;
text-align: center;
margin-bottom: 40px;
}
.final-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 25px;
margin-bottom: 40px;
}
.stat-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
border-radius: 15px;
text-align: center;
}
.stat-value {
font-size: 3.5em;
font-weight: bold;
margin-bottom: 10px;
}
.stat-label {
font-size: 1.2em;
opacity: 0.9;
}
.conclusion {
background: #d4edda;
border: 3px solid #28a745;
border-radius: 15px;
padding: 30px;
text-align: center;
}
.conclusion-text {
font-size: 1.8em;
color: #155724;
line-height: 1.8;
font-weight: bold;
}
.conclusion-emoji {
font-size: 4em;
margin-bottom: 20px;
}
@media (max-width: 1024px) {
.comparison-row,
.visual-area {
grid-template-columns: 1fr;
}
.final-stats {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚀 协程到底有多快?</h1>
<p class="subtitle">用<span class="highlight">去银行办事</span>的例子说明白!</p>
</div>
<!-- 比喻说明 -->
<div class="explanation-card">
<div class="explanation-title">💡 生活中的例子</div>
<div class="comparison-row">
<div class="comparison-box box-sequential">
<span class="emoji">🐢</span>
<h3>顺序执行</h3>
<p>就像银行<strong>只开一个窗口</strong></p>
<p>三个人排队办业务:</p>
<p>👨 第1个人办完 (1秒)</p>
<p>👩 第2个人才能开始 (1.5秒)</p>
<p>👴 第3个人最后办 (0.8秒)</p>
<div class="time-display">总共: 3.3秒</div>
</div>
<div class="comparison-box box-concurrent">
<span class="emoji">⚡</span>
<h3>并发执行 (协程)</h3>
<p>就像银行<strong>开三个窗口</strong></p>
<p>三个人同时办业务:</p>
<p>👨👩👴 三人一起开始</p>
<p>最快的先办完</p>
<p>等最慢的那个就行</p>
<div class="time-display">总共: 1.5秒</div>
</div>
</div>
<div style="text-align: center; font-size: 1.5em; color: #667eea; font-weight: bold;">
✨ 并发执行快了 <span style="color: #28a745;">54%</span>,这就是协程的魔力!
</div>
</div>
<!-- 实际演示 -->
<div class="demo-section">
<div class="demo-title">🎮 点击按钮,看真实效果!</div>
<div class="button-container">
<button class="demo-button btn-fast" data-emoji="⚡" onclick="runConcurrent()" id="btnConcurrent">
并发执行 (三个窗口)
</button>
<button class="demo-button btn-slow" data-emoji="🐢" onclick="runSequential()" id="btnSequential">
顺序执行 (一个窗口)
</button>
</div>
<div class="visual-area">
<!-- 并发执行可视化 -->
<div class="visual-panel">
<div class="panel-header">
<div class="panel-title">⚡ 并发执行</div>
<div class="panel-subtitle">三个任务同时开始</div>
</div>
<div class="task-card">
<div class="task-header">
<div class="task-name">🌐 HTTP请求</div>
<div class="task-status status-waiting" id="concurrent-status-1">等待</div>
</div>
<div class="task-progress">
<div class="progress-bar" id="concurrent-progress-1"></div>
</div>
<div class="task-time" id="concurrent-time-1">需要 1000ms</div>
</div>
<div class="task-card">
<div class="task-header">
<div class="task-name">💾 数据库查询</div>
<div class="task-status status-waiting" id="concurrent-status-2">等待</div>
</div>
<div class="task-progress">
<div class="progress-bar" id="concurrent-progress-2"></div>
</div>
<div class="task-time" id="concurrent-time-2">需要 1500ms</div>
</div>
<div class="task-card">
<div class="task-header">
<div class="task-name">⚡ Redis操作</div>
<div class="task-status status-waiting" id="concurrent-status-3">等待</div>
</div>
<div class="task-progress">
<div class="progress-bar" id="concurrent-progress-3"></div>
</div>
<div class="task-time" id="concurrent-time-3">需要 800ms</div>
</div>
<div class="result-panel">
<div class="result-label">总执行时间</div>
<div class="result-time" id="concurrentResult">-</div>
</div>
</div>
<!-- 顺序执行可视化 -->
<div class="visual-panel">
<div class="panel-header">
<div class="panel-title">🐢 顺序执行</div>
<div class="panel-subtitle">一个接一个执行</div>
</div>
<div class="task-card">
<div class="task-header">
<div class="task-name">🌐 HTTP请求</div>
<div class="task-status status-waiting" id="sequential-status-1">等待</div>
</div>
<div class="task-progress">
<div class="progress-bar" id="sequential-progress-1"></div>
</div>
<div class="task-time" id="sequential-time-1">需要 1000ms</div>
</div>
<div class="task-card">
<div class="task-header">
<div class="task-name">💾 数据库查询</div>
<div class="task-status status-waiting" id="sequential-status-2">等待</div>
</div>
<div class="task-progress">
<div class="progress-bar" id="sequential-progress-2"></div>
</div>
<div class="task-time" id="sequential-time-2">需要 1500ms</div>
</div>
<div class="task-card">
<div class="task-header">
<div class="task-name">⚡ Redis操作</div>
<div class="task-status status-waiting" id="sequential-status-3">等待</div>
</div>
<div class="task-progress">
<div class="progress-bar" id="sequential-progress-3"></div>
</div>
<div class="task-time" id="sequential-time-3">需要 800ms</div>
</div>
<div class="result-panel">
<div class="result-label">总执行时间</div>
<div class="result-time" id="sequentialResult">-</div>
</div>
</div>
</div>
</div>
<!-- 最终对比结果 -->
<div class="final-result" id="finalResult">
<div class="final-title">🎉 对比结果出炉!</div>
<div class="final-stats">
<div class="stat-card">
<div class="stat-value" id="statConcurrent">-</div>
<div class="stat-label">并发执行</div>
</div>
<div class="stat-card">
<div class="stat-value" id="statSequential">-</div>
<div class="stat-label">顺序执行</div>
</div>
<div class="stat-card">
<div class="stat-value" id="statSaved">-</div>
<div class="stat-label">节省时间</div>
</div>
</div>
<div class="conclusion">
<div class="conclusion-emoji">🚀</div>
<div class="conclusion-text" id="conclusionText">
协程让程序快了XX%!<br>
就像银行多开窗口,效率大幅提升!
</div>
</div>
</div>
</div>
<script>
let concurrentTime = 0;
let sequentialTime = 0;
let isRunning = false;
// 并发执行
async function runConcurrent() {
if (isRunning) return;
isRunning = true;
disableButtons();
resetTasks('concurrent');
document.getElementById('concurrentResult').textContent = '执行中...';
const startTime = Date.now();
// 模拟三个任务同时开始
const tasks = [
animateTask('concurrent', 1, 1000),
animateTask('concurrent', 2, 1500),
animateTask('concurrent', 3, 800)
];
await Promise.all(tasks);
// 调用真实的服务器接口
try {
const response = await fetch('http://127.0.0.1:8080/async-tasks');
const realTime = Date.now() - startTime;
concurrentTime = realTime;
document.getElementById('concurrentResult').textContent = realTime + 'ms';
checkAndShowResult();
} catch (error) {
document.getElementById('concurrentResult').textContent = '连接失败';
alert('请确保服务器正在运行:php server.php start');
}
isRunning = false;
enableButtons();
}
// 顺序执行
async function runSequential() {
if (isRunning) return;
isRunning = true;
disableButtons();
resetTasks('sequential');
document.getElementById('sequentialResult').textContent = '执行中...';
const startTime = Date.now();
// 一个接一个执行
await animateTask('sequential', 1, 1000);
await animateTask('sequential', 2, 1500);
await animateTask('sequential', 3, 800);
// 调用真实的服务器接口
try {
const response = await fetch('http://127.0.0.1:8080/sequential');
const realTime = Date.now() - startTime;
sequentialTime = realTime;
document.getElementById('sequentialResult').textContent = realTime + 'ms';
checkAndShowResult();
} catch (error) {
document.getElementById('sequentialResult').textContent = '连接失败';
alert('请确保服务器正在运行:php server.php start');
}
isRunning = false;
enableButtons();
}
// 动画模拟任务执行
function animateTask(type, taskNum, duration) {
return new Promise(resolve => {
const statusEl = document.getElementById(`${type}-status-${taskNum}`);
const progressEl = document.getElementById(`${type}-progress-${taskNum}`);
const timeEl = document.getElementById(`${type}-time-${taskNum}`);
statusEl.textContent = '执行中';
statusEl.className = 'task-status status-running';
let elapsed = 0;
const interval = setInterval(() => {
elapsed += 50;
const progress = Math.min((elapsed / duration) * 100, 100);
progressEl.style.width = progress + '%';
timeEl.textContent = `已执行 ${elapsed}ms / ${duration}ms`;
if (elapsed >= duration) {
clearInterval(interval);
statusEl.textContent = '完成';
statusEl.className = 'task-status status-done';
timeEl.textContent = `✓ 完成,耗时 ${duration}ms`;
resolve();
}
}, 50);
});
}
// 重置任务状态
function resetTasks(type) {
for (let i = 1; i <= 3; i++) {
const statusEl = document.getElementById(`${type}-status-${i}`);
const progressEl = document.getElementById(`${type}-progress-${i}`);
const durations = [1000, 1500, 800];
statusEl.textContent = '等待';
statusEl.className = 'task-status status-waiting';
progressEl.style.width = '0%';
document.getElementById(`${type}-time-${i}`).textContent = `需要 ${durations[i-1]}ms`;
}
}
// 检查并显示最终结果
function checkAndShowResult() {
if (concurrentTime > 0 && sequentialTime > 0) {
const saved = sequentialTime - concurrentTime;
const percentage = ((saved / sequentialTime) * 100).toFixed(1);
document.getElementById('statConcurrent').textContent = concurrentTime + 'ms';
document.getElementById('statSequential').textContent = sequentialTime + 'ms';
document.getElementById('statSaved').textContent = saved + 'ms';
document.getElementById('conclusionText').innerHTML = `
协程让程序快了 <span style="color: #28a745; font-size: 1.2em;">${percentage}%</span>!<br>
就像银行多开窗口,效率大幅提升!
`;
const resultEl = document.getElementById('finalResult');
resultEl.classList.add('show');
setTimeout(() => {
resultEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
}, 300);
}
}
function disableButtons() {
document.getElementById('btnConcurrent').disabled = true;
document.getElementById('btnSequential').disabled = true;
}
function enableButtons() {
document.getElementById('btnConcurrent').disabled = false;
document.getElementById('btnSequential').disabled = false;
}
</script>
</body>
</html>