【翻译】Seastar 迷你教程

教程翻译自Seastar官方文档:https://github.com/scylladb/seastar/blob/master/doc/mini-tutorial.md

转载请注明出处:https://www.cnblogs.com/morningli/p/15920456.html

futures 和 promises

future 是现在还不可用的计算的结构,比如:

  • 正在从网络读取的数据缓冲区(data buffer)
  • 定时器到期的事件
  • 完成写磁盘的操作
  • 其他未来的值的计算结果

promise是一个带有future的对象或函数,并期望它会填充future

promisefuture 简化了异步编程,因为它们解耦了事件生产者(promise)和事件消费者(使用future的逻辑)。无论promise是在future填充之前还是之后被消费,都不会对代码的结果有影响。

消费 future

您通过使用其then()方法来使用future,并为其提供回调(通常是lambda)。例如,考虑以下操作:

future<int> get();   // 承诺最终将产生一个 int 
future<> put(int)    // 承诺存储一个 int

void f() {
	get().then([] (int value) {
		put(value + 1).then([] {
			std::cout << "value stored successfully\n";
		});
	});
}

在这里,我们启动一个get()操作,请求当它完成时,一个 put()操作将被安排一个递增的值。我们还要求当put()完成时,一些文本会被打印出来。

链式future

如果then() lambda 返回一个future(称为 x),那么then() 将返回一个将接收相同值的future(称为 y)。这消除了嵌套 lambda 块的需要;例如上面的代码可以重写为:

future<int> get();   // 承诺最终将产生一个 int
future<> put(int)    // 承诺存储一个 int

void f() {
	get().then([] (int value) {
		return put(value + 1);
	}).then([] {
		std::cout << "value stored successfully\n";
	});
}

循环

循环是通过尾调用实现的;例如:

future<int> get();   // 承诺最终将产生一个 int
future<> put(int)    // 承诺存储一个 int

future<> loop_to(int end) {
	if (value == end) {
		return make_ready_future<>();
	}
	get().then([end] (int value) {
		return put(value + 1);
	}).then([end] {
		return loop_to(end);
	});
}

make_ready_future ()函数返回一个已经可用的future —— 对应于循环终止条件,不需要进一步的I/O。

揭秘

当上面的循环运行时,两个then方法调用都会立即执行 —— 但不执行主体。会发生以下情况:

  1. get()被调用,启动 I/O 操作,并分配一个临时结构(称为f1)。
  2. 第一个then()调用将其主体链接到f1并分配另一个临时结构f2.
  3. 第二个then()调用将其主体链接到f2.
  4. 同样,所有这些都立即运行,无需等待任何东西。

在完成发起的 I/O 操作get()后,它会调用存储在f1中的 continuation,调用它并释放f1continuation调用put(),它启动执行存储所需的 I/O 操作,并分配一个临时对象f12,并将一些粘合代码链接到它。

由完成发起的 I/O 操作put()后,它调用与f12关联的continuation,只是告诉它调用与关联的continuation f2。这个continuation只是调用 loop_to(). f12f2都被释放。loop_to()然后调用 get(),这将重新开始该过程,分配新版本f1f2

 

 

 

处理异常

如果.then()子句抛出异常,调度程序将捕获它并取消任何依赖.then()子句。如果要捕获异常,请.then_wrapped()在末尾添加一个子句:

future<buffer> receive();
request parse(buffer buf);
future<response> process(request req);
future<> send(response resp);

void f() {
	receive().then([] (buffer buf) {
		return process(parse(std::move(buf));
	}).then([] (response resp) {
		return send(std::move(resp));
	}).then([] {
		f();
	}).then_wrapped([] (auto&& f) {
		try {
			f.get();
		} catch (std::exception& e) {
			// 你的处理程序放在这里
		}
	});
}

以前的future作为参数传递给 lambda,它的值可以用f.get()查询. 当get()变量作为函数调用时,它将重新引发中止处理的异常,然后您可以应用任何需要的错误处理。它本质上是下面代码的一种转变:

buffer receive();
request parse(buffer buf);
response process(request req);
void send(response resp);

void f() {
	try {
		while (true) {
			auto req = parse(receive());
			auto resp = process(std::move(req));
			send(std::move(resp));
		}
	} catch (std::exception& e) {
		// 你的处理程序放在这里
	}
}	

但是请注意,无论.then_wrapped()是否发生异常,都会安排该子句。因此,仅仅.then_wrapped()执行的事实并不意味着抛出了异常。只有执行 catch 块才能保证这一点。

如下所示:

future<my_type> receive();

void f() {
	receive().then_wrapped([] (future<my_type> f) {
		try {
			my_type x = f.get();
			return do_something(x);
		} catch (std::exception& e) {
			// 你的处理程序放在这里
		}
	});
}

设置说明

SeaStar 是一个高性能框架,默认情况下经过调整以获得最佳性能。因此,我们倾向于轮询与中断驱动。我们的假设是为 SeaStar 编写的应用程序将忙于处理 100,000 IOPS 及以上。轮询意味着我们的每个核心都将消耗 100% 的 cpu,即使没有给它任何工作。

posted @ 2022-02-21 20:10  morningli  阅读(415)  评论(0编辑  收藏  举报