51用d编程并行

std.parallelism程序在多核中并行运算.
仅当并行操作真正独立时,才使用这些算法.
parallel,并行访问区间元素
task,并行执行的任务,
asyncBuf,半激进并行迭代输入区间元素
map,用输入区间元素半激进并行调用函数
amap,用随机访问区间元素全激进并行调用函数
reduce,通过随机访问区间元素并行计算

void main(){
    auto students =
        [ Student(1), Student(2), Student(3), Student(4) ];

    foreach (student; students) {
        student.aSlowOperation();
    }//如果每个学生操作相互独立,则可以并行计算
}

import std.stdio;
import core.thread;

struct Student {
    int number;

    void aSlowOperation() {
        writefln("The work on student %s has begun", number);

        // 等待来模拟长时操作
        Thread.sleep(1.seconds);

        writefln("The work on student %s has ended", number);
    }
}

有多少核:

import std.stdio;
import std.parallelism;

void main() {
    writefln("系统有%s个核.", totalCPUs);
}

taskPool.parallel()
parallel()并行访问区间的元素.
仅这样,就可以并行执行了:

import std.parallelism;
// ...
    foreach (student; parallel(students)) {
//多核同时并行运行

parallel返回一个知道如何对每个元素分发闭包执行给多个核.
程序可同时包含多个主动执行线程.操作系统启动并执行u并调度核心上的每个线程.
如果多个对象是独立的,则可以并行.即无序执行.
支持依赖其他线程的线程编程模型,叫并发.
= parallel(range, work_unit_size = 100);

    foreach (student; parallel(students, 2)) {
        // ...
    }

有时,循环操作短,这时一个线程执行多个元素会比较快(减少成本),工作单元决定线程每次操作几个元素.循环内容快内容少,最好就多点.不浪费线程.一般为100,基本上合适.
非随机访问的,会序化.
parallel()工作在asyncBuf() 和 map()上时,忽略工作单元大小数,而是重用结果区间的内部缓冲区.
同程序的其他操作并行执行的叫任务.如std.parallelism.Task.
parallel为每个工作线程构造一个新任务并自动启动它,parallel再等待所有线程完成任务后才退出.因为它自动构造,启动,等待任务,所以,很有用.
当任务与区间不相关或不能由区间表示时,程序员可显式处理他们.构造task() , 启动executeInNewThread() , 执行yieldForce()
执行以下代码anOperation两次,打印id的第一个字母来表明哪个任务在干活.
stdout不直接输出,而是放入输出缓冲区中,直到完成一行才输出,写不输出换行符,在到达行尾前调用stdout.flush()来输出数据.

import std.stdio;
import std.parallelism;
import std.array;
import core.thread;

//每半秒打印首字母,返回1来模拟计算,在主中使用结果.
int anOperation(string id, int duration) {
    writefln("%s 将花%s 秒", id, duration);

    foreach (i; 0 .. (duration * 2)) {
        Thread.sleep(500.msecs);  /* 半秒*/
        write(id.front);stdout.flush();
    }

    return 1;
}

void main() {
    //构造执行操作的任务,把指定函数参数传递给任务函数
    auto theTask = task!anOperation("theTask", 5);
    theTask.executeInNewThread();//启动
    //任务执行时,再直接在主调用`aa..`,
    immutable result = anOperation("main's call", 3);

    //此时完成操作,常规函数操作发起的
    //不确定完成`任务`,等待操作,返回值为结果

    immutable taskResult = theTask.yieldForce();
    writeln();
    writefln("完成了;结果是%s.",result + taskResult);
}

上面任务函数作为任务的参数task!anOperation

import std.parallelism;

double foo(int i) {
    return i * 1.5;
}

double bar(int i) {
    return i * 2.5;
}

void main() {
    auto tasks = [ task!foo(1),
                   task!bar(2) ];    // 编译错误
}

类型不一样,这时应该用类似c++的函数<双精(整)>.
下面也是一种,task的重载接受函数地址作参数,避免模板实例化

import std.parallelism;

double foo(int i){
     return i * 1.5;
}
double bar(int i){
     return i * 2.5;
}
void main(){
     auto tasks=[任务(&foo,1),任务(&bar,2)];//编译
}//加个地址

这样也可以,task接个闭包(λ函数)

    auto theTask = task((int value) {
                            /* ... */
                        }, 42);

由不同线程执行任务时,任务自动抓异常.要重抛的话,调用任务yieldForce函数,使主线程能够抓异常.

import std.stdio;
import std.parallelism;
import core.thread;

void mayThrow() {
    writeln("启动mayThrow() ");
    Thread.sleep(1.seconds);
    writeln("mayThrow()抛异常了");
    throw new Exception("错误消息");
}

void main() {
    auto theTask = task!mayThrow();
    theTask.executeInNewThread();

    writeln("继续main");
    Thread.sleep(3.seconds);

    writeln("main在等待任务");
    theTask.yieldForce();
}

任务抛出的未抓异常,只停止任务,而主程序未停止.

    try {
        theTask.yieldForce();

    } catch (Exception exc) {
        writefln("Detected an error in the task: '%s'", exc.msg);
    }

试-抓块中调用yieldForce,可抓任务抛出的异常.这与单线程不一样,单线程抓可能抛的代码的异常.
并行时,它包装yieldForce.
任务的成员函数:

    if (theTask.done) {//done,任务是否完成
        writeln("是,已完成任务");

    } else {
        writeln("不,还在进行");
    }

done,是否完成,如任务被异常终止时重抛异常.
executeInNewThread,在新线程执行任务,
executeInNewThread(int priority),以特定优先级(操作系统)在新线程执行任务,
有三个函数等待任务完成.
yieldForce,未启动,则启动;已完成,返回值;在运行,则等待;抛异常,则重抛;
spinForce,如在运行,则使微控器忙,其余与上一样.
workForce,在等待任务完成时,开启新任务,其余与第1个一样
一般,第1个就可以了,如果想尽快完成任务,用第2个,第3个用于启动其他任务而不是暂停当前任务.
其他看标准库的Task.
taskPool.asyncBuf()
类似parallel,asyncBuf并行迭代输入区间,
他将区间产生的元素放在缓冲,并转给用户.
其实都是折腾,一开始就不用区间,行不行?
它一波一波的将区间元素转成缓冲,等到都消费完了,再来一波.说是折腾不为过

auto elements = taskPool.asyncBuf(range, buffer_size);

参数为:一个源区间,一个缓冲大小(一个波次大小),

import std.stdio;
import core.thread;

struct Range {
    int limit;
    int i;

    bool empty() const @property {
        return i >= limit;
    }

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

    void popFront() {
        writefln("在%s后产生元素", i);
        Thread.sleep(500.msecs);
        ++i;
    }
}

void main() {
    auto range = Range(10);

    foreach (element; range) {
        writefln("用元素%s", element);
        Thread.sleep(500.msecs);
    }
}

用半秒迭代,半秒处理的区间,只生成指定数的缓冲.
元素是懒生产并使用的.

import std.parallelism;
//...
    foreach(element;taskPool.asyncBuf(range,2)){

并行.也可在外部使用

    auto range = Range(10);
    auto asyncRange = taskPool.asyncBuf(range, 2);
//作为输入区间
    writeln(asyncRange.front);

taskPool.map()
如果无论怎样都要调用且每个操作相互独立,就比并行慢得很了.
std.parallelism中的taskPool.map()和taskPool.amap()在多核时多种情况下都要快些.

import std.stdio;
import std.algorithm;
import core.thread;

struct Student {
    int number;
    int[] grades;

    double averageGrade() @property {
        writefln("在%s学生上执行",number);
        Thread.sleep(1.seconds);
        const average =grades.sum / grades.length;
        writefln("在%s学生上执行完", number);
        return average;
    }
}

void main() {
    Student[] students;

    foreach (i; 0 .. 10) {/* 每个学生两个得分*/
        students ~= Student(i, [80 + i, 90 + i]);
    }
    auto results = map!(a => a.averageGrade)(students);
    //函数/λ作模板参数,区间作函数参数
    foreach (result; results) {
        writeln(result);
    }
}

taskPool.map()半激进执行程序,并把结果放在缓冲区供执行,缓冲大小由第2个参数决定.

import std.parallelism;
// ...
double averageGrade(Student student) {
    return student.averageGrade;
}
// ...
    auto results = taskPool.map!averageGrade(students, 3);

同样,函数为模板参数,区间为函数参数.加个缓冲大小.需要自由函数,
auto results =taskPool.map!(a => a.averageGrade)(students, 3);,这样是编译不过的.

 ...= taskPool.map!func(range, buffer_size = 100,work_unit_size = size_t.max);

缓冲大小与工作单元大小,注意区别.
taskPool.amap()
与上面的区别是:1,全激进.2,与同随机访问区间工作.

  auto results = taskPool.amap!averageGrade(students);

amap()map()快的原因是,前面的用大把的内存来存储结果,花内存得速度.
auto results = taskPool.amap!averageGrade(students, 2);
第2个参数为工作单元数.第2个参数放存储位置(随机访问区间).

    double[] results;
    results.length = students.length;
    taskPool.amap!averageGrade(students, 2, results);

taskPool.reduce()类似fold,化简/折叠.区别是,他们的函数参数是相反的.因而,对非并行代码,推荐fold,在区间表达式中可利用统一调用.
reduce是许多函数式语言的常见的高阶算法.以1/多个函数为模板参数,以初始值作为结果,用结果的当前值和区间中的每个元素来调用函数,当没有初始值时,用区间的第一个元素.就是个递归.

import std.stdio;
import std.algorithm;

void main() {
    writeln(reduce!((a, b) => a + b * b)(0, [5, 10]));
}
r=i;循环(区间元素e)r=f(r,e);中 r;

因而多任务版taskPool.reduce()出现了.可以利用多核.

import std.stdio;
import std.algorithm;
import core.thread;

int aCalculation(int result, int element) {
    writefln("started  - element: %s, result: %s",
             element, result);

    Thread.sleep(1.seconds);
    result += element;

    writefln("finished - element: %s, result: %s",
             element, result);

    return result;
}

void main() {
    writeln("Result: ", reduce!aCalculation(0, [1, 2, 3, 4]));
}

替换:

import std.parallelism;
// ...
    writeln("Result: ", taskPool.reduce!aCalculation(0, [1, 2, 3, 4]));

应用非常受限.参数类型与返回类型要相同,
同时,如果必须按顺序计算,则多核并没有什么好处.在本程序中并行版本的更慢.
关键是把程序设计成独立的,无依赖的.
还可以带多个函数作模板参数

import std.stdio;
import std.algorithm;
import std.conv;

double quarterOf(double value) {
    return value / 4;
}

string tenTimes(double value) {
    return to!string(value * 10);
}

void main() {
    auto values = [10, 42, 100];
    auto results = map!(quarterOf, tenTimes)(values);

    writefln(" Quarters  Ten Times");

    foreach (quarterResult, tenTimesResult; results) {
        writefln("%8.2f%8s", quarterResult, tenTimesResult);
    }
}

结果用元组表示.一个结果一个位置.
并行算法容器用的是taskPool.

import std.stdio;
import std.parallelism;

void main() {
    auto workers = new TaskPool(2);

    foreach (i; workers.parallel([1, 2, 3, 4])) {
        writefln("Working on %s", i);
    }//调用`并行`.

    workers.finish();//完成任务时停止.
}

任务池,包含一堆线程,默认比多核数少1.

发布了440 篇原创文章 · 获赞 29 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/fqbqrr/article/details/104590155