STATE THREADS 回调终结者

State Threads(ST)是一个轻量级的网络应用框架,源于Netscape的MSPR项目,用于高性能可扩展服务器领域。与传统的事件驱动状态机(EDSM)相比,ST通过多线程编程范式提供更简单的编程模型,减少了回调的复杂性。ST的核心是将每个连接抽象为线程,使用事件驱动调度,避免了回调的线性思维分解。在多核环境下,ST通过虚拟处理器(vp)实现线程的并发处理,同时避免了资源同步问题。然而,ST的限制在于所有I/O操作必须使用其API,且调试相对困难。
摘要由CSDN通过智能技术生成

(感谢网友 @我的上铺叫路遥 投稿)

上回写了篇《一个“蝇量级”C语言协程库》,推荐了一下Protothreads,通过coroutine模拟了用户级别的multi-threading模型,虽然本身足够“轻”,杜绝了系统开销,但这个库本身应用场合主要是内存限制的嵌入式领域,提供原生态组件太少,使用限制太多,比如依赖其它调用产生阻塞等。

这回又替大家在开源界淘了个宝,推荐一个轻量级网络应用框架State Threads(以下简称ST),总共也就3000行C代码,跟Protothreads不同在于ST针对的就是高性能可扩展服务器领域(值得一提的是Protothreads官网参考链接上第一条就是ST的官网)。在其FAQ页面上一句引用”Perfection is achieved not when there is nothing more to add, but rather when there is nothing more to take away.”可以视为开发人员对ST源码质量的自信。

历史渊源

首先介绍一下这个库的历史渊源,从代码贡献者来看,ST不是个人作品,而是有着雄厚的商业支持和应用背景,比如服务器领域,在这里你可以看到ST曾作为Apache的多核应用模块发布。其诞生最初是由网景(Netscape)公司的MSPR(Netscape Portable Runtime library)项目中剥离出来,后由SGI(Silicon Graphic Inc)还有Yahoo!公司(前者是主力)开发维护的独立线程库。历史版本方面,作为SourceForge上开源项目,由2001年发布v1.0以来一直到2009年v1.9稳定版后未再变动。在平台移植方面,从Makefile的配置选项中可知ST支持多种Unix-like平台,还有专门针对Win32的源码改写。源码例子中,提供了web server、proxy以及dns三种编程实例供参考。可以说代码质量应该是相当的稳定和可靠的。

至于许可证方面,有必要略作说明。出于历史原因,网景最初发布时选择了MPL1.1许可证,而后SGI在维护中又混进了GPLv2许可证,照理说这两种许可证是互不兼容的(MPL1.1后续版本是GPL兼容的),也就是说用双许可证打包发布理论上是非法无效的,见GNU官网上MPL兼容性一节。但这里有值得商榷的地方,因为文中又提及,根据MPL1.1中某条款第13节,如果整段或部分代码允许采用另一许可证作为备用(alternate)选择,比如GPL及其兼容,那么整个库的许可证就可视为GPL兼容的。如此一来所谓GPL兼容性一般解释为你不能在GPLv2的代码中混入MPL1.1,而不是说你不能在MPL1.1代码中混入GPLv2,也就是说GPLv2在MPL1.1之后是可以接受的,事实上SGI就采用了后面的做法,尚未引起版权上的纠纷。为此我还考证了一下FAQ上license一节的说法,说ST既可以在MPL和GPL之间选择一种,也可以继续用双许可证,还补了一句在non-free项目使用上也没有限制,但对ST源码所做改动必须对用户可见。在源码文件中的SGI的附加声明还解释了将ST转为GPL代码的做法,就是可以删除前面MPL的声明,否则后续用户仍可以在两者之间二选一。个人觉得既然SGI都这样发话了,那么可解释为反之删除GPL的声明继续采用MPL也是可以接受的,如果你对双许可证承诺仍不放心的话。

基于事件驱动状态机(EDSM)

好了,下面该进入技术性话题了。前面说了ST的目标是高性能可扩展,其技术特征一言以蔽之就是

“It combines the simplicity of the multi-threaded programming paradigm, in which one thread supports each simultaneous connection, with the performance and scalability of an event-driven state machine (EDSM) architecture.”

我们先来纵向比较ST与传统的EDSM区别,再来横向比较与其它线程库(比如Pthread)的区别(注:以下图片全部来自State Threads Library FAQ)。

传统EDSM最常见的方式就是I/O事件的异步回调。基本上都会有一个叫做dispatcher的单线程主循环(又叫event loop),用户通过向dispatcher注册回调函数(又叫event handler)来实现异步通知,从而不必在原地空耗资源干等,在dispatcher主循环中通过select()/poll()系统调用来等待各种I/O事件的发生,当内核检测到事件触发并且数据可达或可用时,select()/poll()会返回从而使dispatcher调用相应的回调函数来对处理用户的请求。所以异步回调与其说是通知,不如说用委托更恰当。

插播福利

1.近期整理了20G资源,包含产品/运营/测试/程序员/市场等,互联网从业者【工作必备干货技巧、行业专业书籍、面试真题宝典等】,获取方式:

  • 微信扫码关注公众号“非典型互联网”,转发文章到朋友圈,截图发至公众号后台,即可获取干货资源链接;

