Workflow是搜狗近日开源的一款C++后端异步引擎,性能强劲。本文介绍Workflow的一项重要理念:Task。
一切皆是Task
在Workflow的编程范式中,一切可以独立完成某项任务的、边界清晰明确的代码模块,都可以抽象为一个Task。 Task之间可以互相组合,从而可以由简单Task构建出复杂Task。 从Client-Server的角度来看,无论是作为客户端还是服务器,Task理念都能很好地抽象实际情况。
Client请求是Task
使用Workflow作为Client,十分方便。 例如,创建一个HTTP请求,可以这么写:
std::function<void(WFHttpTask *)> http_callback;
auto *task = WFTaskFactory::create_http_task("http://www.sogou.com", 1, 3, http_callback);
task->start();
这条语句创建了一个对http://www.sogou.com
的请求,允许1
次重定向,最多可以重试3
次,通信结束后自动调用callback即http_callback
。 callback结束后Task执行完毕,自动销毁。 这个请求被包装为一个Task返回给用户,用户具体的操作都可以在这个Task上进行。
除了WFHttpTask
,Workflow还内置了WFRedisTask
、WFMySQLTask
、WFKafkaTask
等网络Task,以及可以用来创建线程任务的WFThreadTask
、创建定时器的WFTimerTask
、创建类似goroutine的WFGoTask
、用于协调同步的WFCounterTask
和构建有向无环图的WFGraphTask
等丰富多彩的Task。 这些Task大多可以通过工厂类WFTaskFactory
的静态方法创建。
Server处理对象也是Task
上面的程序片段已经展示了如何发起一个HTTP请求,下面的程序说明如何构建一个HTTP Server。
void process(WFHttpTask *task)
{
task->get_resp()->append_output_body("<p>Hello</p>");
}
WFHttpServer server(process);
server.start(8080);
Workflow的理念是一切皆是Task,作为Server,也可以用Task的方式实现。 server.start(8080)
之后,当有客户端对8080端口发起HTTP请求时, Workflow会生成一个Task,并调用void process(WFHttpTask *)
来处理这个Task。 因此,用户只要在process
中实现服务逻辑,即可构建出一个服务器。
复杂的服务器逻辑,很有可能需要Client Task的帮助, 比如一个代理服务器,收到Client的请求之后,需要向真实的Server发出请求,并将应答回复给Client, 这种Task之间的组合,正是Workflow擅长的地方。
Task间的相互组合
从Task的角度来看,重要的事情有两件:一是这个Task做什么,二是这个Task做完之后下一个Task是谁。 对于后一个问题,Workflow提供了多种Task间的组合方式。
串并联
auto *parallel = Workflow::create_parallel_work(nullptr);
auto *t1 = WFTaskFactory::create_http_task("http://www.sogou.com", 1, 3, nullptr);
auto *t2 = WFTaskFactory::create_go_task("A", some_function_A);
auto *t3 = WFTaskFactory::create_go_task("B", some_function_B;
auto *t4 = WFTaskFactory::create_redis_task("redis://127.0.0.1:6379/1", 3, nullptr);
parallel->add_series(Workflow::create_series_work(t2, nullptr));
parallel->add_series(Workflow::create_series_work(t3, nullptr));
auto *series = Workflow::create_series_work(t1, nullptr);
series->push_back(parallel);
series->push_back(t4);
series->start();
上面的代码,实现了下图中的先后次序。 当t1
的callback执行结束时,t2
和t3
将会开始执行;当t2
和t3
的callback都执行结束时,t4
将会开始执行。
|--> t2 ---|
--> t1 ---| |--> t4 ---
|--> t3 ---|
这种串并联机制是十分朴素自然的组织方式,可以应对大多数简单的场景。 SeriesWork
由Task串联组成,ParallelWork
由SeriesWork
并联组成,它们都可以设置callback。 同时,Workflow对于SereisWork和ParallelWork进行了一定的约束:
- SeriesWork由若干Task串联而成,任何运行中的Task都属于某个SeriesWork;
- ParallelWork由若干SeriesWork并联而成;
- ParallelWork是一种Task。
之所以有此约束,是为了防止滥用串并联而带来的复杂性。
动态地串并联
串并联除了可以静态地组装,也可以动态地在callback中修改。 例如:
auto *t1 = WFTaskFactory::create_http_task("http://www.sogou.com", 1, 3, [](WFHttpTask * task){
auto *t2 = WFTaskFactory::create_go_task("", [](){});
auto *series = series_of(task);
series->push_back(t2);
});
t1->start();
由于Task必然是某个SeriesWork的一环(即使没有显式创建此SeriesWork),因此可以在Task的callback中拿到这个SeriesWork,向其添加新的Task。 需要注意的是,无法通过类似的方法向ParallelWork中添加新的SeriesWork,因为: 第一,并非所有的SeriesWork都是某个ParallelWork的一支; 第二,ParallelWork会同时启动它包含的所有SeriesWork,之后再次试图添加,是违反语义的。
总结
Workflow的Task理念是一种完备的编程方法,巧妙地将各种异步过程结合起来,方便用户使用。 Workflow还有其它有趣的特色,后面会继续介绍。 在Workflow的主页上,也有十分详细的进一步的文档介绍,欢迎各位访问。