windoze 发表于 2014-03-21 16:09

开源项目Fiberized.IO召唤测试

本帖最后由 windoze 于 2015-08-31 23:19 编辑

如果你习惯于写每连接一个线程的程序,那试试这个吧 Fiberized.IO
你可以继续使用原来的模型,只需要把thread换成fiber,并使用库里面提供的stream类处理I/O,程序内部会使用一个m:n的green thread(fiber):kernel thread的映射,把blocking I/O的API替换成底层asynchronous I/O,并发能力和吞吐量都会大幅度提高。
这个库同时还提供C++11兼容的mutex/condition_variable实现,可以用于fiber之间的同步。

软件的文档在这里

2015年8月31日更新
昨天灵机一动,想到了一个简单的办法支持HTTP/1.1 Chunked encoding,本来想歪了以为要大动干戈,所以一直没做这个功能,找对了方法只花了一晚上就搞定了。
现在可以说Fiberized.IO的HTTP/1.1协议部分已经完整了,正在啃HTTP/2,看看有没有什么办法在不伤筋动骨的前提下把它也搞定。

2015年8月30日更新
加入了WebSocket服务端支持,一个简单的WebSocket echo例子在这里
暂时没空管Windows,所以新加入的与HTTP相关的功能还是不能用……

2015年5月28日更新
好吧我还是低估了VC的脑残程度,fibio的基础部分是可用的,但是HTTP部分由于我滥用C++11黑魔法,导致VC连续给我二十几个会心一击。
目前HTTP高地的战斗还在继续……
话说微软什么时候才能推出一个真正的C++编译器啊,现在这个C艹编译器实在是受够了!!!{:yct77:}

2015年5月27日更新
项目到今天已经有一年多的历史了,今天Fiberized.IO终于可以在VC2015RC下编译并通过基本测试,这一切都是在至今也没有人给我赞助Windows机器的情况下进行的,有好心人愿意给我捐款不?
然而用CMake生成VC2015RC的工程文件似乎还是有问题,所以我手工创建了一组工程文件,正在检查看有没有绝对路径什么的,如果一切正常将在近期上传至GitHub。
PS. 有人知道怎么创建一个NuGet包吗?网上的教程似乎不是地球人写的,每个字都认得但连在一起什么意思居然不知道?!
PPS. 如果有人打算折腾C++11/14,敬请远离VC,血淋淋的教训呐……

12月2日更新:
支持Redis PUBSUB(终于……)和scan
加入了一个简单的Mustache模板,做HTTP server更简单了。
修了几个bug :P

11月12日更新
加入Apache Thrift支持,可以使用Fiberized.IO的架构实现Thrift客户端和服务端,比自带的TThreadedServer/TThreadPoolServer/TNonblockingServer更好、更容易的支持大并发。

11月更新
(比较)完整的HTTP服务端框架,支持HTTPS,加入了request routing和基本的cookie/请求参数解析,现在可以拿来写点Servlet干的事了 ;-)
加入了一个URL client,支持HTTP和HTTPS,支持自动重定向跳转。
加入了一个基本的redis客户端,(几乎)支持所有的redis命令。

7月更新:
Fiber专用同步对象如mutex、condition_variable等的内部状态改用spinlock保护,在多CPU机器上可以提高一些效率。

6月更新:
完整支持C++11 future/promise,Boost.ASIO集成,重写了timeout实现,改正了之前实现中的race condition。

3月28日更新:
清理了io部分代码结构,加入SSL支持

3月24日更新:
在Linux下打开segment stack,Fiberized.IO测试程序已经成功创建1M个fiber,妈妈再也不用担心连接数太多了 :D
test_http_server性能测试,在loopback上用"ab -k -c 100 -n 100000 http://localhost:23456/",qps超过200k (需要将test_http_server.cpp:128行注释掉,否则程序会自动退出)

完整的echo_server例子源代码在这里

pandaiam 发表于 2014-03-21 16:55

厉害啊,
看不懂的路过。

windoze 发表于 2014-03-21 17:12

本帖最后由 windoze 于 2014-03-21 17:14 编辑

简单的说就是这样,假定你有一个blocking的tcp_stream类,一个echo server大概会是这个样子:
void servant(tcp_stream s) {
    while(!s.eof()) {
      // 从socket stream中读入一行
      std::string line;
      std::getline(s, line);
      // 向socket中写入一行,std::endl会调用stream.flush(),确保数据都被发送出去
      s << line << std::endl;
    }
}

int main_entry(int argc, char *argv[]) {
    // acceptor本质上就是一个listen socket
    io::tcp::acceptor acc=io::listen(12345);
    while(1) {
      //blocking accept调用,返回一个connected socket,假定它有一个简单的basic_iostream兼容包装叫tcp_stream
      tcp_stream stream(io::accept(acc));
      // 创建一个工作线程处理该socket,detach说明这个thread不需要被join
      std::thread(servant, std::move(stream)).detach();
    }
    return 0;
}

