ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从协程调度到IO管理:sylar框架源码精读与工程实践

从协程调度到IO管理:sylar框架源码精读与工程实践 作为一个在C服务端开发岗位上摸爬滚打了七八年的老码农我这两年最大的感受就是光靠写业务逻辑、调CRUD技术成长真的会到瓶颈。很多人问我怎么突破我的答案很简单找一个足够硬核的开源项目沉下心精读 复刻。而我第一个推荐的项目就是sylar。sylar是一个基于C11/14的高性能分布式服务端框架作者是sylar-yin目前在GitHub上开源。虽然它不像某些大厂框架那样有商业化背书但它在协程、网络IO、HTTP协议、RPC通信、序列化、配置管理、日志系统这些方面的实现非常完整代码风格清晰注释虽少但结构极其模块化。在我看来它就是一个把教科书理论变成工业级实践的绝佳范本。这篇学习系列的第一篇我会从框架的整体架构和核心引擎——协程调度——入手带你拆解它的设计思路和实现细节并把我啃代码过程中踩过的坑、总结出来的方法论一并分享出来。1. 为什么值得啃sylar框架全景与学习路径1.1 它不是玩具而是一个完整的服务端技术栈很多人一听到“开源框架学习”第一反应是去看TinyWebServer这种几百行的教学项目。TinyWebServer适合入门但它的粒度太粗很多生产环境必须的模块比如日志、配置、协程调度、Hook异步化要么没有要么非常简陋。而sylar不一样它几乎覆盖了一个工业级服务端框架的所有关键部分。我简单梳理一下sylar的核心模块你就知道它的含金量了日志模块仿log4j设计支持Logger、Appender、Formatter、Level等概念支持按级别输出、按大小滚动文件。配置模块支持YAML配置文件的解析和运行时动态修改配置变更会自动推送回调给业务代码这个设计在很多商业框架里才能见到。协程模块基于ucontext实现的有栈协程支持协程的创建、切换、挂起、恢复这是整个框架的基石。协程调度模块实现了一个N:M的线程-协程调度器把任务队列和线程池结合起来。IO协程调度模块在调度器基础上整合了epoll实现IO事件与协程调度的统一这是sylar区别于很多教学项目的地方——它真正把“异步IO”和“协程”揉在了一起。Hook模块通过动态库的符号替换把socket相关的阻塞调用如read、write、connect、accept自动转化成协程的挂起和唤醒业务代码写起来像同步底层跑的是异步。Socket与Address模块封装了socket常用操作和地址解析并配合协程提供了异步接口。HTTP模块包括HTTP请求/响应解析、HTTP Server实现支持Keep-Alive、Chunked编码等。Stream模块抽象了流式数据的读写包括SocketStream、HttpSession等。RPC模块基于自定义协议实现远程过程调用支持序列化、心跳检测、连接复用。你看这是一个完整的“从底层到上层”的技术栈。如果说大学课程教的是一个个离散的知识点那sylar就是把这些知识点串成了一条线。学完它你对整个服务端框架的认知会从一个点扩展成一个面。1.2 什么样的学习路径最高效坦白讲sylar这面代码量不小我数过光src目录下的核心源文件就有将近100个。如果从头到尾按目录顺序读很容易在中途放弃。我的建议是把整个学习过程分成五个阶段有主有次地去啃第一阶段跑起来。先编译、运行示例代码理解日志和配置模块的基本用法建立“这个东西能用”的直观感受。第二阶段攻克协程基石。集中精力搞懂Fiber和Scheduler的原理这是后续所有模块的基础。这一阶段我认为是最难的部分也是回本最高的部分。第三阶段理解IO与Hook。看IO调度器如何整合epoll看Hook如何把阻塞调用变成协程友好操作。第四阶段自底向上过业务模块。有了前面的基础再看Socket、HTTP、RPC就轻松很多。第五阶段动手改造。尝试加一个自己的模块比如一个基于sylar的WebSocket服务、一个简单的API网关通过改造加深理解。后面的内容我会按照这个路径先带你攻克第一道也是最关键的一道关卡协程和协程调度器。2. 协程调度sylar的发动机到底怎么运转2.1 有栈协程的本质让函数可以随时“暂停”和“恢复”很多人第一次听说协程容易把它和线程混淆。线程的切换由内核完成涉及用户态到内核态的切换、上下文保存恢复代价不小。而协程的切换完全在用户态进行它本质上就是让一个函数执行到一半时保存现场、跳出去执行别的代码之后还能跳回来继续执行。实现这样一个机制核心就是一个“能保存和恢复CPU执行现场”的数据结构。sylar用的是ucontext这个POSIX标准库底层基于getcontext、makecontext、swapcontext这几个函数。你可以把ucontext_t想象成一个“状态快照盒”里面保存了寄存器、栈指针、程序计数器等信息。切出协程时把当前快照存下来切回时再把快照恢复CPU就“以为”自己没有离开过。举一个生活化的例子你在写一份文档协程A写到一半突然来了个电话IO事件你记录下当前写到第几行、光标在哪保存上下文然后去接电话切到协程B接完电话回来你坐下接着刚才的光标位置继续写恢复上下文。整个过程你并没有离开工位只是在处理不同任务。协程切换就是这种“同一线程内的快速任务切换”因为不用进内核所以开销极小——sylar里一次协程切换的性能实测下来比线程切换低一个数量级。sylar的Fiber类定义在fiber.h里关键成员就几个m_stack栈空间、m_ctx上下文、m_state协程状态、m_id协程ID。它把协程状态划分为INIT、HOLD、EXEC、TERM、READY、EXCEPT这几个枚举值。理解这几个状态之间的转换关系是整个协程模块的关键创建协程后处于INIT表示还没有开始执行。被调度器run时进入EXEC这是执行中状态。执行中如果调用了yield()让出执行权会变成HOLD挂起或者READY如果调度器主动把它重新加入任务队列。执行完毕后进入TERM协程生命周期结束。这里我一开始也绕了很久为什么会有HOLD和READY的区别后来看代码才明白yield时协程自己不知道自己下次是“被重新唤醒继续执行”还是“从头执行”而调度器通过任务状态来标记。如果在IO协程调度模块里协程通常是等待某个事件而挂起属于HOLD如果在普通调度器里主动让出可能直接再次入队属于READY。2.2 调度器Scheduler线程池上的任务分配器有了协程还需要一个“谁来决定跑哪一个协程”的角色这就是Scheduler调度器。sylar的Scheduler其实是一个线程池 任务队列的组合体它的总体结构可以用一句话概括创建一组线程每个线程有自己的协程运行环境当一个协程主动让出或执行完毕时调度器从队列中取下一个任务给线程去跑。我建议你精读Scheduler::run()这个函数它是调度器的核心。伪代码逻辑大致是这样的每个调度线程的run(): 设置当前线程的调度器上下文 执行线程初始化回调 while (true) { 加锁检查任务队列 如果有待执行任务取出一个协程任务或回调任务 否则如果调度器没有停止尝试从其他线程的队列偷任务idle情况下 如果实在没任务执行idle协程在idle里等待唤醒条件 执行取出的任务执行完就释放继续循环 }这个设计里有一个容易让人困惑的点调度器本身是运行在“主协程”中的。每个调度线程创建时会初始化一个“线程主协程”t_thread_fiber当没有任务时run()会执行一个叫idle的协程——这个协程通常是一个空循环或者等待事件。sylar在IO调度器里重写了这个idle协程让它在没有任务时去epoll_wait阻塞等待IO事件这样CPU就不会空转了。这一点在后面看IO调度器时要特别注意它就是把“普通调度器”和“IO调度器”区分开的那层窗户纸。调度器的另一个重要设计是任务队列的分配。sylar并没有用单一全局队列而是使用了一个FiberAndThread结构来表示任务里面记录了这个任务希望放在哪个线程上执行如果m_thread -1就表示任意线程均可执行调度器会把它放到当前调度线程的本地队列中。这种设计有几个好处一是每个线程操作自己的私有队列减少了锁竞争二是当某个协程被指定到固定线程执行时它的局部变量、缓存亲和性都能得到保留。我在自己实现一个简化版调度器时深有体会偷懒用单全局队列结果高并发场景下锁竞争非常严重后来换成每线程本地队列工作窃取才解决问题。3. IO协程调度器与Hook机制把“异步”翻译成“同步”3.1 IO调度器epoll和协程是怎么合体的普通的Scheduler只能调度纯计算型任务一旦遇到IO操作比如读一个socket线程就会阻塞在系统调用上这就浪费了协程“轻量切换”的优势。sylar给出的解决方案很简单粗暴却非常优雅让调度线程在空闲时去等epoll事件当某个fd可读可写时唤醒等待该fd的协程。IOManager类继承自Scheduler它引入了两个重要概念FdContext和事件回调。每个被监控的文件描述符对应一个FdContext里面保存了fd的读写事件回调实际上是协程。IOManager启动后所有调度线程的idle协程都会进入epoll_wait等待事件。当某个fd上有事件触发epoll返回IOManager遍历就绪事件列表找到对应的FdContext把事件回调协程放入调度队列中等待调度线程执行。这里我要重点说一下epoll_wait这件事。因为所有调度线程都在等同一个epoll实例如果多个线程同时返回同一批事件就可能导致同一个协程被多个线程同时调度。sylar用了一个很巧妙的锁机制每个调度线程在调用epoll_wait之前会先尝试获取一个互斥锁只有拿到锁的线程才能真正进入epoll_wait其他线程在idle协程里自旋等待。这样即使有多个调度线程也只有一个线程在等事件避免了惊群效应和重复唤醒。我在初读这一层时花了很长时间主要卡在一个问题上为什么协程的“读事件”不是进入epoll监听后就完事了而是要在事件触发后再把协程重新放回任务队列后来想明白了sylar的协程调度模型是非阻塞轮询 回调驱动的结合体。epoll负责“告诉”调度器哪个fd准备好了但真正“执行读操作”的仍然是协程本身。也就是说协程不会在epoll等待期间占用任何线程资源当事件触发后它会被重新放回队列由某个空闲线程接着执行刚才yield之后的代码。这就是“用同步的代码写异步的逻辑”的核心内涵。3.2 Hook钩子让“阻塞”自动变“协程”有了IO调度器理论框架已经通了。但还有一个问题如果你在协程里直接调用read(fd, buf, len)这个调用仍然是阻塞的会卡住整个线程。怎么让普通的socket编程接口也能自动适应协程调度呢sylar的做法是Hook——在系统调用层做文章。Hook的实现原理并不神秘Linux的动态链接机制允许我们自定义一个与libc里同名的函数比如read然后通过dlsym(RTLD_NEXT, read)拿到真正的系统函数地址。sylar在启动时会条件性地对这些系统函数做替换通过环境变量或者hook模块的初始化开关替换的逻辑大致是判断当前调用是否发生在协程中如果不是直接调用真正的系统函数。如果是协程判断操作的fd是否是非阻塞模式sylar默认会把fd设为非阻塞。如果是非阻塞fd先发起系统调用如果返回EAGAIN表示当前没数据就把当前协程挂起注册到IOManager的epoll监听列表里等fd可读/可写事件触发后再恢复协程。事件触发后协程恢复再次发起真正的系统调用这时候数据已经就绪调用立即返回。这里有一个细节让我印象很深connect的Hook实现。正常connect一个非阻塞socket会立即返回EINPROGRESS意味着连接正在建立这时候你需要用poll或epoll等待可写事件来确认连接是否成功。sylar的connect Hook会在返回EINPROGRESS后让协程挂起等待可写事件然后在事件回调里用getsockopt(SO_ERROR)检查连接结果再返回给业务层。这样做之后你在协程里写connect的代码就跟写阻塞式socket一样了根本不需要关心底层的非阻塞和事件循环。Hook是sylar学习曲线中最陡峭的部分之一。建议你对照着sylar/hook.cpp和/sylar/iomanager.cpp两个文件反复读最好自己动手写一个只Hookread和write的最小版本再做一个小测试一个协程读socket、另一个协程写socket看看能否实现互相唤醒。这个过程做完之后你对“同步转异步”的理解会非常深刻。4. 定时器、Socket封装与HTTP协议栈的工程化细节4.1 定时器设计最小堆如何支撑海量超时任务服务端框架里定时器几乎是必需品比如RPC超时、连接心跳、延迟任务等。sylar的Timer模块并没有直接用现成的定时器库而是自己基于最小堆实现了一个并和IOManager深度融合。最小堆的核心思路是所有定时器按照下一次触发时间戳排序堆顶就是最接近超时的那个。每次插入或删除定时器后IOManager会重新计算当前最小超时时间动态修改epoll_wait的timeout参数。当epoll_wait因为超时返回时框架检查堆顶定时器是否到期如果到期就取出对应回调放入任务队列同时继续检查下一个。如果没到期就继续epoll_wait等待剩余时间。这样做有一个很大的优势定时器和IO事件在同一个线程里被统等不需要单独的定时器线程自然也不会出现多线程加锁的复杂问题。我之前用过纯std::threadsleep实现的定时器在高频插入删除的场景下被锁竞争折磨得够呛。而sylar这种“定时器跟IO事件共享同一个事件循环”的设计本质上就是Redis里单线程事件循环的翻版代码简洁、性能稳定。需要注意的一个工程细节是定时器回调中出现异常不能影响整体调度所以sylar在定时器回调执行时用了一个try...catch...包住并且捕获后记录日志。这个细节我在看代码时一开始忽略了后来自己实现定时功能时因为回调抛异常导致整个调度线程崩溃才理解了这个兜底的重要性。4.2 Socket封装细节决定上层业务能跑多顺sylar对socket的封装职责边界很清晰只负责socket生命周期的管理和常用操作的封装不掺入具体的业务协议。Socket类内部持有m_fd、m_family、m_type、m_protocol等属性提供connect、accept、read、write、close等方法。在协程环境下这些方法几乎都走Hook机制所以业务代码可以直接以“同步阻塞”的方式写而底层是非阻塞的。这里我要分享一个我在改造和踩坑中总结出来的经验不要把业务逻辑写在Socket类里。sylar的Socket做了很好的分层设计Socket只负责字节流传输而封装HTTP协议的是HttpSession封装RPC协议的是RpcSession它们都在Socket之上增加协议解析逻辑。这种“传输层与协议层分离”的思想是任何一个可扩展服务端框架都会遵循的黄金法则。如果你写代码时发现某个类既要做网络传输又要解析报文那大概率就是设计出问题了。地址模块也值得一提。sylar的Address类支持IPv4、IPv6、Unix Socket地址它做了几个细节一个是通过getaddrinfo实现域名解析另一个是在地址转字符串时正确处理了端口和IPv6的方括号表示法。看起来不起眼但如果你自己写过socket程序一定遇到过“地址打印出来格式不对”的尴尬。4.3 HTTP模块从解析到Server的完整闭环我当初学sylar时最期待的就是HTTP模块因为它直接能跑出一个Web服务来。sylar的HTTP模块分为两部分http_parser负责HTTP报文解析http_server负责把解析好的报文交给业务处理。先说报文解析。sylar没有自己造轮子而是直接集成了http-parser这个C库。http-parser解析HTTP报文时完全是事件驱动的它通过回调把URL、Header、Body等信息一一交付给调用方。这样做的好处是解析速度快、不额外分配内存。但坏处也很明显它的回调式API用起来不太符合我们人类的直觉。sylar做了一层封装把这些回调转成HttpRequest/HttpResponse对象业务层拿到的就是结构化数据。再说HTTP Server。sylar的HttpServer是基于前面讲的IO调度器运行的。每个客户端连接会创建一个HttpSession然后协程处理循环大致是从socket读取请求交给http_parser解析得到HttpRequest后调用业务servlet处理把结果封装成HttpResponse再通过socket写回。这里最关键的一个细节是Keep-Alive的实现如果请求头里带了Connection: keep-alive处理完当前请求后不能关闭连接而是要继续读下一个请求。很多初学者在这里会踩坑导致客户端复用连接时出现各种奇怪掉链子问题。sylar的HttpSession内部用了一个状态机来区分“解析header中”“解析body中”“请求完成”处理的非常规范。Servlet机制也很有价值。sylar里有一个ServletDispatch类它是一个“路由表”把不同路径映射到不同的处理函数。比如你可以注册/hello到某个回调注册/user/info到另一个回调。框架会支持精确匹配、模糊匹配通配符两种方式还有个默认的兜底Servlet来处理404。这个机制其实就是一个极简的Web框架路由理解了它之后你再去用Django、Spring的URL Router时会发现它们本质都是同一回事。我这里贴一段我后来自己实现的一个最小HttpServer代码片段帮助你串起整个流程用sylar的框架#include sylar/http/http_server.h #include sylar/http/http_session.h #include sylar/log.h static auto g_logger SYLAR_LOG_ROOT(); void run() { sylar::http::HttpServer::ptr server(new sylar::http::HttpServer); // 注册一个默认servlet处理所有请求 server-getServletDispatch()-addServlet(/hello, [](sylar::http::HttpRequest::ptr req, sylar::http::HttpResponse::ptr rsp, sylar::http::HttpSession::ptr session) { rsp-setStatus(sylar::http::HttpStatus::OK); rsp-setContentType(text/plain); rsp-setBody(Hello Sylar!); return 0; }); // 监听8899端口 auto addr sylar::Address::LookupAny(0.0.0.0:8899); if (!server-bind(addr)) { SYLAR_LOG_ERROR(g_logger) bind failed; return; } server-start(); } int main() { sylar::IOManager ioManager(2); ioManager.schedule(run); return 0; }这段代码虽然简单但背后跑的是IOManager创建2个线程HTTP Server的accept事件注册到epoll每个连接进来框架自动创建协程去处理请求。你不需要写任何线程管理、epoll循环、协议解析代码——这就是sylar这类框架带来的生产力提升。5. 常见问题与排查技巧实录我在学习sylar时踩过的坑5.1 编译与环境坑老版本依赖是真麻烦sylar的代码很重要的一环是第三方依赖。默认情况下它依赖yaml-cpp配置解析、boost部分库、hiredis如果启用Redis模块、mysql-connector数据库模块。我第一次编译时最头疼的就是yaml-cpp版本不兼容老版本代码和最新版头文件有冲突报了一堆模板编译错误。建议编译前先看看CMakeLists.txt里查找了哪些库挨个用包管理器装好。如果编译时报模板错误第一反应不要怀疑编译器而要检查yaml-cpp版本。另外sylar是老代码用较新的GCC比如GCC 11编译可能会遇到一些C标准变更导致的问题。我的经验是优先用GCC 9/10配合C14编译踩坑最少。5.2 协程栈溢出真的会“悄悄”发生有栈协程每创建一个协程默认分配128KB左右的栈空间sylar里可通过参数调整。如果你在协程里定义了一个大数组比如char buf[1MB]就会直接栈溢出程序可能表现为“崩溃”或者“内存损坏”。因为协程栈是mmap出来的溢出的行为不像线程栈那样容易检测。排查技巧sylar里其实提供了一个StackTrace功能在崩溃时打印调用栈编译时需要开启-g并用addr2line解析地址。我在调试时会在Fiber的构造函数里临时加上栈指针打印对比创建时的栈底和运行时的栈指针能快速判定是不是栈溢出。如果你希望偷懒直接把所有协程栈大小调大比如1MB当然这会增加内存开销非法协程数量多时会明显吃内存适合自己的项目需要权衡。5.3 多个线程同时调度同一个协程惊天大坑这是一个我在看IO调度器代码时经常被问到的点。如果在IOManager里一个协程因为读事件被挂起等到fd可读时epoll返回并唤醒它。这个唤醒动作会把协程放入“当前触发事件的线程”的任务队列。但问题是这个协程之前可能是在另一个线程上被挂起的现在换了一个线程来执行它协程的局部变量和栈内容没问题但thread_local变量就完全是另一套了。sylar的处理是要么接受这种跨线程迁移业务代码里不依赖thread_local要么在创建任务时通过FiberAndThread指定线程id把任务固定到某个线程上。我自己实现模块时踩过这个坑我在协程里用了thread_local缓存一个连接池对象结果协程被其他线程调度后拿到的是另一套池出现了“连接丢失”的诡异bug。最后定位到这个问题时真的花了整整一天。5.4 Hook后的Socket行为变化Hook把阻塞调用变成协程友好之后有一个副作用如果你在协程里直接操作一个普通的阻塞fd比如open一个文件read/write不会被Hook仍然是阻塞的。这种阻塞一旦发生会卡住整个调度线程其他协程全部停摆。sylar的解决思路是read/write的Hook只对“非阻塞fd”生效如果fd是阻塞模式Hook逻辑直接调用系统原函数。因此你在使用sylar时如果你自己创建了一个socket但没有设置非阻塞又在协程里调用它IO操作就会阻塞线程。我后来给团队做代码评审时发现很多人不知道这一点拿默认的socket去读数据结果“莫名其妙”线程卡死。避坑指南凡是需要在sylar框架里用的fd请确保基于sylar的Socket类创建它会自动设置非阻塞。如果你需要操作原始fd记得手动fcntl(fd, F_SETFL, O_NONBLOCK)。5.5 线上排查load飙高但线程数不变这类问题其实就是“协程死循环或长时间占用调度线程”的表现。由于协程是协作式调度一个协程如果不主动yield其他协程是抢不到执行机会的哪怕你有8个线程只要8个线程里各有一个协程在死循环整个进程就“假死”了。排查思路分两步第一看CPU占用如果某几个线程CPU跑到100%再用gdbattach上去看线程栈基本就能定位到是哪个协程/哪段代码在死循环。第二看是不是某个阻塞调用没有走Hook比如你在协程里调用了std::thread::sleep_for或者recv在阻塞fd上。学会用sylar::Fiber::GetThis()-dump()打印当前协程的调用栈在调试阶段非常有帮助。我从几次线上问题里总结出的一个原则是在协程里任何可能阻塞的操作都要三思而后行。能用框架封装函数的地方就不要用原生API拿不准某个调用是否被Hook时就直接查hook列表sylar的hook函数清单都在hook.cpp里列得明明白白。6. 上手实践如何用sylar快速搭一个自己的测试服务6.1 推荐的动手顺序先改配置再写业务光看不练永远掌握不了框架的精髓。我的建议是拿到sylar源码后不要急着去跑它的所有示例而是先完成下面这四个亲手操作每一步都结合前面的知识点去思考编译并运行自带的HttpServer示例。用curl访问一下再开多个终端同时请求感受一下并发能力。此时你应该能隐约感知到“IO调度”的存在。修改日志配置文件。sylar支持通过YAML配置文件来定义日志输出格式和级别。尝试把日志级别从DEBUG改成INFO、把输出目标从标准输出改成文件观察变化。这一步能帮你熟悉它的Config模块和日志模块。给HttpServer增加一个带复杂业务逻辑的Servlet。比如做一个带URL参数解析的接口里面模拟一个复杂的计算。用ab或者wrk压一下观察IO调度是否正常。如果卡住就按上一节的思路去排查。自己写一个基于sylar的TCP EchoServer。不依赖HTTP层直接拿Socket类收发数据。这能帮你强化对Socket、ByteArray、协程调度的理解。这里我特别推荐一个调试工具组合ab命令压测HTTP、gdbattach抓栈、strace跟踪系统调用。strace在Hook场景下尤其好用如果你看到一个线程长时间阻塞在read调用上但它是个非阻塞fd那就说明Hook机制没有生效基本可以断定是fd没有正确设置为非阻塞。6.2 从sylar抽离协程模块给自己写一个迷你框架学完sylar的协程调度后我强烈建议你做一个“抽离实验”把Fiber、Scheduler、IOManager这三个核心模块从sylar中摘出来去掉日志、配置、HTTP等依赖组成一个只包含协程调度的迷你库然后写几个示例程序去调度协程。这个过程看起来像是“重复造轮子”但实际上收获巨大。为什么这样说因为当你在sylar完整框架里读代码时会被大量的模块依赖关系干扰而抽离出来后你就能静下心来理解每一个类的职责、每一个变量的生命周期。我的做法是先把fiber.h/fiber.cpp、scheduler.h/scheduler.cpp、iomanager.h/iomanager.cpp、hook.h/hook.cpp、mutex.h/mutex.cpp、thread.h/thread.cpp、log.h/log.cpp、util.h/util.cpp拷贝到新工程。把所有宏定义和依赖关系理清必要时把部分日志输出简化成printf。编译跑通一个简单的协程测试创建10个协程每个协程打印自己的ID后yield让调度器轮流执行它们。当你看到10个协程在2个线程来回切换却输出有序时那种“我理解了框架核心”的感觉比背十遍八股文都来得踏实。这个迷你框架还有很大的扩展空间。比如你可以试着给IOManager增加一个timerfd来实现定时器或者实现一个简单的Hook只替换sleep函数让协程在sleep时不阻塞线程。这些改造加进去后你会发现自己已经拥有写出一个“迷你版sylar”的能力到这一步sylar对你来说就不再是一个黑盒了。我个人在实际操作中的体会是学习框架最忌一口吃成胖子也不建议逐行读代码。更好的方式是“问题驱动”先给自己一个问题比如“我如何让一个socket在协程里非阻塞地读数据”然后带着问题去sylar里找答案。这样读代码是有靶心的而不是像翻字典一样看过就忘。sylar这套代码我前前后后看了三遍每一遍的收获都完全不同第一遍搞懂结构第二遍理解设计意图第三遍已经能指出里面某些老化代码并尝试给出优化思路。如果你打算走服务端开发这条路相信我花在sylar上的时间永远不会亏。
返回列表