sharedについて


投稿ツリー



前の投稿 - 次の投稿 | 親投稿 - 子投稿.1 .2 .3 .4 .5 .6 | 投稿日時 2009/9/25 20:49 | 最終変更
SHOO  管理人   投稿数: 658
ネタ元: [http://d.hatena.ne.jp/haru-s/20090925/1253864045 ...ing logging 3.0 - synchronized その3(haru-sさん)]

グローバルのデータ以外にsharedをつけた場合、shared型になったりshared classになったり、shared関数になったりしますが、これらのsharedはどんな役割をもったものなのでしょうか?

その変数に対する操作がアトミックな操作になるのではないか?と仮説を立ててコードを組んでみましたが、どうやら偽であるようです。
反例)
#code(d){{{
import core.thread;

class SharedTester
{
shared int a;
shared void inc()
{
a++;
}
shared void dec()
{
a--;
}
shared int get()
{
return a;
}
}

void main()
{
auto tg = new ThreadGroup;
auto sd = new shared(SharedTester);
foreach (x; 0..10)
{
tg.create=()
{
foreach (i; 0..1000000) sd.inc;
};
tg.create=()
{
foreach (i; 0..1000000) sd.dec;
};
}
tg.joinAll;
assert(sd.get() == 0);
}
}}}
高確率でassertに引っかかる…


shared型にしても、制限がきつくて使い勝手が悪い気がするのですが…
たとえば、いかなるスレッドから呼び出したとしても、データの破壊を起こすことのないsharedメンバ関数、と仮定したとしても、shared型のオブジェクトでないものから呼び出せないのはおかしい気がしますし、そうでないとしたら、synchronized修飾でshared関数になる理由がわかりません。
なぜsharedメンバ関数は、非sharedオブジェクトから呼び出すことができないのでしょうか?

現状の型の種類は5種類…オブジェクトの型と、メンバ関数の呼び出しの可否をまとめてみました。
| - | 通常関数 | const関数 | shared関数 | const shared関数 | immutable関数 |h
| ~通常型 | ○ | ○ | × | × | × |
| ~const型 | × | ○ | × | × | × |
| ~shared型 | × | ○ | ○ | × | × |
| ~const shared型 | × | ○ | × | ○ | × |
| ~immutable型 | × | ○ | × | × | ○ |

const shared型からconst関数が呼べるのはいいのか?
immutable型からconst shared関数を呼べないのはなぜか?
個人的には謎なんですが、いかがなものでしょうか。
私の感覚だと↓のようになるんじゃないかなぁと思うのですが。
| - | 通常関数 | const関数 | shared関数 | const shared関数 | immutable関数 |h
| ~通常型 | ○ | ○ |COLOR(red): ○ |COLOR(red): ○ | × |
| ~const型 | × | ○ | × |COLOR(red): ○ | × |
| ~shared型 | × |COLOR(red): × | ○ |COLOR(red): ○ | × |
| ~const shared型 | × |COLOR(red): × | × | ○ | × |
| ~immutable型 | × | ○ | × |COLOR(red): ○ | ○ |


皆さんのご意見お聞かせください。
投票数:59 平均点:5.59
返信する
前の投稿 - 次の投稿 | 親投稿 - 子投稿なし | 投稿日時 2009/9/29 3:01
ゲスト 
shared関数ってあったんですか?
仕様のどこに書いているのでしょうか.
投票数:60 平均点:5.67
返信する
前の投稿 - 次の投稿 | 親投稿 - | 投稿日時 2009/9/29 3:02
haru-s  新米   投稿数: 17
すいませんログインするの忘れました.
投票数:70 平均点:4.00
返信する
前の投稿 - 次の投稿 | 親投稿 - 子投稿なし | 投稿日時 2009/9/29 3:45
SHOO  管理人   投稿数: 658
dmd2.032の更新の一つ、

synchronized member functions now implicitly typed as shared.
(synchronized メンバ関数は暗黙に shared 型になるようになりました。)

