53用d编程共享数据

消息传递是安全的并发方法.
多线程读写相同数据.共享数据不安全,就是大家(不受控的线程)都要来争.本章虽然简单,但实际中经常遇见.尽管用std.concurrency,对core.thread也是适用的.
在d中不是自动共享的.
默认为tls(本地线程)的.尽管所有线程都可访问模块级变量,但每个线程只是得到一个副本.

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("结束工作前");
}

void main() {
    spawn(&worker);
    thread_joinAll();
    printInfo("结束工作后");
}

两个地址和值是不一样的.两份.
因而,spawn不允许传递引用给线本对象.

import std.concurrency;

void worker(bool * isDone) {
    while (!(*isDone)) {
        // ...
    }
}

void main() {
    bool isDone = false;
    spawn(&worker, &isDone);// 编译错误,引用
    // ...
    // 希望发出信号表明终止
    isDone = true;

    // ...
}

线程本地数据,给不了其他线程,是引用的地址.
std.concurrencystatic assert避免访问其他线程的可变数据.
但是可以访问全局共享(整个程序独一份)的__gshared.在与c/c++库(默认数据共享)交流时需要__gshared.
线程间可变数据的共享,必须要有shared关键字.

import std.concurrency;

void worker(shared(bool) * isDone) {
    while (*isDone) {
        // ...
    }
}

void main() {
    shared(bool) isDone = false;
    spawn(&worker, &isDone);
    // ...
    isDone = true;// 通知可以结束了:
    // ...
}

一般先考虑传递消息,再用共享.
immutable是可共享的.暗含共享.

import std.stdio;
import std.concurrency;
import core.thread;

void worker(immutable(int) * data) {
    writeln("data: ", *data);
}

void main() {//不变的数据,随便用
    immutable(int) i = 42;
    spawn(&worker, &i);         //编译

    thread_joinAll();
}

无论在哪的不变都是可以随便用的.
core.thread.thread_joinAll等待所有子线程结束.
竞技条件示例
在线程间共享可变数据时,要小心程序是否正确.
考虑多线程共享相同可变变量.

import std.stdio;
import std.concurrency;
import core.thread;

void swapper(shared(int) * first, shared(int) * second) {
    foreach (i; 0 .. 10_000) {
        int temp = *second;
        *second = *first;*first = temp;
    }
}

void main() {
    shared(int) i = 1;
    shared(int) j = 2;

    writefln("before: %s and %s", i, j);

    foreach (id; 0 .. 10) {
        spawn(&swapper, &i, &j);
    }

    thread_joinAll();//等待所有线程来完成他们任务
    writefln("after : %s and %s", i, j);
}

由于竞争关系,都破坏了,结果是随机的,10个线程在随机改.都是瞎搞.上面原因就是多个线程,至少1个都在改.
synchronized(同步)来避免都来竞争.必须拿到锁才能做事情,否则就等吧.

  foreach (i; 0 .. 10_000) {
        synchronized {
            int temp = *b;
            *b = *a;
            *a = temp;
        }
    }

改为,多个线程,要同步.
锁很贵.在有些情况下,可以用原子来保证正确,而不用锁.
要同步多个块时,对每个块加同步.

void incrementer(shared(int) * value) {
    foreach (i; 0 .. count) {
        *value = *value + 1;
    }//如分别对此
}

void decrementer(shared(int) * value) {
    foreach (i; 0 .. count) {//及此同步,是不正确的
        *value = *value - 1;
    }//因为锁不一样
}

要如下才能同步,光一个同步是不行的.

import std.stdio;
import std.concurrency;
import core.thread;

enum count = 1000;

class Lock {
}

void incrementer(shared(int) * value, shared(Lock) lock) {
    foreach (i; 0 .. count) {
        synchronized (lock) {//同步锁
            *value = *value + 1;
        }
    }
}

void decrementer(shared(int) * value, shared(Lock) lock) {
    foreach (i; 0 .. count) {
        synchronized (lock) {//同步锁
            *value = *value - 1;
        }
    }
}

void main() {
    shared(Lock) lock = new shared(Lock)();
//任何类对象都可用来作为同步锁
    shared(int) number = 0;

    foreach (i; 0 .. 100) {
        spawn(&incrementer, &number, lock);
        //必须用同一把锁来同步,所以必须共享锁
        spawn(&decrementer, &number, lock);
    }

    thread_joinAll();
    writeln("Final value: ", number);
}