2.互联网人交流群:

  • 关注公众号“非典型互联网”,在公众号后台回复“入群”,人脉共享,一起交流;

整个过程都是单线程的。这种处理本质上就是将一堆互不相交(disjoint)的回调实现同步控制,就像串联在一个顺序链表上。见图1,黑色的双箭头表示I/O事件复用,回调是个筐,里面装着对各种请求的处理(当然不是每个请求都有回调,一个请求也可以对应不同的回调),每个回调被串联起来由dispatcher激活。这里请求等价于thread的概念(不是操作系统的线程),只不过“上下文切换”(context switch)发生在每个回调结束之时(假设不同请求对应不同回调),注册下一个回调以待事件触发时恢复其它请求的处理。至于dispatcher的执行状态(execute state)可作为回调函数的参数保存和传递。

EDSM

异步回调的缺陷在于难以实现和扩展,虽然已经有libevent这样的通用库,以及其它actor/reacotor的设计模式及其框架,但正如Dean Gaudet(Apache开发者)所说:“其内在的复杂性——将线性思维分解成一堆回调的负担(breaking up linear thought into a bucketload of callbacks)——仍然存在”。从上图可见,回调之间请求例程不是连续的,比如回调之间的切换会打断部分请求,又比如有新的请求需要重新注册。

ST本质上仍然是基于EDSM模型,但旨在取代传统的异步回调方式。ST将请求抽象为thread概念以更接近自然编程模式(所谓的linear thought吧,就像操作系统的线程之间切换那样自然)。ST的调度器(scheduler)对于用户来说是透明的,不像dispatcher那种将执行状态(execute state)暴露给回调方式。每个thread的现场环境可以保存在栈上(一段连续的大小确定的内存空间),由C的运行环境管理。从图2看到,ST的threads可以并发地线性地处理I/O事件,模型比异步回调简单得多。

State Threads

这里稍微解释一下ST调度工作原理,ST运行环境维护了四种队列,分别是IOQ、RUNQ、SLEEPQ以及ZOMBIEQ,当每个thread处于不同队列中对应不同的状态(ST顾名思义所谓thread状态机)。比如polling请求的时候,当前thread就加入IOQ表示等待事件(如果有timeout同时会被放到SLEEPQ中),当事件触发时,thread就从IOQ(如果有timeout同时会从SLEEPQ)移除并转移到RUNQ等待被调度,成为当前的running thread,相当于操作系统的就绪队列,跟传统EDSM对应起来就是注册回调以及激活回调。再比如模拟同步控制wait/sleep/lock的时候,当前thread会被放入SLEEPQ,直到被唤醒或者超时再次进入RUNQ以待调度。

ST的调度具备性能与内存双重优点:在性能上,ST实现自己的setjmp/longjmp来模拟调度,无任何系统开销,并且context(就是jmp_buf)针对不同平台和架构用底层语言实现的,可移植性媲美libc。下面放一段代码解释一下调度实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++多线程回调可以通过线程池技术实现。具体实现思路如下: 1. 创建一个线程池,线程池中包含多个线程,这些线程都是可重用的资源。 2. 在主线程中创建一个任务队列,将需要异步处理的任务加入到任务队列中。 3. 线程池中的线程会不断地从任务队列中取出任务进行处理。 4. 当任务处理完成后,线程会将处理结果通过回调函数返回给主线程。 下面是一个简单的C++多线程回调的例子: ```cpp #include <iostream> #include <thread> #include <mutex> #include <condition_variable> #include <queue> #include <functional> using namespace std; class ThreadPool { public: ThreadPool(int numThreads) : stop(false) { for (int i = 0; i < numThreads; ++i) { threads.emplace_back( [this] { for (;;) { function<void()> task; { unique_lock<mutex> lock(this->queue_mutex); this->condition.wait(lock, [this] { return this->stop || !this->tasks.empty(); }); if (this->stop && this->tasks.empty()) return; task = move(this->tasks.front()); this->tasks.pop(); } task(); } } ); } } template<class F> void enqueue(F&& f) { { unique_lock<mutex> lock(queue_mutex); tasks.emplace(forward<F>(f)); } condition.notify_one(); } ~ThreadPool() { { unique_lock<mutex> lock(queue_mutex); stop = true; } condition.notify_all(); for (thread& worker : threads) worker.join(); } private: vector<thread> threads; queue<function<void()>> tasks; mutex queue_mutex; condition_variable condition; bool stop; }; void asyncTask(int x, int y, function<void(int)> callback) { int result = x + y; callback(result); } int main() { ThreadPool pool(4); pool.enqueue([] { cout << "Task 1" << endl; }); pool.enqueue([] { cout << "Task 2" << endl; }); pool.enqueue([] { cout << "Task 3" << endl; }); pool.enqueue([] { cout << "Task 4" << endl; }); pool.enqueue([] { cout << "Task 5" << endl; }); pool.enqueue([] { asyncTask(1, 2, [](int result) { cout << "Result: " << result << endl; }); }); return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值