上下文简介
首先要理解boost::context
的概念和应用场景。程序在执行的时候,当前的执行的代码环境和所处的状态,就是context。boost::context
保留了当前程序执行时的寄存器等的状态,可以认为是一个上下文A;然后线程就可以去执行其他的代码,完成执行后,可以切回上下文A,并从当初切走的地方执行,而且当初的上下文的现场不变,这就是完成了一次真正意义上的上下文切换。
上线文切换,在协程和用户态线程等有重要的意义(统称它们为routine),我们可以启动一定数量的操作系统的线程,然后让routine在OS的thread中切换,切换时仅需要保留有关的上下文即可,通过一定的调度方式,是的OS的Thread不停的执行这些routine。routine的切换速度,远远快于thread,可极大提高效率。
boost::context提供了上下文的抽象,并给了两种方式,fiber
和call/cc
的方式保留和执行上下文切换。分别介绍两种模式。
fiber的方式切换上下文
fiber
是指,在fiber
之后的代码片段,比如:
boost::context::fiber f{// your code}
其中your code
就是fiber
的内容,是一个特殊格式的函数。当执行上下文时,会进行有关的调用。
int main() {namespace ctx = boost::context;int genNum{0};bool stop{false};// 初始化fiber,sink是启动该上下文的调用者的fiber,该实例中是指main的上下文ctx::fiber generator{[&genNum, &stop](ctx::fiber &&sink) {while (true) {if (stop) {break;}genNum = (genNum + 1) * 2;sink = std::move(sink).resume(); // 保留当前routine的状态,切回到调用者的上下文状态,resume表示执行}return std::move(sink); // 必须返回调用者的上下文状态}};genNum = 1;for (int i = 0; i < 10; ++i) {generator = std::move(generator).resume(); // 切换到generator的上下文并执行std::cout << genNum << " ";}std::cout << std::endl;return 0;}
编译:
g++ main.cpp -o main -g -std=c++17 -lpthread -lboost_fiber -lboost_context && ./main
最终输出:
4 10 22 46 94 190 382 766 1534 3070
注意几个关键点:
routine的调用,是在一个线程中的,因此同一个线程中的routine,不可以出现任何线程锁。fiber
只有移动语义,没有构造语义,resume()
和resume_with()
函数只能右值调用,因此需要使用std::move
把左值转换成右值。使用右值,是为了表示恢复fiber
之后,原来的fiber
就无效了。
给出一个更加经典的生产者和消费者的例子,代码示例:
void pro_con(int N) {namespace ctx = boost::context;std::queue<int> buffer;bool stop = false; // 消除IDE报警用// 生产者ctx::fiber producer{[&buffer, &stop](ctx::fiber &&sink) {int cargo = 0;for (;;) {if (stop) {break;}buffer.push(cargo);std::cout << "producer push " << cargo << std::endl;++cargo;sink = std::move(sink).resume(); // 移交控制权}return std::move(sink);}};// 消费者ctx::fiber consumer{[&buffer, &stop](ctx::fiber &&sink) {for (;;) {if (stop) {break;}int n = buffer.front();buffer.pop();std::cout << "consumer pop " << n << std::endl;sink = std::move(sink).resume(); // 移交控制权}return std::move(sink);}};for (int i = 0; i < N; ++i) {producer = std::move(producer).resume();consumer = std::move(consumer).resume();}}int main() {pro_con(10);return 0;}
resume_with
可以在resume
之前,添加一个新的上下文,比如上面消费者的代码改成:
// 消费者ctx::fiber consumer{[&buffer, &stop](ctx::fiber &&sink) {for (;;) {if (stop) {break;}int n = buffer.front();buffer.pop();std::cout << "consumer pop " << n << std::endl;sink = std::move(sink).resume_with([](ctx::fiber &&f) {std::cout << "====" << std::endl;return std::move(f);}); // 移交控制权}return std::move(sink);}};
这样,会输出如下的形式:
producer push 0consumer pop 0====producer push 1consumer pop 1====
callcc方式切换上下文
该方式和fiber
的调用基本一致,这里给出代码实例即可:
int main() {namespace ctx=boost::context;int a;bool stop = false;ctx::continuation source = ctx::callcc([&a, &stop](ctx::continuation &&sink) {a = 0;int b = 1;for (;;) {if (stop) {break;}sink = sink.resume();int next = a + b;a = b;b = next;}return std::move(sink);});for (int j = 0; j < 10; ++j) {std::cout << a << " ";source = source.resume();}return 0;}