Open Nagarei opened 9 years ago
見直してみるとわかりづらいですね...。
ここではDxLibがスレッドセーフに動作するとしておきます。 例えば関数Texture2D::DrawGraphを呼び出すとします。 しかし、同時にScreen::draw関数が別のスレッドでよばれていた場合Texture2D::DrawGraphはどこに描画されるかがわからなくなってしまいます。 このように表から見れば異なるオブジェクトに対する操作なのにスレッドセーフでなくなってしまうというのが問題です。
うーん、クラス内にstatic変数作って、mutexすればそこまで面倒では無いと思うんですが・・・。 http://minus9d.hatenablog.com/entry/20130914/1379168684
下のケースではライブラリ内でmutexを使っても状況は変わりません。はじめからこっちを載せておくべきでした...。
//DxLibの他のオブジェクトからの動作と競合する例
//異なるオブジェクトへのアクセスのみだが、問題が起こる
void func()
{
Screen screen(640, 480);
screen.draw([]{
LoadGraphScreen( 0 , 0 , "test1.bmp" , TRUE ) ;
});
}
void Run(){
Texture2D graph1(Texture2D::LoadGraph("test1.bmp));
std::thread thd(func);
graph1.DrawGraph( 0 , 0 , true) ;
thd.join();
}
ユーザーがmutexを使って排他制御をすれば大丈夫なのですが...。それを要請するためにどのようなスタンスを取るべきかといった感じです。
ちょっと待って下さい。私はクラス内のstatic変数としてmutexをつかう、と書いたと思います。
この時点でヘッダーだけで実装することはかなわず、.cppファイルが必要になりますが、それくらい作ってもいいと思います。
なるべく.hppに書くようにしてきた理由は関数callのオーバーヘッドを減らすためだったので、それにかかわらなければ.cppに書いていいと理解していますが、そうでは無いのでしょうか?
ごめんなさい。僕は「クラス内のstatic変数としてmutexを使う」ことにしても、この問題を解決する方法を思いつきません。 上のケースを解決するサンプルコードを書いていただけないでしょうか?
DrawnOn関数を仮実装ですが追加しておきました。
あとで実験してちゃんと書きますが、イメージです
#include <mutex>
class foo{
public:
//前略
template<typename Func_T>
void DrawnOn(Func_T&& draw_func) {
this->screen_authority.lock();
auto old_draw_screen = DxLib::GetDrawScreen();
this->SetDrawScreen();
draw_func();
DxLib::SetDrawScreen(old_draw_screen);
this->screen_authority.unlock();
}
//中略
private:
//中略
static std::mutex screen_authority;
}
#include "a.hpp"
std::mutex foo::screen_authority;
そのやり方だと、以下が反例になります。Run関数が呼んでいるLoadGraphScreenの書き込む先がDrawnOnのSetDrawScreenの呼び出しの前か後かで変わってしまいます。 このケースは異なるオブジェクトに対するアクセスなのでこのような曖昧さが生じるべきではないです。
void func()
{
Screen screen(640, 480);
screen.DrawnOn([]{
DxLib::LoadGraphScreen( 0 , 0 , "test1.bmp" , TRUE ) ;
});
}
void Run(){
std::thread thd();
DxLib::LoadGraphScreen( 0 , 0 , "test2.bmp" , TRUE ) ;
thd.join();
}
いや、正直仕方ないかなと。そしてやっと
DxLibを使う関数は全てスレッドセーフ保障なしにする
がわかった。
100%確実なのは、DxLib側のmutexに相当する部分をAPIとして公開してもらうことですが、それも馬鹿げてるので(というかあれのAPI化とか多分相当な労力になる気がする)、dxle名前空間の関数を使用する限りにおいてはスレッドセーフを保証する方向で行きましょう。混ぜるな危険、ということで。
もっとも、スレッドセーフを保証できなくなるのは一部の関数だけのはずなので、(ですよね?)、あとでスレッドセーフを保証できなくなるDxLib側の関数をリストアップしましょう。
継承元のTexture2Dにmutex変数をstaticメンバー変数として持たせるほうがいいのかな
継承元のTexture2Dにmutex変数をstaticメンバー変数として持たせる
DrawBox系をdxleに入れた場合問題になる、と思ったけれどそれはなさそうですね...。 しかし、将来こういう事が起こる可能性を考慮するとmutexをグローバル変数にしたほうが良いのではと思うのですがどう思いますか?
いやそれよりもmutexをstaticメンバー変数にもつクラスを例えばscreen_mutex_c
とかいう名前で作って、それを継承したほうがいい気がしてきました・・・
いずれにせよ、変更はたいしたbreaking changeではないのでどれでもいいっちゃいいんですが。
mutexをstaticメンバー変数にもつクラスを例えばscreen_mutex_cとかいう名前で作って、それを継承したほうがいい気がしてきました・・・
ではそんな感じで作ってみます。
mutex系で一つのヘッダにするのと関連する機能のところに書くのとどっちが良いですかね?
多分関連する機能のところに書く方向でいいと思います。
そういえばもう死んでますが http://wayback.archive.org/web/20031210174108/http://dkingyoutility.sourceforge.jp/studiokingyonet/DxEx.html こんなサイトがあったんですね。クリティカルセクションってなんだろう。
WinAPIではmutexよりもクリティカルセクションを使った同期の方が早い模様。 https://msdn.microsoft.com/ja-jp/library/cc429052.aspx std::mutexは内部でどっちを使ってるんだろう...。 ただ、どうせDxLib関連の部分でマルチスレッドなんて誰も使わないので環境依存の少ないstd::mutexで実装します。
各クラスの説明のところにスレッドセーフ保障についてどのレベルで提供するかを書く事を提案します。
こんな感じでしょうか? C++11スレッドセーフは#4の僕の書き込みの「全てが読み込み操作ならば排他制御は不要である」のことを指しています。
それがいいと思います。
マルチスレッド対応について正確に決めておく必要があると思います。
と3パターンあると思いますが、さらに
というパターンがあります。 特に最後のはただ「スレッドセーフではない」では済まないのでどうしたものかという感じです。
論点(だと思う):DxLibを呼び出す関数のスレッドセーフ保障