也可定义类类型同步

synchronized class Class {
    void foo() {
        // ...
    }

    void bar() {
        // ...
    }
}

即在给定类对象上,同步所有(非静态)成员函数.
等价于如下类:

class Class {
    void foo() {
        synchronized (this) {
            // ...
        }
    }

    void bar() {
        synchronized (this) {
            // ...
        }
    }
}

在多个对象上同步时,需要同时指定所有对象.否则你等待我,我等待你,造成死锁.
例如多线程中转账.

void transferMoney(shared BankAccount from,
                   shared BankAccount to) {
    synchronized (from) {
        synchronized (to) {//分开了,是错误的
            // ...
        }
    }
}

有可能两个线程转账,都在锁定.A锁定from(A),B锁定from(B),结果都锁定了,都在等待,死锁了.

void transferMoney(shared BankAccount from,
                   shared BankAccount to) {
    // 注意: dmd 2.074.0不支持
    synchronized (from, to) { 
        // ...
    }
}

要这样,按顺序挨个挨个锁上,
shared static this用于共享初化,只初化一次,static this而则每个线程都要执行一次,以便所有线程初化模块级变量.
shared static ~this()共享析构.

import std.stdio;
import std.concurrency;
import core.thread;

static this() {//要出错
    writeln("执行static this()");
}

void worker() {
}

void main() {
    spawn(&worker);
    thread_joinAll();
}

要这样

int a;              // 线本
immutable int b;    // 所有线程,不变始终是安全的
//当然,如果多次初始化,那一定要出错

static this() {
    writeln("每线程变量", &a);
    a = 42;
}

shared static this() {
    writeln("每程序变量", &b);
    b = 43;
}

同样,shared static ~this(),程序级别的最终处理.
原子操作.
另一种确保只一个线程改变特定变量的方法是使用原子操作.
微控器,编译器,操作系统提供.
d的原子操作在core.atomic.
atomicOp,应用像"+", "+="样的模板参数到其双函数参数中.

import core.atomic;
// ...
    atomicOp!"+="(*value, 1);//原子

等价于非原子行的星value += 1;只是原子操作保证单线程执行(不受其他线程干扰).
因而,当只需要同步二元操作时,没必要使用同步块(因为锁,所以太慢)

import core.atomic;
//...
void incrementer(shared(int) * value) {
    foreach (i; 0 .. count) {
        atomicOp!"+="(*value, 1);
    }
}

void decrementer(shared(int) * value) {
    foreach (i; 0 .. count) {
        atomicOp!"-="(*value, 1);
    }
}

由于是原子操作,所以不用担心出错了.没必要再用锁类了.
也可与其他二元操作一起使用atomicOp.
cas
大名鼎鼎的比交(比较交换).如果仍是当前值,则改变
通过同时指定当前值与期望值来用它.
bool is_mutated = cas(address_of_variable, currentValue, newValue);
仍等于当前值,表明没其他线程改变它.则给它赋值为新值,并返回.相反,不同,则不变,返回.

void incrementer(shared(int) * value) {
    foreach (i; 0 .. count) {
        int currentValue;
        do {
            currentValue = *value;
        } while (!cas(value, currentValue, currentValue + 1));
    }
}

void decrementer(shared(int) * value) {
    foreach (i; 0 .. count) {
        int currentValue;
        do {
            currentValue = *value;
        } while (!cas(value, currentValue, currentValue - 1));
    }
}

重读旧值,直到成功操作,调用cas(…).也叫如值为旧值,则用新值替换.其实就是个自旋锁.不断的循环.
上面的函数块无需同步即可正常操作.
多数时候,core.atomic模块比用同步块快几倍.因此尽量用原子.
原子操作允许无锁编程数据结构.
你还可以查看core.sync,其包含经典的并发原语.如:
core.sync.barrier core.sync.condition core.sync.config core.sync.exception core.sync.mutex core.sync.rwmutex core.sync.semaphore
不依赖时,并行(任务).依赖时,并发.
靠传递消息更好
只能共享shared数据.不变隐含共享.
_ _gshared用于同c/c++交流.
给类定义synchronized,则只有其他线程不在其上面操作时,才能执行成员函数.特定时间只能执行一个成员函数.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值