Files
workflow/docs/tutorial-06-parallel_wget.md
2021-05-16 19:04:39 +08:00

128 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 一个简单的并行抓取parallel_wget
# 示例代码
[tutorial-06-parallel_wget.cc](/tutorial/tutorial-06-parallel_wget.cc)
# 关于parallel_wget
这是我们第一个并行任务的示例。
程序从命令行读入多个http URL以空格分割并行抓取这些URL并按照输入顺序将抓取结果打印到标准输出。
# 创建并行任务
之前的示例里我们已经接触过了SeriesWork类。
* SeriesWork由任务构成代表一系列任务的串行执行。所有任务结束则这个series结束。
* 与SeriesWork对应的ParallelWork类parallel由series构成代表若干个series的并行执行。所有series结束则这个parallel结束。
* ParallelWork是一种任务。
根据上述的定义,我们就可以动态或静态的生成任意复杂的工作流了。
Workflow类里有两个接口用于产生并行任务
~~~cpp
class Workflow
{
...
public:
static ParallelWork *
create_parallel_work(parallel_callback_t callback);
static ParallelWork *
create_parallel_work(SeriesWork *const all_series[], size_t n,
parallel_callback_t callback);
...
};
~~~
第一个接口创建一个空的并行任务第二个接口用一个series数组创建并行任务。
无论用哪个接口产生的并行任务在启动之前都可以用ParallelWork的add_series()接口添加series。
在示例代码里我们创建一个空的并行任务并逐个添加series。
~~~cpp
int main(int argc, char *argv[])
{
ParallelWork *pwork = Workflow::create_parallel_work(callback);
SeriesWork *series;
WFHttpTask *task;
HttpRequest *req;
tutorial_series_context *ctx;
int i;
for (i = 1; i < argc; i++)
{
std::string url(argv[i]);
...
task = WFTaskFactory::create_http_task(url, REDIRECT_MAX, RETRY_MAX,
[](WFHttpTask *task)
{
// store resp to ctx.
});
req = task->get_req();
// add some headers.
...
ctx = new tutorial_series_context;
ctx->url = std::move(url);
series = Workflow::create_series_work(task, nullptr);
series->set_context(ctx);
pwork->add_series(series);
}
...
}
~~~
从代码中看到我们先创建http任务但http任务并不能直接加入到并行任务里需要先用它创建一个series。
每个series都带有context用于保存url和抓取结果。相关的方法我们在之前的示例里都介绍过。
# 保存和使用抓取结果
http任务的callback是一个简单的lambda函数把抓取结果保存在自己的series context里以便并行任务获取。
~~~cpp
task = WFTaskFactory::create_http_task(url, REDIRECT_MAX, RETRY_MAX,
[](WFHttpTask *task)
{
tutorial_series_context *ctx =
(tutorial_series_context *)series_of(task)->get_context();
ctx->state = task->get_state();
ctx->error = task->get_error();
ctx->resp = std::move(*task->get_resp());
});
~~~
这个做法是必须的因为http任务在callback之后就会被回收我们只能把resp通过std::move()操作移走。
而在并行任务的callback里我们可以很方便的获得结果
~~~cpp
void callback(const ParallelWork *pwork)
{
tutorial_series_context *ctx;
const void *body;
size_t size;
size_t i;
for (i = 0; i < pwork->size(); i++)
{
ctx = (tutorial_series_context *)pwork->series_at(i)->get_context();
printf("%s\n", ctx->url.c_str());
if (ctx->state == WFT_STATE_SUCCESS)
{
ctx->resp.get_parsed_body(&body, &size);
printf("%zu%s\n", size, ctx->resp.is_chunked() ? " chunked" : "");
fwrite(body, 1, size, stdout);
printf("\n");
}
else
printf("ERROR! state = %d, error = %d\n", ctx->state, ctx->error);
delete ctx;
}
}
~~~
在这里我们看到ParallelWork的两个新接口size()和series_at(i)分别获得它的并行series个数和第i个并行series。
通过series->get_context()取到对应series的上下文打印结果。打印顺序必然和我们放入顺序一致。
在这个示例中,并行任务执行完就没有其它工作了。
我们上面说过ParallelWork是一种任务所以同样我们可以用series_of()获得它所在的series并添加新任务。
但是如果新任务还要使用到抓取结果我们需要再次用std::move()把数据移到并行任务所在series的上下文里。
# 并行任务启动
并行任务是一种任务所以并行任务的启动并没有什么特别可以直接调用start()也可以用它建立或启动一个series。
在这个示例里我们启动一个series在这个series的callback里唤醒主进程正常退出程序。
我们也可以在并行任务的callback里唤醒主进程程序行为上区别不大。但在series callback里唤醒更加规范一点。