54用d编程纤程

纤程允许执行一个线程完成多个任务.与线程相比,切换更有效,类似协程(更小)与绿色线程.
纤程允许每个线程有多个调用栈.要掌握纤程,必须了解线程的调用栈.
参数,局部变量,返回值,函数的临时表达式,及其他执行时的额外信息组成了函数的本地状态
运行时调用函数时自动分配和初化函数的本地状态.
为函数调用分配的局部存储空间叫栈桢(桢),随着函数调用其他函数,一帧一帧的,当前活动的函数调用是线程的调用栈.

void main() {
    int a;int b;
    int c=foo(a, b);
}
int foo(int x, int y) {
    bar(x + y);return 42;
}

void bar(int param) {
    string[] arr;
    // ...
}

会有三级栈帧.
递归函数来说,栈帧的优势更明显.
递归极大的简化了分而治之的函数.

import std.array;

int sum(int[] arr, int currentSum = 0) {
    if (arr.empty) {
        //不添加元素,已计算的结果
        return currentSum;
    }
    //用剩余元素加当前元素
    return sum(arr[1..$], currentSum + arr.front);
}

void main() {
    assert(sum([1, 2, 3]) == 6);
}

std.algorithm.sum用特殊算法为浮点计算更精确的计算.
当递归函数返回自己时,编译器用尾调用优化,为每个递归调用消除调用栈.
多线程时,每个线程拥有自己的线程栈来维护自己的执行状态
纤程强大之处在于,虽然不是线程,但有自己的调用栈,允许一个线程中有多个调用栈.一个调用栈保持一个任务的执行状态,从而允许一个线程执行多个任务.

void fiberFunction() {
    // ...
}

纤程从可调用实体开始(函数指针,λ函数)不带参数也不返回.

import core.thread;
// ...
auto fiber=new Fiber(&fiberFunction);

core.thread.Fiber+可调用实体,可创建纤程.

class MyFiber : Fiber {//继承
    this() {
        super(&run);//传递纤程函数
    }
    void run() {
        // ...
    }
}
//...
auto fiber = new MyFiber();

fiber.call();启动和恢复纤程.
不像线程,当纤程执行时,调用者就停止执行了.两个都是同一线程,所以必然的.

void fiberFunction() {
    // ...
        Fiber.yield();
    // ...
}

Fiber.yield(),将执行权交给调用者.
纤程产生后,调用者就恢复了.也有可能由纤程转到另一个纤程,毕竟多个调用栈嘛,想往哪个栈转就往哪个栈转.

    if (fiber.state == Fiber.State.TERM) {
        // ...
    }

状态由纤程的.state属性决定.
Fiber.State有以下值:
HOLD,暂停,可启动/恢复,
EXEC,执行,正在执行.
TERM,终止,再次使用前必须调用reset(),
区间实现的纤程,要记住状态,

struct FibonacciSeries {
    int current = 0;int next = 1;//两个状态

    enum empty = false;

    @property int front() const {
        return current;
    }

    void popFront() {
        const nextNext = current + next;
        current = next;
        next = nextNext;
    }
}

但有些区间不易保存状态,但用递归却很容易保存状态.
如以下插入与打印的递归实现不定义变量,与树中包含的元素独立,插入通过insertOrSet间接递归.

import std.stdio;
import std.string;
import std.conv;
import std.random;
import std.range;
import std.algorithm;

struct Node {
//表示二叉树的节点,在树实现中使用,不应直接用
    int element;
    Node * left;     // 左子树
    Node * right;    // 右子树

    void insert(int element) {
        if (element < this.element) {//小往左
            insertOrSet(left, element);
        }else if(element>this.element){//大往右
            insertOrSet(right, element);
        } else {
            throw new Exception(format("已存在%s", element));
        }
    }

    void print() const {//先打印左子树元素
        if (left) {
            left.print();write(' ');
        }

        write(element);//打印当前

        if (right) {//打印右子树
            write(' ');right.print();
        }
    }
}

//插入指定右子树,可能的话初始化其节点
void insertOrSet(ref Node * node, int element) {
    if (!node) {
        node=new Node(element);//子树的第一个节点
    } else {
        node.insert(element);
    }
}

struct Tree {
//实际树表示,允许树根`无效`的空树
    Node * root;

    void insert(int element) {
        insertOrSet(root, element);
    }//插元素到树

    void print() const {
        if (root) {
            root.print();
        }
    }//有序打印
}