から、synchronized関数は自動的にshared関数になるよ、という風に読み解きました。
なにぶん、shared属性について何も公式のドキュメントで触れていないため、既存の文章とdmdの実装から予想するしかないですね。
投票数:61 平均点:4.92
返信する
前の投稿 - 次の投稿 | 親投稿 - 子投稿なし | 投稿日時 2009/9/30 2:14
SHOO  管理人   投稿数: 658
補足します。


現状のdmdの実装だとまずいかもしれない例
#code(d){{{
import std.stdio;
import core.thread;

class Test
{
/// 内部データa
int a;
/// aの2倍の値を返す
const int get()
{
int tmp = a;
Thread.sleep(100000);
return tmp + a;
}
/// aの値を1増やす
synchronized void inc()
{
Thread.sleep(50000);
a++;
Thread.sleep(50000);
}
}

void main()
{
shared d = new shared(Test);
auto tg = new ThreadGroup;
tg.create=()
{
// 0を1に増やす
d.inc();
};
tg.create=()
{
// a*2(つまり偶数)を表示するつもり
writeln(d.get());
};
tg.joinAll();
}
}}}
#code(console){{{
$ dmd -run main
1
}}}
上記例では、getの関数、incの関数はそれぞれ型情報に対し忠実に型情報から来る約束事を守っている(とりあえずコンパイラによるエラー報告がない)にもかかわらず、目的の動作が得られていない。
なぜならば、getはマルチスレッドを考慮していないにも関わらず、shared型のオブジェクトから呼び出すことができてしまっているためである。
したがって、shared型のオブジェクトからは、const関数を呼び出すことは不正であるべきである(と思う)。
同様に、const shared型のオブジェクトからも、const関数を呼び出すことは不正であるべきである(と思う)。

う~ん、この辺のことってMLとかで話し合われてたりしないのかな…
投票数:51 平均点:5.69
返信する
前の投稿 - 次の投稿 | 親投稿 - 子投稿.1 | 投稿日時 2009/9/30 9:13
haru-s  新米   投稿数: 17
うーん?
2つのcreateは完全に並列な存在なので,
incしてからgetするとは限らないのではないでしょうか?
それを保証したいならincしてからjoinしてgetしないといけないのでは.
投票数:66 平均点:5.45
返信する
前の投稿 - 次の投稿 | 親投稿 - 子投稿なし | 投稿日時 2009/9/30 17:45
SHOO  管理人   投稿数: 658
>incしてからgetするとは限らないのではないでしょうか?

その通りです。
incされてからgetされるかもしれませんし、getされてからincされるかもしれません。また、getしている途中にincされることも、あり得ます。
ただし、変数dはshared型ですので、同期が正しく行われることが保障されています。そのためgetしている途中にincされたとしても正しく計算されることが保障されているはずです。
ですので、クラスのメンバ関数の実装をブラックボックスとして取り扱った場合、想定通り実行されたのであれば、0か2かどちらかの値が表示されるはずなのです。
・・・が、1が表示されてしまっているのは、まさにshared型を導入するきっかけとなっている、共有データに対して行ってはいけないことをした結果というわけです。
今回の場合行ってはいけないこととは、共有データであるdという変数に対して、マルチスレッドを考慮していない方法(get関数)を使ってデータを取得しようとしたこと、ということになるでしょうか。


>[[The Case for D - Memory Model and Manycores>Articles/The Case for D/3]]
>ここで重要な点として、型システムが共有データのことを知っている、という事が挙げられます。型システムは、共有データに対して行ってよいことを制限しており、これによって、同期の仕組みが全体を通して正しく使われることが保証されます。

この記述から推測するに、型システムが同期の仕組みを正しく行われることを保証するため、sharedなメンバ関数を使う時、その関数はデータの同期が正しく行われることを保証しているはず、と判断して使うことができ、また、それにそむいた使い方をしようとした場合はコンパイラが教えてくれるはずだ、ということが期待できるものかと思います。
そして、それがsharedの役割だと考えると、sharedでマークされていない限り、データの同期が正しく行われず、マルチスレッドでは扱うことができない、と考えることもできます。

これらのことを踏まえて考えると、共有データであるshared型のオブジェクトで、マルチスレッドを考慮していない(可能性の高い)constメンバ関数を呼べてしまうのは、おかしいのではないか?ということです。
~