int main(int argc, char *argv[]) {
    std::thread(main_entry, argc, argv).join();
    return 0;
}
以上是一个极简单的1 connection per thread的echo server,只要并发连接数不多(比如只有几个或十几个,不超过CPU物理核数),它能很好的工作,效率远非任何非阻塞/异步模型可比。
但上面的程序也有致命问题,如果并发连接数太多,比如C10K甚至C100K的时候,CPU和OS无法有效的调度每个线程,甚至无法创建这么多的线程,导致程序效率骤降甚至崩溃。
Fiberized.IO的解决方案是采用coroutine/green thread/fiber(随便你叫什么,反正差不多),将10K甚至100K的fiber映射到一组固定尺寸的线程池上,在用户态进行调度,从程序角度看,它依然适用blocking I/O的模型,但在底层,所有blocking I/O的操作其实都是一个Async I/O的操作,通过适当的处理fiber状态并切换fiber context,保证每个工作线程的利用率最大化,减少线程争用,提高吞吐量和并发能力。
上面的程序很容易修改成fiber based,只需将std::thread换成fibio::fiber,并将main函数改成这样即可:
#include <fibio/fiber.hpp>
#include <fibio/stream/iostream.hpp>
using namespace fibio;

//之前的程序片段
...

// 4是线程池尺寸,你可以用std::thread::hardware_concurrency()代替以创建和CPU物理核数一致的线程池
int main(int argc, char *argv[]) {
    return fiberize(4, main_entry, argc, argv);
}

OwnWaterloo 发表于 2014-03-21 20:13

回复 1# windoze

请教一下coroutine的实现方式?
boost,pth等程序库?Fibter/ucontext等平台特定的api?还是直接汇编地干活?

另外。。。 C++11已经很流行了么。。。

OwnWaterloo 发表于 2014-03-21 20:50

本帖最后由 OwnWaterloo 于 2014-03-21 20:50 编辑

回复 1# windoze

啊,我翻到了。。。
fiber_object.hpp (22)
#include <boost/coroutine/coroutine.hpp>
fiber_object.hpp (73)
      typedef boost::coroutines::coroutine<after_step_handler_t>::pull_type runner_t;
      typedef boost::coroutines::coroutine<after_step_handler_t>::push_type caller_t;
继续请教。
如果用stackful的话会不会有移植性方面的问题?
而如果只用stackless会不会有一些限制? 比如不能从嵌套的函数里跳出或者重新进入后局部变量不能还原?

windoze 发表于 2014-03-21 22:34

回复 5# OwnWaterloo

Boost.context/Boost.coroutine可用于不少系统,\兼容性不是大问题,目前支持x86/x64/arm/mips/sparc/sparc64/ppc/ppc64上的Solaris/Linux/FreeBSD/Mac/Windows,基本涵盖了所有常见的系统,经实测在使用ELF的HaikuOS上也能用(虽然要hack一下makefile)。
stackful coroutine最大的好处就是和其它程序/库的兼容性好,因为在切换context之后,任何一个函数都无法“感觉到”新的context有什么不同,即使对于使用sjlj的变态库,只要小心点,也可以完全兼容。当然,使用stackful coroutine也要付出一些性能上的代价,但整体来说处于可接受范围内。

stackless看上去很美,但实际使用的时候会有很多问题。
一般认为stackless会有一些性能上的优势,但实际使用中你会发现stackless coroutine使用类似于CPS的方式串起整个执行链,所有“局部”变量都需要保存在一个执行链上每个块都可以访问到的区域,以C的实际情况而言,这个区域只能在堆上,而过度使用堆会导致cache局部性大打折扣。
另外一个巨大的问题就是错误处理,C++内置的异常处理机制完全不可用,每个pseudo blocking操作要么使用返回值处理异常(类似于node.js的错误处理),要么hack一点,在每个块外捕获异常,随CPS stack frame传递至下一个块重新抛出,直到被用户提供的异常捕获代码处理为止。

在开始这个项目之前,我曾经仔细考虑过这两者,最后还是选择了stackful coroutine,简单、兼容性好、性能损失很小,有这几点就足够了。

cokeboL 发表于 2014-03-22 15:30

不明觉厉,路过帮顶

linux_c_py_php 发表于 2014-03-22 19:03

高大上,C++技术千秋万载。

windoze 发表于 2014-03-22 19:15

回复 8# linux_c_py_php

晕,我该把你这话当赞美么……

既然你冒泡了,有几个问题想问你一下
1. 之前你搞过一个epoll+lua的东西,有什么办法可以快速创建一个lua state并且包含一组module么?
2. 在那个程序里,读写超时你是怎么处理的?

linux_c_py_php 发表于 2014-03-23 13:57

windoze 发表于 2014-03-22 19:15 static/image/common/back.gif
回复 8# linux_c_py_php

晕,我该把你这话当赞美么……


额, 必须是赞美啊。

1,我那个是单线程程序,就一个lua state,其他都是启的coroutine。。应该不会频繁加载module吧...
2,那个demo没实现lua的读写api。。。实现的话肯定是在c层跑个timer做超时啦。
页: [1] 2 3 4 5
查看完整版本: 开源项目Fiberized.IO召唤测试