//从10乘n个数中取随机数来填充树
Tree makeRandomTree(size_t n) {
    auto numbers = iota((n * 10).to!int).randomSample(n, Random(unpredictableSeed)).array;
//取数

    randomShuffle(numbers);//洗牌
    auto tree = Tree();
    numbers.each!(e => tree.insert(e));
    //用这些数来填充树
    return tree;
}

void main() {
    auto tree = makeRandomTree(10);
    tree.print();
}

randomSample,从区间中不改变顺序随机抽取元素,
eachmap类似.但map生成新元素,each有副作用(可能是覆盖).map是懒的,each是激进的.
std.range.iota,懒生成元素们(比如区间).
randomShuffle,洗牌,随机移动.
提供区间接口.以便使用现有算法.

struct InOrderRange {
        //???
    }

    InOrderRange opSlice() const {
        return InOrderRange(root);
    }

虽然打印基本实现了,按序访问元素.
但不容易为树实现输入区间,这里不实现了,但你可以研究下树的迭代器(一些实现要求额外的节点 *指向每个节点的父)
递归算法的print很普通的原因是自动管理调用栈,调用栈隐式包含当前元素信息,还包含此时到达的执行程序(如该左/右节点),

  void print() const {
        if (left) {
            left.print();
            write(' ');   //调用栈包含该谁了
        }

        // ...
    }

纤程在类似使用调用栈比显式维护状态更容易的时候有用.
为了简单,我们包含常见纤程操作,实现树区间.

import core.thread;

//生成元素的纤程函数,设置`ref`参数
void fibonacciSeries(ref int current) {
    //用参数使当前元素同纤程通信.也可为`out`
    current = 0;//注意是参数
    int next = 1;

    while (true) {
        Fiber.yield();//下个调用从此开始/恢复
        //当前元素可用时,停止
        const nextNext = current + next;
        current = next;
        next = nextNext;
    }
}

void main() {
    int current;
    Fiber fiber = new Fiber(() => fibonacciSeries(current));//纤程函数不带参,不能直接用
    //用无参闭包(适配器)传给纤程函数,

    foreach (_; 0 .. 10) {
        fiber.call();//启动和恢复
        import std.stdio;
        writef("%s ", current);
    }//纤程就是这样的(一个线程被分成多个纤程).
}

std.concurrency.Generator,把纤程当区间展示.
未提供区间接口,与现有算法不兼容.
修改引用参数展示元素与复制元素到调用者上下文比不理想
通过成员函数显式构造使用纤程,暴露低级实现细节
std.concurrency.Generator解决以上问题

import std.stdio;
import std.range;
import std.concurrency;

alias FiberRange = std.concurrency.Generator;//避免冲突名字

void fibonacciSeries() {
    int current = 0;int next = 1;

    while (true) {
        yield(current);
        const nextNext = current + next;
        current = next;
        next = nextNext;
    }
}

void main() {
    auto series = new FiberRange!int(&fibonacciSeries);
//生成的是区间
    writefln("%(%s %)", series.take(10));
}

不是用return返回单元素,而是用yield返回多元素,
用的是std.concurrency.yield,而不是Fiber.yield

import std.concurrency;

alias FiberRange = std.concurrency.Generator;

struct Node {
// ...
    auto opSlice() const {
        return byNode(&this);
    }//已删打印函数
}

//按序产生下个树节点的纤程函数
void nextNode(const(Node) * node) {
    if (!node) return;//无节点

    nextNode(node.left);    // 下个左节点
    yield(node);            // 下个中节点
    nextNode(node.right);   // 下个右节点
}

auto byNode(const(Node) * node) {
//返回到树的输入区间
    return new FiberRange!(const(Node)*)(() => nextNode(node));
}

// ...

struct Tree {
// ...

    auto opSlice() const {//已删打印
        return byNode(this).map!(n => n.element);
    }//节点=>元素的转换
}

//返回节点树的输入区间,如根为空,则为空区间
auto byNode(const(Tree) tree) {
    if (tree.root) {
        return byNode(tree.root);

    } else {
        alias RangeType = typeof(return);
        return new RangeType(() {});//空区间
    }
}

用生成器,可容易的按输入区间展示树的元素.
特别注意,如何通过递归结点nextNode按适配器实现byNode节点.
现在可对树切片了.

   writefln("%(%s %)", tree[]);

异步输入出中的纤程
纤程的调用栈可简化异步输入出任务.

import std.stdio;
import std.string;
import std.format;
import std.exception;
import std.conv;
import std.array;
import core.thread;

struct User {
    string name;
    string email;
    uint age;
}

class SignOnFlow : Fiber {
//用户登录流
    string inputData_;//本流最近数据

    string name;
    string email;
    uint age;//信息

    this() {
        super(&run);//函数启动点
    }

