Dlang 并行化
Dlang 并行化
好难受,dlang 生态太差,没办法,学了半天才明白。
我尽量以精炼的语言解释。
采用 定义,例子(代码),解释 的步骤讲解。
所以你可能看到很多代码,一点解释……
我会省略一些
import
,让代码短一些
parallelism
并行
感觉好废物,这一小部分了解即可。
这部分只需要会
parallel
和map & amap
其实就差不多了。
介绍比较实用的几种方法。
parallel
迭代
foreach (i; parallel(range, work_uint_size = 100)) {
// do something here
}
其中 work_unit_size
表示最多同时运行的数量。
例子:
import std.stdio, std.parallelism;
import core.thread;
struct Producer {
void produce() {
Thread.sleep(1.seconds);
writeln("Process +1");
}
};
void main() {
auto prods = new Producer[](10);
foreach (prod; parallel(prods)) {
prod.produce();
}
}
Task
创建任务:
auto theTask = task!anOperation(arguments);
// or
auto theTask = task(&someFunction, parameters...)
运行任务:theTask.executeInNewThread()
查看是否完成:if (theTask.done) { ... }
获取结果:auto result = theTask.yeildForce()
asyncBuf
感觉没啥用。
并行保存多个需要长时间制作的元素。还需要保证使用的长时间的……
例子:
struct Producer {
int i, total;
bool empty() const {
return total <= i;
}
int front() const {
return i;
}
void popFront() {
writefln("Producing product ID: %d", i);
Thread.sleep(1.seconds / 2);
++i;
}
};
void main() {
auto prods = Producer(0, 10);
foreach (prod; taskPool.asyncBuf(prods, 3)) {
writef("Got product id: %d\n", prod);
Thread.sleep(1.seconds);
writeln("Used product...");
}
}
map & amap
先看例子:
int increase(int x) {
Thread.sleep(500.msecs);
return x + 3;
}
void main() {
int[] nums;
foreach (i; 0 .. 10) {
nums ~= i;
}
// auto results = taskPool.map!increase(nums);
auto results = taskPool.amap!increase(nums);
foreach (result; results) {
writeln(result);
}
}
可以类比 python
中的 map
。
两者的区别:
map
可以指定同时运行的数量,而amap
是有多少运行多少。map
会一定程度上按顺序执行,而amap
并不是顺序执行,它依靠RandomAccessRange
,也就是随机顺序执行。
消息并发
我不知道怎么翻译,反正就是
Message Passing Concurrency
。
核心方法: spawn
(唤起)
我们可以形象的认为,spawn
方法可以唤起一个新的工人(线程)来为我们工作。
并且这个工人与主线程是分开的(先看代码后面解释):
import std.stdio;
import std.concurrency;
import core.thread;
void worker() {
foreach (i; 0 .. 5) {
Thread.sleep(500.msecs);
writeln(i, " (worker) in ", thisTid);
}
}
void main() {
Tid myWorkerTid = spawn(&worker);
foreach (i; 0 .. 5) {
Thread.sleep(300.msecs);
writeln(i, " (main) in ", thisTid);
}
writeln("main is done!");
}
最终输出:
0 (main) in Tid(7f0eb19bc0b0)
0 (worker) in Tid(7f0eb19bc000)
1 (main) in Tid(7f0eb19bc0b0)
2 (main) in Tid(7f0eb19bc0b0)
1 (worker) in Tid(7f0eb19bc000)
3 (main) in Tid(7f0eb19bc0b0)
2 (worker) in Tid(7f0eb19bc000)
4 (main) in Tid(7f0eb19bc0b0)
main is done!
3 (worker) in Tid(7f0eb19bc000)
4 (worker) in Tid(7f0eb19bc000)
实际输出可能略有差异。
解释:
spawn(&worker)
唤起了一个新的线程运行worker
函数,并返回了新的线程的id
是一个结构体Tid
。thisTid
类似于一个宏,用于获取当前所在线程的id
。
发送消息
先看代码后解释:
void worker() {
int value = 0;
while (value >= 0) {
value = receiveOnly!int();
double result = cast(double)value / 7;
ownerTid.send(result);
}
}
void main() {
Tid myWorker = spawn(&worker);
foreach (val; 0 .. 10) {
myWorker.send(val);
double result = receiveOnly!double();
writefln("Send %s got %s", val, result);
}
myWorker.send(-1); // terminate worker process
}
最终输出:
Send 0 got 0
Send 1 got 0.142857
Send 2 got 0.285714
Send 3 got 0.428571
Send 4 got 0.571429
Send 5 got 0.714286
Send 6 got 0.857143
Send 7 got 1
Send 8 got 1.14286
Send 9 got 1.28571
解释:
ownerTid
类似于一个宏,用于取得唤醒自己的线程的Tid
,从而发送消息。Tid.send(...)
可以向Tid
代表的那个线程发送一条消息。如果同时要发送多个东西,在发送的地方是
Tid.send(a, b, c, ...)
。在接受的地方要变化为
receiveOnly!(typeof(a), typeof(b), typeof(c), ...)
,最终得到的是一个tuple
,可以通过下标访问。
receiveOnly!type()
表示只接受类型为type
的消息。最后
myWorker.send(-1)
是根据代码逻辑结束的,并不属于通法。
如果我们需要更灵活的接受方法怎么办?
void workerFunc() {
bool isDone = false;
while (!isDone) {
void intHandler(int message) {
writeln("handling int message: ", message);
if (message == -1) {
writeln("exiting");
isDone = true;
}
}
void stringHandler(string message) {
writeln("handling string message: ", message);
}
receive(&intHandler, &stringHandler);
}
}
我们可以指定多种 Handler
以处理不同的数据类型。利用 receive
注册 到处理类型消息的函数中。
更优雅的方式
处理更多的类型:
struct Exit {}
void worker() {
bool done = false;
while (!done) {
receive(
(int message) {
writeln("int message ", message);
},
(string message) {
writeln("string message", message);
},
(Exit message) {
writeln("Exit message");
done = true;
},
(Variant message) {
writeln("Unexpected message: ", message);
}
);
}
}
void main() {
Tid myWorker = spawn(&worker);
myWorker.send(10);
myWorker.send("hello");
myWorker.send(10.1);
myWorker.send(Exit());
}
主要是使用了匿名函数……
解释:
- 利用
std.variant.Variant
以接收任何类型的数据。但是需要保证,处理所有类型数据的方法应该放在最后面,不然会导致全部被判断成Variant
。
超时接受
我们可以定一个超时时间,超过这个时间就直接返回。
先看代码:
struct Exit {}
void worker() {
bool done = false;
while (!done) {
bool received = receiveTimeout(600.msecs,
(Exit message) {
writeln("Exit message");
done = true;
},
(Variant message) {
writeln("Some message: ", message);
}
);
if (!received) {
writeln("no message yet...");
}
}
}
void main() {
Tid myWorker = spawn(&worker);
myWorker.send(10);
myWorker.send("hello");
Thread.sleep(1.seconds);
myWorker.send(10.1);
myWorker.send(Exit());
}
最终输出:
Some message: 10
Some message: hello
no message yet...
Some message: 10.1
Exit message
解释:
receiveTimeout
只比recieve
多了一个参数,用于指定超时时间。返回一个
bool
变量,如果为false
则没有接收到任何消息。
等待所有线程结束:thread_joinAll()
。
一般来说放在需要放的地方……即可。
数据共享
终于讲到这里了。
我们先考虑一个程序:
import std.stdio;
import std.concurrency;
import core.thread;
int variable;
void printInfo(string message) {
writefln("%s: %s (@%s)", message, variable, &variable);
}
void worker() {
variable = 42;
printInfo("Before the worker is terminated");
}
void main() {
spawn(&worker);
thread_joinAll();
printInfo("After the worker is terminated");
}
其输出是这样的:
Before the worker is terminated: 42 (@7F308C88C530)
After the worker is terminated: 0 (@7F308C98D730)
可以发现,同样的变量在不同的线程里面地址是不一样的,也就是说数据是独立的,所以要有共享。
此时我们只需要修改:
shared int variable;
即可。
实际上写为
shared(int) variable;
会更标准,但是好麻烦……
当然,不得不说,有了消息传递,那么数据共享就是备用的方案了。
Data Race
数据竞争是一个很常见的问题。
例子:
void worker(shared int* i) {
foreach (t; 0 .. 200000) {
*i = *i + 1;
}
}
void main() {
shared int i = 0;
foreach (id; 0 .. 10) {
spawn(&worker, &i);
}
thread_joinAll();
writeln("after i to ", i);
}
期望输出 2000000
,但是实际输出可能远小于此。
所以我们要考虑同步:
void worker(shared int* i) {
foreach (t; 0 .. 200000) {
synchronized {
*i = *i + 1;
}
}
}
解释:
synchronized
会隐式地创建一个锁,保证只有一个线程会持有这个锁,并且执行这些操作。有些时候,
synchronized
会使得因为等待锁的额外开销使得程序变慢。但有些时候,我们可以通过更好的方法避免等待的开销,例如使用原子操作。synchronized
创建的锁只会对于这一个代码块生效,不会影响到其他的代码块。
共用锁
void increase(shared int* i) {
foreach (t; 0 .. 200000) {
synchronized {
*i = *i + 1;
}
}
}
void decrese(shared int* i) {
foreach (t; 0 .. 200000) {
synchronized {
*i = *i - 1;
}
}
}
void main() {
shared int i = 0;
foreach (id; 0 .. 10) {
if (id & 1) spawn(&increase, &i);
else spawn(&decrese, &i);
}
thread_joinAll();
writeln("after i to ", i);
}
期望输出 0
但是实际输出……不知道。所以我们需要共用锁:
synchronized (lock_object) {
// ...
}
修改后的代码:
class Lock {}
shared Lock lock = new Lock();
void increase(shared int* i) {
foreach (t; 0 .. 200000) {
synchronized (lock) {
*i = *i + 1;
}
}
}
void decrese(shared int* i) {
foreach (t; 0 .. 200000) {
synchronized (lock) {
*i = *i - 1;
}
}
}
现在就可以得到正确的答案了。
同步类
我们可以使用 synchronized
修饰一个类。这相当于在每一个代码块里面嵌套一个 synchronzied
:
synchronized class Cls {
void func() {
// ...
}
}
上面的等价于:
class Cls {
void func() {
synchronized (this) {
// ...
}
}
}
同步初始化
我们考虑这份代码:
static this() {
writeln("executing static this()");
}
void worker() {
}
void main() {
spawn(&worker);
thread_joinAll();
}
最终会输出两次 executing static this()
。
如果我们修改为 shared static this() { ... }
,那么最终只会输出一次。
原子操作
需要用到
core.atomic
库。
有代码:
atomic!"+="(var, x);
atomic!"-="(var, x);
// ... like *= /= ^= ...
这些都是原子操作。
有方法:
shared(int) *value;
bool is_mutated = cas(value, currentValue, newValue);
如果返回 true
,那么值会改变,否则没有。
原子操作一般来说快于
synchronized
。同时,原子操作也可以作用于结构体上,这里不作为讲解。
更多操作可以参考标准库:
core.sync.barrier
core.sync.condition
core.sync.config
core.sync.exception
core.sync.mutex
core.sync.rwmutex
core.sync.semaphore
Dlang 并行化的更多相关文章
- Hawk 4.6 并行化
并行化 Hawk支持单机并行化,也就是使用多线程获取数据.它可以控制目前所有任务的数量,为了不给网站造成过大的压力,仅当任务池中的任务数量小于一定值后,才会插入新的任务. 你可以在数据清洗的 执行面板 ...
- Java 8函数编程轻松入门(五)并行化(parallel)
1.并发与并行的区别 并发: 一个时间段内有几个程序都处于已启动到运行完毕之间,且这几个程序都是在同一个处理机上运行.但在任一个时刻点只有一个程序在处理机上运行 并行: 在同一个时刻,多核处理多个任务 ...
- kmeans算法并行化的mpi程序
用c语言写了kmeans算法的串行程序,再用mpi来写并行版的,貌似参照着串行版来写并行版,效果不是很赏心悦目~ 并行化思路: 使用主从模式.由一个节点充当主节点负责数据的划分与分配,其他节点完成本地 ...
- go:多核并行化问题
分别用串行和并行实现了一个NUM次加法的程序,代码如下: package main import ( "fmt" //"runtime" //执行并行段时需要引 ...
- [Java 8] (10) 使用Lambda完成函数组合,Map-Reduce以及并行化
好文推荐!!!!! 原文见:http://blog.csdn.net/dm_vincent/article/details/40856569 Java 8中同时存在面向对象编程(OOP)和函数式编程( ...
- Parallel并行化编程
在很多场景中我们需要通过并行化的方式来提高程序运行的速度,比较典型的需求就是并行下载.前期遇到一个需求是要批量下载瓦片,每次大概下载上百万个瓦片,要想提高瓦片的下载速度,只能通过并行化的方式,下面把我 ...
- Bash脚本实现批量作业并行化
http://jerkwin.github.io/2013/12/14/Bash%E8%84%9A%E6%9C%AC%E5%AE%9E%E7%8E%B0%E6%89%B9%E9%87%8F%E4%BD ...
- Intel® Threading Building Blocks (Intel® TBB) Developer Guide 中文 Parallelizing Data Flow and Dependence Graphs并行化data flow和依赖图
https://www.threadingbuildingblocks.org/docs/help/index.htm Parallelizing Data Flow and Dependency G ...
- 【转】Bash脚本实现批量作业并行化
首先附上自己常用的代码 ---------------------------------------------------------------------------------------- ...
- 梯度下降之随机梯度下降 -minibatch 与并行化方法
问题的引入: 考虑一个典型的有监督机器学习问题,给定m个训练样本S={x(i),y(i)},通过经验风险最小化来得到一组权值w,则现在对于整个训练集待优化目标函数为: 其中为单个训练样本(x(i),y ...
随机推荐
- 在Kubernetes部署GitLab
在Kubernetes部署GitLab 前置条件 已安装Helm工具已部署NFS自动创建PVC 使用HELM安装 [root@k8s-master01 ~]# helm repo add gitlab ...
- 在Kubernetes(k8s)中部署 jenkins
在Kubernetes(k8s)中部署 jenkins YAML配置文件 由于jenkins需要持久化存储,通过nfs动态供给pvc存储卷. 可以参考我之前的文档:https://cloud.tenc ...
- 二进制安装Kubernetes(k8s) v1.24.2 IPv4/IPv6双栈
二进制安装Kubernetes(k8s) v1.24.2 IPv4/IPv6双栈 Kubernetes 开源不易,帮忙点个star,谢谢了 介绍 kubernetes二进制安装 强烈建议在Github ...
- 在k8s(kubernetes) 上安装 ingress V1.1.0
Ingress 公开了从集群外部到集群内服务的 HTTP 和 HTTPS 路由.流量路由由 Ingress 资源上定义的规则控制. 下面是一个将所有流量都发送到同一 Service 的简单 Ingre ...
- [Linux]异常配置专题之重复配置的有效性:系统/环境变量 | hosts
1 文由 在项目中经常遇到这种情况,1个hosts文件里同一IP 或 域名存在多个映射配置,那么到底哪个有效?环境变量亦有此问题. 问题本身不难,只是为了避免混淆,进行专门记录,以加深记忆. 2 ho ...
- linux防火墙开放1521端口
问题描述:使用plsql连接数据库发现TNS报错,登录服务器发现防火墙开放,如果直接关闭防火墙,所有的端口都可以连接,但是实际中可能会遇到开启防火墙的库,这时候需要开放单一端口对某一服务器或者某一网段 ...
- C# 获取指定窗口的上层窗口
如何获取当前窗口层级上方的所有窗口信息 User32有函数GetWindow function (winuser.h) - Win32 apps | Microsoft Docs,可以根据已知窗口句柄 ...
- 学node 之前你要知道这些
初识nodejs 19年年底一个偶然的机会接到年会任务,有微信扫码登录.投票.弹幕等功能,于是决定用node 来写几个服务,结果也比较顺利. 当时用看了下koa2的官方文档,知道怎么连接数据库 ...
- 关于Mybatis
一:Mybatis的优缺点 优点: 1.与JDBC相比,减少了百分之五十的代码量. 2.Mybatis是最简单的持久层框架,简单易学. 3.Mybatis灵活,不会对应用程序和现数据库设计强加任何影响 ...
- CD的认知与学习
cd命令的作用 ●ls可以理解成当前而cd是切换到那一层 ls查看当前目录下的所有文件(不包含隐藏): ls /查看/下的所有文件 cd 切换到哪个文件下 pwd命令的作用 通过ls来验证当前的工作目 ...