マルチスレッドプログラミング

D 言語はマルチスレッドプログラミングのための機能を言語自身が持っている.

クリティカルセクション

synchronized{...}

クリティカルセクションを作成できる. このブロックの中の文は同時に一つのスレッド しか実行することができなくなる.

multi_thread.d

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import std.stdio;
import core.thread;

class ShareData{
  private int v;
  public void increase(){
    synchronized{
      int a = v;
      foreach(i; 0..10){
	v = v + i;
      }
    }
  }
  public int value() const{
    return v;
  }
}

class IncrementThread : Thread{
  private ShareData sd;
  public this(ShareData sd){
    this.sd = sd;
    super(&f);
  }
  private void f(){
    sd.increase();
  }
}

void main(){
  // 全部のスレッドで共有するデータ
  auto sd = new ShareData;
  // 10 個のスレッドを次々に作って実行開始
  Thread[] threads;
  foreach(i; 0..10){
    auto t = new IncrementThread(sd);
    threads ~= t;
    t.start();
  }
  foreach(t; threads){
    t.join();
  }
  // インクリメント結果を表示
  writeln(sd.value);
}

multi_thread.d の実行結果は:

[cactus:~/code_d/d_tuts]% ./multi_thread
450

また, 排他制御に用いる mutex オブジェクトを指定することもできる.

multi_thread2.d

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import std.stdio;
import core.thread;

class ShareData{
  private int v;
  public void increase(){
    synchronized(this){
      int a = v;
      foreach(i; 0..10){
	v = v + i;
      }
    }
  }
  public int value() const{
    return v;
  }
}

class IncrementThread : Thread{
  private ShareData sd;
  public this(ShareData sd){
    this.sd = sd;
    super(&f);
  }
  private void f(){
    sd.increase();
  }
}

void main(){
  // 全部のスレッドで共有するデータ
  auto sd = new ShareData;
  // 10 個のスレッドを次々に作って実行開始
  Thread[] threads;
  foreach(i; 0..10){
    auto t = new IncrementThread(sd);
    threads ~= t;
    t.start();
  }
  foreach(t; threads){
    t.join();
  }
  // インクリメント結果を表示
  writeln(sd.value);
}

multi_thread2.d の実行結果は:

[cactus:~/code_d/d_tuts]% ./multi_thread2
450

synchronized メンバ関数

複数のスレッドから同じインスタンスにアクセスするとき, synchronized メンバ関数 を用いて排他制御を行える.

上の例を synchronized メンバ関数を使う形に書き換えると以下のようになる.
(エラーとなったコードだが, 残念!!)

multi_thread3.d

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import std.stdio;
import core.thread;

class ShareData{
  private int v;
  public synchronized void increase(){
      int a = v;
      foreach(i; 0..10){
	v = v + i;
      }
  }
  public int value() const{
    return v;
  }
}

class IncrementThread : Thread{
  private ShareData sd;
  public this(ShareData sd){
    this.sd = sd;
    super(&f);
  }
  private void f(){
    sd.increase();
  }
}

void main(){
  // 全部のスレッドで共有するデータ
  auto sd = new ShareData;
  // 10 個のスレッドを次々に作って実行開始
  Thread[] threads;
  foreach(i; 0..10){
    auto t = new IncrementThread(sd);
    threads ~= t;
    t.start();
  }
  foreach(t; threads){
    t.join();
  }
  // インクリメント結果を表示
  writeln(sd.value);
}

multi_thread3.d の実行結果は:

[cactus:~/code_d/d_tuts]% dmd -w multi_thread3.d
multi_thread3.d(24): Error: function multi_thread3.ShareData.increase () shared is not callable using argument types ()

shared 型修飾子

一般に, メモリには静的変数 (static 変数とグローバル変数) のための領域がある.

この領域はデータセグメントと呼ばれ, スレッドごとに用意される.

D 言語 2.x では, 新しいスレッドを生成すると, デフォルトでセグメントは共有されず コピーされる.

そのため, あるスレッドで一方のデータセグメントを書き換えても, 他のスレッド のためデータセグメントは変化しない.

shared 型修飾子は, そのデフォルト動作から変更し, スレッド間で静的変数を共有 させるための修飾子だ.

shared.d

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import std.stdio;
import core.thread;

int a; // スレッドごとに別々の静的変数を用意
shared int b; // スレッド間で共有される静的変数を用意

void main(){
  auto tg = new ThreadGroup;
  tg.create = { // ここを実行するスレッドから見えているのは main 関数から見える a とは別の a
    a++;
  };
  tg.create = {
    b++; // ここを実行するスレッドから見えているのは main 関数から見える b と同じ b
  };
  tg.joinAll();
  writeln(a); // main 関数から見える a は変更されていないので 0
  writeln( cast(int)b ); // 共有しているので変更されて 1
}

shared.d の実行結果は:

[cactus:~/code_d/d_tuts]% ./shared
0
1

synchronized メンバ関数は, shared 修飾されたオブジェクトを介してしか呼び出す ことはできない.

shared2.d

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import std.stdio;
import core.thread;

class A{
  synchronized void f(){}
}

void main(){
  auto x = new A;
  // x.f(); // error
  auto y = new shared(A);
  y.f(); // OK
}

shared 型変数と非 shared 型変数は別の型として扱われる.

shared 修飾されたオブジェクトからしか synchronized メンバ関数を呼び出すこと はできない.