    void run() {
        name = inputData_;
        Fiber.yield();

        email = inputData_;
        Fiber.yield();

        age = inputData_.to!uint;
        //有信息,可以返回了,而不再产生了
        //纤程状态变为`Fiber.State.TERM`
    }

    @property void inputData(string data) {
        inputData_ = data;
    }//从调用者接收数据

    @property User user() const {
        return User(name, email, age);
    }//构造用户并返回
}

//为特定流展示从输入读取的数据
struct FlowData {
    size_t id;
    string data;
}

//解析相关流数据
FlowData parseFlowData(string line) {
    size_t id;
    string data;

    const items = line.formattedRead!" %s %s"(id, data);
    enforce(items == 2, format("Bad input '%s'.", line));

    return FlowData(id, data);
}

void main() {
    User[] users;
    SignOnFlow[] flows;

    bool done = false;

    while (!done) {
        write("> ");
        string line = readln.strip;

        switch (line) {
        case "hi":
            //从新连接开始流
            flows ~= new SignOnFlow();

            writefln("Flow %s started.", flows.length - 1);
            break;

        case "bye"://退出程序
            done = true;
            break;

        default://按流数据用输入
            try {
                auto user = handleFlowData(line, flows);

                if (!user.name.empty) {
                    users ~= user;
                    writefln("Added user '%s'.", user.name);
                }

            } catch (Exception exc) {
                writefln("Error: %s", exc.msg);
            }
            break;
        }
    }

    writeln("Goodbye.");
    writefln("Users:\n%(  %s\n%)", users);
}

User handleFlowData(string line, SignOnFlow[] flows) {
//标识输入的所有者纤程,设置其输入数据并重用纤程
//如完成流,返回带有效字段用户
    const input = parseFlowData(line);
    const id = input.id;

    enforce(id < flows.length, format("Invalid id: %s.", id));

    auto flow = flows[id];

    enforce(flow.state == Fiber.State.HOLD,
            format("Flow %s is not runnable.", id));

    flow.inputData = input.data;//置流数据

    flow.call();//恢复流

    User user;

    if (flow.state == Fiber.State.TERM) {
        writefln("Flow %s has completed.", id);
        user = flow.user;//置返回数据为新创建用户
        //待办:未来可为新流重用流数组中的本纤程项
        //但首先必须用`flow.reset()`重置.
    }

    return user;
}

从输入读流,解析,分发流数据至适当流来处理它,流的调用栈自动保存流状态.当用户信息完整时,加新用户.
运行是普通函数,当其他流登录时,没有阻塞.
vibe.d使用类似设计.
异常和纤程.
可利用调用栈实现异常机制.
由于抛出异常,而离开函数清理栈局部变量的行为叫栈展开.由于异常,先后一个个的清理帧.
纤程有自己的栈,执行纤程时抛出的异常展开纤程而不是调用者的栈.如未抓异常(未处理),则终止纤程,纤程的状态变为Fiber.State.TERM.
但有时需要将错误条件转给调用者,而不丢失自身状态.Fiber.yieldAndThrow允许纤程产生并在调用者自身上下文中抛异常.
age = inputData.to!uint ;
类似这样

while(true){
    try {
        age = inputData_.to!uint;
        break;  // 成功转换
    } catch (ConvException exc) {
        Fiber.yieldAndThrow(exc);//相当于重试
    }//抛异常
}

操作系统的线程是不可预测的,抢占式.而纤程则是协作式.可显式暂停,调用者也可显式恢复.从一个线程到另一个线程的切换是很慢的.
而一个线程多个纤程,没有上下文切换,多个调用栈,多次调用与常规函数调用成本一样.(不能同时执行调用者和纤程)
协作式多任务处理,调用者和纤程数据都可能位于cpu缓冲中,比从内存读取快上100倍,进一步提高纤程速度.
调用者与纤程从不同时执行,不存在竞争条件,无需同步数据,但程序员要确保在预期时间产生(yield).如准备好数据时.

void fiberFunction() {
    // ...
        func();           //工作未完成,不能产生
        sharedData *= 2;
        Fiber.yield();    //该产生了.
    // ...
}

一个明显缺点就是不能利用多核.可以使用M:N(杂模式)来利用多核,都可以去研究研究(不同线程模型).
调用栈可有效简化递归算法.
纤程为每个线程启用多个调用栈,模型就是一个调用栈多个纤程,但不同时执行.
纤程自身暂停产生->调用者,调用者->调用,运行纤程.
Generator把纤程当作输入区间.
纤程简化了严重依赖调用栈的程序及异步输入出操作,
纤程,协作式多任务处理.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值