>それを保証したいならincしてからjoinしてgetしないといけないのでは.

というのに対しては、実装の内部を知っているとその通りで、そのほかの方法としては synchronized (d) writeln(d.get); や、mutexを使った排他、などを必ず行わなければ正しく計算されません。
しかしそれでは、型システムが同期の仕組みを正しく行われることを保証しているのに、改めて自分で同期させることを考えなければいけないのでは、shared型の意味がないように思います。
したがって、例のコードはコンパイルエラーにしてしまうべきではないのかと思います。
投票数:100 平均点:3.70
返信する
前の投稿 - 次の投稿 | 親投稿 - 子投稿.1 | 投稿日時 2009/9/30 22:33 | 最終変更
haru-s  新米   投稿数: 17
なるほど.
じゃあ全部のメンバ関数が同期される synchronized class A{...} があってもいいかな・・・.
投票数:70 平均点:3.57
返信する
前の投稿 - 次の投稿 | 親投稿 - 子投稿なし | 投稿日時 2009/10/2 21:56
SHOO  管理人   投稿数: 658
同様に、shared class A{...} があってもいい気がしますね。


スレッドセーフとスレッドアンセーフでクラス分けした方がよいと考えると、関数個々をマークするより全体につけてしまった方が判りやすそうですしね。

……とここまで考えて問題?発生

スレッドセーフなクラス内では、アンスレッドセーフなコードの使い回しができないのではないか?

sharedメンバ関数内では全てのメンバ変数がshared型になるため、スレッドセーフなクラスでは全てのメンバ変数をスレッドセーフなオブジェクトやAtomicな変数、immutableな変数、としなければならない。そうしなければ、sharedなメンバ関数内から取り扱うのにcastを使用しなければならない。
しかしながら、スレッドセーフにするつもりであるところのメンバ変数で同期などを考えるとすると、その中でスレッドセーフなものしか呼ぶことができないというのも非効率的な気がする。
投票数:70 平均点:5.29
返信する
前の投稿 - 次の投稿 | 親投稿 - 子投稿なし | 投稿日時 2009/10/3 23:54
ゲスト 
個人的にshared自体はスレッド間で共有される際に利用されます、という意味以外一切の意味はないと思っています。

あくまでも型システムのためのスレッド間共有の表明であって、機能的に同期を保証するような仕組みではないと思います。
shared自体もimmutableと同様にコンパイラ最適化の機会を与えることが重要なのではないでしょうか?
同期が必要かどうかを見て、必要ないならしないということは当然といえば当然ですし。


私個人はマルチスレッド関係の分類を型システムに組み込んだ、という捉え方をしているので、
通常型とshared型とimmutable型で分類し、通常型とshared型のそれぞれにconstのあるなし、という分類で考えていました。
そのため先述のconst sharedからconstが呼べるという事実は非常に謎ですね。
逆に私は通常型からshared型のメンバが呼べないのは妥当だと思っています。
1つのクラスで通常型とshared型の両対応にしようと思うとコードが倍化しますけどね^^;


通常型でshared型が呼べるとコードは減りますが下手に最適化出来ませんし、オーバーロード集合はどうなるのでしょうか?
「通常型とshared型のメソッドがオーバーロードされている場合に通常型からアクセスすると通常型を優先的に選択する」という規則が必要ですよね?
細かくはわかりませんがアクセス禁止なのを書き換えればいいのかな?
あいまいさを取り除く規則がやっぱり必要かな?

immutableからconst sharedが呼ばれないのは、このオーバーロードの規則的な問題だと思います。
性能的に考えたらconstとconst sharedで片方しか取れないとすると、前者を取るのは妥当のように見えますし。

ここまで考えて規則追加したほうがいいんじゃないかと思ってきました^^;
共有を考慮しなくて良いものを共有考慮したもので置き換えても性能以外に問題は出ませんね…

と、通りすがりながら長文失礼しました。
投票数:65 平均点:3.54
返信する

このトピックに投稿する

題名
ゲスト名
投稿本文

  条件検索へ


メインメニュー

ログイン

ユーザー名:


パスワード:





パスワード紛失  |新規登録

Menu