【问题标题】:Continuations/Coroutines/Generators in C++ / gcc / linuxC ++ / gcc / linux中的延续/协程/生成器
【发布时间】:2012-02-19 19:25:40
【问题描述】:

背景:我正试图通过提出这个玩具问题来弄清楚如何实现延续/协程/生成器(无论以下称为什么)。环境是 gcc 4.6 和 linux 3.0 x86_64 上的 C++11。不可移植很好,但不允许使用外部库(boost.coroutine、COROUTINE 等)。我认为longjmp(3) 和/或makecontext(2) 和朋友可能会有所帮助,但不确定。

说明:

下面的玩具解析器应该解析等长的 as 和 bs 序列。即

((a+)(b+))+

使得第二个括号产生的长度等于第三个。

当它找到一个产生式(例如 aaabbb)时,它会输出它找到的 as 的数量(例如 3 个)。

代码:

#include <stdlib.h>
#include <iostream>
using namespace std;

const char* s;

void yield()
{
        // TODO: no data, return from produce
        abort();
}

void advance()
{
        s++;
        if (*s == 0)
                yield();
}

void consume()
{
        while (true)
        {
                int i = 0;

                while (*s == 'a')
                {
                        i++;
                        advance();
                }

                cout << i << " ";

                while (i-- > 0)
                {
                    if (*s != 'b')
                        abort();
                    advance();
                }
        }
}

void produce(const char* s_)
{
        s = s_;

        // TODO: data available, continue into consume()
        consume();
}

int main()
{
        produce("aaab");
        produce("bba");
        produce("baa");
        produce("aabbb");
        produce("b");

        // should print: 3 1 4

        return 0;
}

问题:

如您所见,consume 调用堆栈的状态必须在调用 yield 时保存,然后 produce 返回。当再次调用produce 时,必须通过从yield 返回来重新启动consume。挑战在于修改produce 调用consume 的方式,并实现yield 以便它们按预期运行。

(显然重新实现消费以便保存和重建其状态违背了练习的目的。)

我认为需要做的是类似于 makecontext 手册页底部的示例:http://www.kernel.org/doc/man-pages/online/pages/man3/makecontext.3.html,但不清楚如何将其转换为这个问题。 (我需要睡觉)

解决方案:

(感谢 Chris Dodd 的设计)

#include <stdlib.h>
#include <iostream>
#include <ucontext.h>
using namespace std;

const char* s;
ucontext_t main_context, consume_context;

void yield()
{
    swapcontext(&consume_context, &main_context);
}

void advance()
{
    s++;
    if (*s == 0)
            yield();
}

void consume()
{
    while (true)
    {
            int i = 0;

            while (*s == 'a')
            {
                    i++;
                    advance();
            }

            cout << i << " ";

            while (i-- > 0)
            {
                    advance();
            }
    }
}

void produce(const char* s_)
{
    s = s_;

    swapcontext(&main_context, &consume_context);
}

int main()
{
    char consume_stack[4096];

    getcontext(&consume_context);
    consume_context.uc_stack.ss_sp = consume_stack;
    consume_context.uc_stack.ss_size = sizeof(consume_stack);
    makecontext(&consume_context, consume, 0);

    produce("aaab");
    produce("bba");
    produce("baa");
    produce("aabbb");
    produce("b");

    // should print: 3 1 4

    return 0;
}

【问题讨论】:

  • 您的意思是longjmp?我不知道任何拼写为 longjump 的函数。
  • makecontext 已弃用 iirc。
  • 您为什么认为 makecontext 已被弃用?手册页上没有说明吗?
  • 哦,这里是:“SUSv2,POSIX.1-2001。POSIX.1-2008 删除了 makecontext() 和 swap-context() 的规范,引用了可移植性问题,并建议应用程序是改写为使用 POSIX 线程。”虽然 POSIX 线程是抢占式的,创建了一个全新的克隆进程,但我认为用户态协作线程对于此类问题的性能会更高。

标签: c++ linux gcc c++11 coroutine


【解决方案1】:

为此使用 makecontext/swapcontext 相当简单——您使用 makecontext 创建一个新的协程上下文并使用 swapcontext 在它们之间进行交换。在您的情况下,您需要一个额外的协程来运行 consume 无限循环,并且您运行 main 并在 main 上下文中生成。

所以main 应该调用 getcontext+makecontext 来创建一个新的上下文来运行消费循环:

getcontext(&consume_ctxt);
// set up stack in consume_context
makecontext(&consume_ctxt, consume, 0);

然后produce会切换到它而不是直接调用consume

void produce(const char* s_)
{
    s = s_; 
    swapcontext(&main_ctxt, &consume_ctxt);
}

最后yield 只是调用swapcontext(&amp;consume_ctxt, &amp;main_ctxt); 切换回主上下文(将在produce 中继续并立即返回)。

注意,由于consume是一个无限循环,所以你不必太担心它返回时会发生什么(所以链接永远不会被使用)

【讨论】:

    猜你喜欢
    • 2019-02-12
    • 2020-06-26
    • 2015-12-11
    • 1970-01-01
    • 2018-03-27
    • 2017-09-02
    • 2017-06-04
    相关资源
    最近更新 更多