マルチスレッドクラス

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
組木紙織

マルチスレッドクラス

#1

投稿記事 by 組木紙織 » 17年前

おかげさまで大分完成してきました。
質問に答えてくださった方々ありがとうございます。

基本的な骨格が完成したので、今回は質問というより批評をお願いしたいと思います。

スレッド作成クラス、ミューテックスを作ったので基本的なスレッド操作は
出来るようになったと思います。
残りはイベントクラス、セマフォクラスを作ったら完成予定です。

基本的なコンセプトは、エラーが起きてもデットロックに陥りにくいように
する。プログラムが途中で停止しない。という2点です。

クラスは全てTKネームスペースの中に入れています。
サンプルコードも入れてあるので使い方は分かると思います。
使用してみて気になったことがあったらコメントを下さい。

#前回の後にコメントしようと思ったらもう過去ログになってた。。。

たかぎ

Re:マルチスレッドクラス

#2

投稿記事 by たかぎ » 17年前

突っ込みどころ満載なので、とりあえず上から順番にいきます。
まずは ThreadException クラスから。
ThreadException(std::exception e):exception("Thread"),name(e.what()){}
ThreadException():exception("Thread"){}
ThreadException(const char * e)  :exception("Thread"),name(e){}
コンストラクタですが、std::exception からの変換コンストラクタの存在意義がよくわかりません。
なぜ exlicit を付けないのか、なぜ値渡しなのかも読み取れませんでした。
const char*からの変換コンストラクタも explicit なしでは危険ではないですか?
コピーコンストラクタを定義していないのは意図通りでしょうか?(なくても問題はなさそうです)
データメンバ name の初期化時に std::bad_alloc 例外が送出される可能性があることは想定済みでしょうか?
ThreadException operator=(ThreadException &e){return e;}
代入演算子の形式が変です。これを書くなら、
ThreadException& operator=(const ThreadException &e)
{
  std::string t(e.name);
  name.swap(t);
  return *this;
}
とすべきです。ここでも std::bad_alloc 例外が送出される可能性がありますが、これは避けられません。
const char * getName(){return this->name.c_str();}
なぜ const がないのでしょうか? これだとコンパイルできないと思います。

そもそも Thread::throwErrorCode にかなり無理があるように思います。

たかぎ

Re:マルチスレッドクラス

#3

投稿記事 by たかぎ » 17年前

Thread クラスですが、コピーコンストラクタやコピー代入演算子でコピーされると、コピーの数だけ CloseHandle が呼ばれますが、大丈夫でしょうか?

Thread::Pack のデータメンバである errorException と errorFlag ですが、参照先の寿命管理は本当に大丈夫ですか?

Thread::run の中で例外を catch しているのはよいのですが、ThreadException のコンストラクタが失敗するとさらに例外が送出され、その場合は破綻してしまいます。

たかぎ

Re:マルチスレッドクラス

#4

投稿記事 by たかぎ » 17年前

Mutex::create は変です。もう一度見直してください。
また、Mutex クラスもコピーされた場合に破綻します。

Critical クラスもコピー代入演算子で破綻しますが、それ以前に何か違和感があります。

組木紙織

Re:マルチスレッドクラス

#5

投稿記事 by 組木紙織 » 17年前

返信遅くなってすみません。

うえから順番に返答します。

まず変換コンストラクタについてですが、現時点ではまだ何も考えていません。
というより完全に忘れてました。

>コピーコンストラクタを定義していないのは意図通りでしょうか?(なくても問題はなさそうです)

意図的です。

>データメンバ name の初期化時に std::bad_alloc 例外が送出される可能性があることは想定済みでしょうか?
一応std::bad_alloc例外については想定済みです。
何とかしてThreadExceptionに子スレッドでどんな例外が投げられたのかを格納したかったので
メンバにnameを作りました。
例外禁止に出来なければ、nameを削除する予定です。
その場合は親スレッドでどんな例外が投げられたのかが知りようがないのが問題ですが。


>そもそも Thread::throwErrorCode にかなり無理があるように思います。

この無理というのは、Thread::throwErrorCodeが例外を投げること自体が無理なのでしょうか?
「無理」という意味が判断できませんでした。
解説をお願いしたいと思います。

Threadクラスについては欲しい機能を追加しただけで他はまだ触っていないです。
コピーコンストラクタなども考える必要があることは分かっています。



>Critical クラスもコピー代入演算子で破綻しますが、それ以前に何か違和感があります。
コピー代入演算子はprivateにいれて定義してなかったように思いますが。

違和感の理由は多分デストラクタで処理を振り分けているからだろうと思います。
このようにした目的は子スレッドでCritical::unlock()を忘れた場合の時のための
安全策のつもりです。
スマートポインタを介したほうがいいのかなと、思ったのですが
どっちの実装にしたほうが良いのか理由が見つからないので、
ここに関してアドバイスが欲しいと思います。



#来週卒業論文提出なのでこんなことをしている場合ではないような。。。

Justy

Re:マルチスレッドクラス

#6

投稿記事 by Justy » 17年前

 面白そうなので、ちょっとだけ参加します。


・ Criticalクラスのデストラクタで objectCounterが0なら objectを
破棄するようになっていますが、(参照カウンターではないので)
他に共有している objectがいない保証になっていませんが、大丈夫ですか?

・ Criticalクラスの使い方で C++なのに lock() / unlock()を対にして
使わせるのはちょっと・・・。
 デストラクタで unlock()忘れ対策をとっているのは判りますが、
対にするならコンストラクタとデストラクタを使った方が確実かと。

・ 連続して lock()を2回以上コールできてしまいますが、意図したとおりでしょうか?
(その場合、デストラクタによる unlock()忘れが正しく機能しない可能性が)。

・ Threadクラスの run()のローカル変数 resultは pf->t()内で例外が発生した時は
何の値を返しているのでしょうか?

たかぎ

Re:マルチスレッドクラス

#7

投稿記事 by たかぎ » 17年前

> >データメンバ name の初期化時に std::bad_alloc 例外が送出される可能性があることは想定済みでしょうか?
> 一応std::bad_alloc例外については想定済みです。

thread.hpp の46行目~55行目の2つの catch 節の中で、ThreadException の自動オブジェクトを生成しています。
このコンストラクタが std::bad_alloc 例外を送出した場合、pf->t も pf も delete の機会を失い、run から例外がリークして破綻すると思うのですが...

> 何とかしてThreadExceptionに子スレッドでどんな例外が投げられたのかを格納したかったので
> メンバにnameを作りました。
> 例外禁止に出来なければ、nameを削除する予定です。
> その場合は親スレッドでどんな例外が投げられたのかが知りようがないのが問題ですが。

格納する内容が文字列だけなら、run の引数として与えるオブジェクトの中に char 配列のメンバを入れておき、strncpy 等でコピーするほうが安全です。

> >そもそも Thread::throwErrorCode にかなり無理があるように思います。
> この無理というのは、Thread::throwErrorCodeが例外を投げること自体が無理なのでしょうか?
> 「無理」という意味が判断できませんでした。
> 解説をお願いしたいと思います。

一番無理がありそうなのは、クライアントコードが errorFlag を知る方法がなく、とりあえず Thread::throwErrorCode を呼んでみて、例外が送出されるかどうかを調べるしかない点です(見落としがあるかもしれませんが...)。
それに、Thread::throwErrorCode は、その仕組み上、元の例外が発生したスレッドの状態に関わらず、非同期に呼び出せてしまいます。しかも、errorFlag は volatile 修飾されていないので、どんな振る舞いをするか予想し切れません。

> >Critical クラスもコピー代入演算子で破綻しますが、それ以前に何か違和感があります。
> コピー代入演算子はprivateにいれて定義してなかったように思いますが。

すみません、これは見落としていました。

組木紙織

Re:マルチスレッドクラス

#8

投稿記事 by 組木紙織 » 17年前

お二人のコメントをいただいて改良版を作ってみました。
コメントいただいた事は大部分は修正できたと思います。

穴があると自分で認識しているのは、
Justy様の
>連続して lock()を2回以上コールできてしまいますが、意図したとおりでしょうか?

連続してlock()を呼ぶことが出来ないようにすることを実装するのを忘れていたぐらいです。

今回も批評をお願いしたいとおもいます。

個人でしか使うつもりはありませんが、
実用に耐えるようにしたいと思っています。

#出来ればお二方だけでなくていろんな人からの突込みがほしいかな。
#突っ込まれた分だけ勉強になったので。

Justy

Re:マルチスレッドクラス

#9

投稿記事 by Justy » 17年前

 おっと、新しくなってますね。
 まだまだいろいろ問題がありそうですが、実用レベルには
大分近づいてきているような気がします。


・ Threadクラスを生成して、即座に破棄するとメモリーリークします。
[color=#d0d0ff" face="monospace]    {
        Thread thread1(std::bind2nd(std::ptr_fun(&bar2), critical));
    }        //ここでリーク
[/color]

 必ず join()しなければならないという仕様であるなら、
デストラクタで必要性を調べ、必要なら join()した方がいいと思います。


・ STRING_SIZE
 C++でこういう定数マクロはちょっと・・・。

 ThreadExceptionクラス内に static const(使えないコンパイラなら enum)で
入れた方が定数がクラスに従属しますし、不用意な(プリプロセッサによる)
置き換えが発生しなくなります。

 あ、あと 19という直値があります。多分、STRING_SIZE-1を狙ったものだとは
思いますが。


・ Criticalクラスのコピーコンストラクタって必要なのでしょうか?
 Criticalクラスの役割を考えると operator=()は無効にしておきながら、
コピーコンストラクタを実装する意味があまりよくわかりませんでした。
(ひょっとして、bar2の引数に実体を渡す為だけ??)

 いっそのこと、コピーコンストラクタもなしにすれば、miniCriticalも不要になって、
Critical/Critical::LockObjectの実装がより簡単になります。
(実体をそのまま持てばいいので、LockObjectへのポインタを保持する必要もなくなるかも・・・)


・ Lockクラスがコピー可能なのは危険なのでは?


・ 前にも書きましたが、Threadクラスの run()のローカル変数 resultは
pf->t()内で例外が発生した時は何の値を返しているのでしょうか?
(不定を返す仕様ならば無視して下さい)

組木紙織

Re:マルチスレッドクラス

#10

投稿記事 by 組木紙織 » 17年前

改良版をさらに改良しました。
今回は結構悩みました。

私にはlock()を2回呼ばないようにできなかったので、
その部分を除いては、今までの指摘されたことすべて改良しました。
(2回呼んでも大丈夫みたいですが)


>・ Threadクラスを生成して、即座に破棄するとメモリーリークします。
今回の実装で一番大変だったのがこの部分です。メモリを管理するスレッドを用意して考えました。



>コピーコンストラクタを実装する意味があまりよくわかりませんでした。
>(ひょっとして、bar2の引数に実体を渡す為だけ??)

ここはご指摘の通り実体を渡すためだけです。


>前にも書きましたが、Threadクラスの run()のローカル変数 resultは
>pf->t()内で例外が発生した時は何の値を返しているのでしょうか?

このresultは他の部分に影響を与えてないように思われるので不定を返しても問題ないような
気がしていたのでそのままにしていました。


今回よくわからなくて実装が途中やめになっている部分があります。
beginthreadex()がエラーを返した時の、errno変数の使い方です。
今考えているのはマルチスレッドなのでエラーを返したことをする確認してerrnoを取得
したとしても、間に別のスレッドで別のエラーをが入っているかも知れないので、
どのようにしてbeginthreadex()のエラーを取得すればよいのかということろです。



#なんとか卒論も提出できてこれで卒業がほぼ確定しました。

Justy

Re:マルチスレッドクラス

#11

投稿記事 by Justy » 17年前

 うーん、イベントを持ってきましたか。
 なんだかちょっと大げさなクラスになってきましたね(^^;


■ デッドロックするかもしれません
 Threadオブジェクトを作ったとき、下記の2つがかち合ってしまい、デッドロックを起こす可能性はありませんか?

  ・ Threadクラスコンストラクタの WaitForSingleObject(this->first,INFINITE);
  ・ Threadクラス template関数 setPack()の WaitForSingleObject(*p_t->second,INFINITE);

(自動リセットオブジェクトにすれば、大丈夫そうですが)


■ ハンドルの解放忘れ
 Threadクラスは正常に動作すると4つのハンドルを作成しますが、うち1つしか解放していません。
 つまり、このクラスを使うたびに3つのハンドルが溜まっていくことになります。


□ const オブジェクト
 ThreadExceptionクラスのコンストラクタや、catch節のオブジェクトなど constを
付けておいた方がいいかと。


□ Critical::LockObjectの miniCritical オブジェクト
 何に使ってます?



>私にはlock()を2回呼ばないようにできなかったので
 とりあえず Criticalクラスの lock()/unlock()を privateに配置して、
Lockクラスを friend指定にすれば、(Lockクラスでしか使われていないので)
現状2回呼ばれる・・・というか lock()/unlock()の呼ばれる回数がおかしくなる
ことは防げますね。

 2回コールを防ぐには lock()が呼ばれたらフラグを trueに、unlock()が呼ばれたら falseに
するようにして、フラグが trueなのに lock()がよばれたら・・・でチェックできるのでは
ないでしょうか。


>今回の実装で一番大変だったのがこの部分です。メモリを管理するスレッドを用意して考えました
 うーん、この為だけにイベント2つにスレッド1つですか。
 ちょっともったいない気もしますね。

 どうせ Threadクラスのデストラクタでスレッドの終了待ちをしなければならないのであれば、
Threadクラスに Pack<F>オブジェクトへのポインタを持つちょっと特殊なスマートポインタ(?)を
持たせて、そのデストラクタを利用してメモリを破棄してもいいような。
(ちょっと特殊なスマートポインタになるのは、Threadクラスのコンストラクタの template引数に
よって中身の Pack<F>の Fが変化するからです)


>このresultは他の部分に影響を与えてないように思われるので
 直接的に影響はないのですが、コンパイラによっては警告が出たりしますので・・・。


>beginthreadex()がエラーを返した時の、errno変数の使い方です。
 たしか、Visual Studioで MTでビルドしているなら、スレッド毎に異なる変数を見に行くはずなので
別のスレッドの値が入ることはなかったかと。
 それ以外だとわかりませんが・・・。

組木紙織

Re:マルチスレッドクラス

#12

投稿記事 by 組木紙織 » 17年前

修正したつもりでしたが、修正できてなかったところをすべて突っ込まれてました。
きちんとコードは確認しておかないと。


>Threadクラスに Pack<F>オブジェクトへのポインタを持つちょっと特殊なスマートポインタ(?)を
>持たせて、そのデストラクタを利用してメモリを破棄してもいいような。

その方法も考えたのですが、実装方法がわからなかったので、上記のようにスレッドで管理しました。
簡単なサンプルコードを提示していただければ助かります。

Justy

Re:マルチスレッドクラス

#13

投稿記事 by Justy » 17年前

 こんなかんじですかね。

 Threadクラスが持つ PackHolderクラスがそれです。

組木紙織

Re:マルチスレッドクラス

#14

投稿記事 by 組木紙織 » 17年前

ありがとうございます。
ざっと見てみました、継承を使うことは思いつかなかったです。
他にも色々と手が入っていたようなので、変更点を確認して、さらに完成を目指していきたいと思います。

Justy

Re:マルチスレッドクラス

#15

投稿記事 by Justy » 17年前

>他にも色々と手が入っていたようなので
 つい気になったところをついでに直してしまいましたが、
ちょっと個人的な意見を。

 Threadのデストラクタでスレッドの終了待ちをしていますが、
これは先の私のコードにしても組木紙織さんのコードにしても
リソース(発生した例外情報)の解放がスレッドが終了しないと出来ない、という観点から
必要な処理だとはおもいますが、現実的にはこれはあまりよろしくないように思えます。

 たとえば
[color=#d0d0ff" face="monospace]    {
        Thread thread1(std::bind2nd(std::ptr_fun(&bar1), &critical));
    }[/color]

 のようなコードの場合。
 デストラクタでスレッドの終了待ちをしてしまうと、bar1スレッドの内容によっては(無限ループとか)
そのまま処理が止まってしまうのでうまくありません。


 根本的な事を言ってしまいますと、Threadクラスからスレッド内で発生した例外の状況を知る、
ということは本当に必要なことなのでしょうか。

 一見その機能は便利に思えるのですが、ずっと過去にスレッド内で発生した例外が
あるかどうかを取得する程度の意味合いでしかないですし、
(私的には)スレッド内で発生した例外はスレッド内で処理しないとまずいことが多いので、
使いどころが難しい機能となります。

 勿論、そのスレッド内で例外の補足を怠った、或いは失敗したのなら、run()内で
それを補足する必要はありますが、その場合は警告を出して即座に
プログラムを止めてしまってもいいたぐいのエラーではないでしょうか。

 とまぁ、Threadクラスから例外の状況取得という機能がなくなれば、
TThreadのデストラクタでスレッドの終了待ちが必要なくなって、
先のコードでも Threadオブジェクトは即座に消滅しハンドルも破棄するけど、
bar1は走り続ける、というコードになります。

 いかかでしょうか?



 それでもやっぱり後から例外の状況を取得したい、ということであれば
アプローチを変えればできるかと思います。

 一例を挙げるなら例えば、今はユーザースレッドの指定を関数オブジェクトで行っていますが、
純粋仮想関数 run()や、仮想関数 exception()とかを持ったユーザースレッド専用の
基底クラス ThreadRunを作って、それを継承してユーザが独自のクラスを作り、
そのオブジェクトを Threadクラスに与えることで ThreadRunの run()をスレッドで実行するようにする。
 内部で例外を補足したら 仮想関数 exception()を呼び、例外の情報をセットする。

 で、外部からの例外の発生状況の取得はその継承したクラスから取得する、
という流れで一応要望は満たせます。

 或いはまぁ単純にスレッドに指定した関数の引数に例外情報を格納する構造体への参照とかポインタとか
入れてしまうとか。

組木紙織

Re:マルチスレッドクラス

#16

投稿記事 by 組木紙織 » 17年前

>Threadのデストラクタでスレッドの終了待ちをしていますが、...

そういえば子のスレッドを途中で破棄すると、子スレッド中でautoで生成したオブジェクトの
デストラクタが呼ばれることはないんですよね。
ということはThreadのデストラクタで子スレッドの終了待ちをしたほうが安全なのかな。
無限ループだけは気をつけないといけなくなりますが。

ただThreadのデストラクタが子スレッドの終了を待つ時間がもったいないなと思ったので
子スレッドを途中で破棄したかったということがあります。


>勿論、そのスレッド内で例外の補足を怠った、或いは失敗したのなら、run()内で
>それを補足する必要はありますが、その場合は警告を出して即座に
>プログラムを止めてしまってもいいたぐいのエラーではないでしょうか。

そのような考え方もありますか。

一番最初にあげたコンセプトからは少し外れるような気もしますが、プログラム作成者にとっては
その方がエラーがわかりやすくなりますね。
ユーザーに選択権を与えるといった方法で考えていきます。


#大幅な組み直しになりそうな予感。

Justy

Re:マルチスレッドクラス

#17

投稿記事 by Justy » 17年前

>ということはThreadのデストラクタで子スレッドの終了待ちをしたほうが安全なのかな。
>無限ループだけは気をつけないといけなくなりますが。
 うーん、大抵の場合終了待ちした方が安全ですね。
 ただ、明示的に join()すればいいだけの話なので、
デストラクタでした方がいいか、と言われると先の例のようなケースもあるので
そうでもないような。

 まぁ結局は Threadのインスタンスの生存期間 >= スレッドの生存期間で
いいのかどうか、でしょう。


>そういえば子のスレッドを途中で破棄すると、子スレッド中でautoで生成したオブジェクトの
>デストラクタが呼ばれることはないんですよね。
 あー、強制的に破棄すると、環境依存ですが呼ばれないこともありますね。
 明示的にそうするメソッドがあってもいいとは思いますが、デストラクタで
キャンセルしてしまうのはまずそうです。

 スレッド内に(ユーザーフラグによる)キャンセルポイントを作って
キャンセル指示を出した後、join()で待つ、が無難でしょうか。


>>その場合は警告を出して即座に
>>プログラムを止めてしまってもいいたぐいのエラーではないでしょうか。
>そのような考え方もありますか。

 次の C++0xにもスレッドクラスが標準ライブラリに入ることになるらしいのですが、
こちらはスレッド内で例外の補足に失敗すると std::terminate()をコールするみたいです。
(30.2.1.2 thread constructors 5)

ttp://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2521.pdf



>#大幅な組み直しになりそうな予感。
 がんばってくださ~い。

組木紙織

Re:マルチスレッドクラス

#18

投稿記事 by 組木紙織 » 17年前

色々とがんばって組み直してみました。
継承を使ったりテンプレート使ったりで難しい。。

まだ全部完成していませんが、全体的な構成の仕方についてコメントが欲しいと思います。

Justy

Re:マルチスレッドクラス

#19

投稿記事 by Justy » 17年前

 なるほど、こうなりましたか。


>全体的な構成の仕方についてコメント

 スレッドを使うのに、実行クラスを作って継承して、くらいまではいいのですが、
ThreadRunHolderにそのクラスの型とデストラクタのポリシーを指定して、
更にそれを Threadのテンプレート引数に、って複雑すぎやしませんか?

 前のようなお手軽感がなくてなってしまったような気がします。


・ デストラクタのポリシークラス
 ThreadNotBreak/ThreadCanBreakはコンストラクタで処理をしていますが、
これならそれぞれのクラスに同じ関数名を持つ静的関数を用意した方が、
いいと思います。
 実質的には変わらないのですが、Threadのデストラクタでオブジェクトを生成して
コールするより、普通に関数コールした方がわかりやすいかな、と。


・ Thread::join()
 キャンセル指示はイベントとかOSの仕組みを利用した方が安全だと思います。


・ ThreadRun/My2
 Threadクラスでの使われ方で、My2のコンストラクタで volatile int*という引数を
強制されてしまっています。
 多少制限があるのは仕方がないのですが、さすがに My2クラスの自由度が
固定されてしまうというのはどうでしょう。


・ このサンプル
 main()の threadオブジェクトは作られて即座に破棄されます。
 その為デストラクタで this->endFlagが trueになり、そのままスレッドの終了待ちを
行うのですが、この段階では Threadクラスの静的関数 run()が実行されていない
可能性があり、その場合 run()が後からよばれ this->endFlagを falseに初期化して
してから、My2の run()が実行されます。

 その場合、スレッドがいつまで経っても終了しなくなります。
 Threadのコンストラクタでは、ちゃんと静的関数 run()が実行されたことを
確認した方がいいと思います。

組木紙織

Re:マルチスレッドクラス

#20

投稿記事 by 組木紙織 » 17年前

>前のようなお手軽感がなくてなってしまったような気がします。
やっぱり複雑でしたか。
今回は前回より簡単に使えるようにしてみました。

>Threadクラスでの使われ方で、My2のコンストラクタで volatile int*という引数を 強制されてしまっています。

これは子スレッドで無限ループを作ってデットロックに陥らないようにするために必要な
ことなのでどうしようもないと考えています。

>Thread::join()
>キャンセル指示はイベントとかOSの仕組みを利用した方が安全だと思います。

wait関数はOSの仕組みではないのですか?
どのような意図かよく理解できないので解説が欲しいと思います。


スレッドを途中で破棄するときにCloseHandle()を使わないようにとMSDNにあったので
どの関数を使って破棄をすればよいのかわからないのが今の気になっているところです。
ほかはすべて実装したつもりです。

Justy

Re:マルチスレッドクラス

#21

投稿記事 by Justy » 17年前

>>>Threadクラスでの使われ方で、My2のコンストラクタで
>>>volatile int*という引数を 強制されてしまっています。
これは子スレッドで無限ループを作ってデットロックに陥らないようにするために必要
なことなのでどうしようもないと考えています。
 それはキャンセル指示の為のフラグを Threadクラスと実行しているスレッドと
共有するためだと思いますが、回避する方法はあります(後述)。


>>Thread::join()
>>キャンセル指示はイベントとかOSの仕組みを利用した方が安全だと思います。
wait関数はOSの仕組みではないのですか?
 wait関数というのは WaitForSingleObjectのことですか?
 たしかに OSの仕組みですが、これは同期待ちの方です。

 キャンセル指示自体は endFlagで行っているので、volatileが付いているとはいえ
これをイベント(CreateEvent/SetEvent)で通知した方が同期もとれていいかなぁ、
と思っただけです。
 今回のケースでは実際どっちでもいいのかもしれませんが。

 で、先の回避の方法ですが。
 単純に一方通行の信号(Threadクラスから実行スレッドへのキャンセル)なので、
TLS(Thread Loacl Storage)を使ってイベントハンドルなどを保持し、
参照カウンタでデータの破棄を行うようにすれば一応できます。
 サンプルを添付したので、それを見て頂ければ判るかと思います。


スレッドを途中で破棄するときにCloseHandle()を使わないようにとMSDNに
 それは _beginthreadの話では?
 _beginthreadexなら普通に CloseHandle()でいいと思います。



※添付のサンプルは全く完全ではなくおかしなところも多々あるので、あくまで参考程度に(^~
# 追記(19:59) 露骨にダメなところを修正
# 追記(23:06) やっぱりまだおかしかったので、再修正。
 ついでに例外補足失敗時の挙動を選べるよう修正。

Justy

Re:マルチスレッドクラス

#22

投稿記事 by Justy » 17年前

 さて、新しい方の Threadクラスですが。

・ Threadの特殊化
 Threadクラスが特殊化されたものがデフォになっていますが、
結構妙な使い方です。
[color=#d0d0ff" face="monospace]    template<typename Run>
    class Thread<ThreadRunHolder<Run> >
    {
        ...
    };
[/color]
 1パターンしか特殊化しないのであれば、普通に Threadテンプレートに
してみてはどうでしょうか。


・ 静的関数 run
 これも NullTypeで特殊化されていますが、内容が1カ所くらいしか違わないのであれば
[color=#d0d0ff" face="monospace]    struct caller_run_void
    {
        template <typename T>
        static void run(ValueType &r, T)    { r.run(); }
    };
    struct caller_run_other
    {
        template <typename T>
        static void run(ValueType &r, T v)  { r.run(v); }
    };[/color]

 のようなクラスを用意して、run<F>の Fが NullTypeだったら、caller_run_voidを、
それ以外なら caller_run_otherの runを使ってコールするようにして
まとめてしまった方がメンテが楽です。
[color=#d0d0ff" face="monospace]    template <typename T, typename U>
    struct IsSameType       { enum { Result = false }; };
    template <typename T>
    struct IsSameType<T, T> { enum { Result = true }; };
    
    template <bool flag, typename T, typename U>
    struct SelectType               { typedef T Type; };
    template <typename T, typename U>
    struct SelectType<false, T, U>  { typedef U Type; };

    typedef SelectType<IsSameType<F, NullType>::Result, caller_run_void, caller_run_other>::Type Caller;
    Caller::run(run, *pf.t);[/color]

・ 破棄
 破棄の時の挙動を My2などの継承先にもってきていますが、
どうせならポリシークラスにしてしまい、テンプレート引数でしていさせた方が
間違いがない、かと思います。


・ 関数
 以前は普通の関数もスレッドに指定できたと記憶していますが、
以後は関数タイプのものはなし、ということでしょうか?

組木紙織

Re:マルチスレッドクラス

#23

投稿記事 by 組木紙織 » 17年前

サンプル見てみました。色々と複雑なことをしているようで、理解するのに時間がかかりそうです。
実装するのにはさらに時間がかかりそう。


> 以前は普通の関数もスレッドに指定できたと記憶していますが、
>以後は関数タイプのものはなし、ということでしょうか?


関数タイプのものは、継承を使ったクラスタイプよりユーザーが簡単に作れると思いますが、
関数タイプを実装していなかったのは、実装する頭の余裕がなかったというだけです。
クラスタイプのめどがついたら関数タイプを実装しようとかんがえてはいます。


時々ポリシークラスという言葉を使っていますが、この構造って一種のStrategyパターンですよね。
実行時の変更ができなくて、コンパイル時にすべてが決まってしまう。
とちょっと気になりました。


#最初はこんなに複雑になると予想はしてなかったです。

たかぎ

Re:マルチスレッドクラス

#24

投稿記事 by たかぎ » 17年前

しばらく見ないうちに、追いきれなくなりましたが...

> 時々ポリシークラスという言葉を使っていますが、この構造って一種のStrategyパターンですよね。

その認識であっていると思いますよ。
「Modern C++ Design」にも、ポリシーが一種の Strategy パターンであることが記されていました。

> 実行時の変更ができなくて、コンパイル時にすべてが決まってしまう。

この例からも分かるように、テンプレートは静的なポリモーフィズムを実現するためのツールとして使えます。
実は、これが Java や C# には真似ができない、C++ の最も強力な機能だったりします。

Justy

Re:マルチスレッドクラス

#25

投稿記事 by Justy » 17年前

>クラスタイプのめどがついたら関数タイプを実装しようとかんがえてはいます。
 なるほど。


>実行時の変更ができなくて、コンパイル時にすべてが決まってしまう。
 たしかにそうですね。
 しかしそれがメリットでもありますので、上手に使えばその方がいいこともあります。


>#最初はこんなに複雑になると予想はしてなかったです。
 いろいろな機能を付けようとすると、多少はそうなりますよね。
 スレッドは意外なところに落とし穴がありますし。

 世の中にはいろんなところに C++で実装したスレッドライブラリがあるので、
いろいろ見てみるといいかもしれません。


CodeProject: A C++ Cross-Platform Thread Class. Free source code and programming help
ttp://www.codeproject.com/KB/threads/thread_class.aspx

CodeProject: Encapsulating Win32 threads in C++. Free source code and programming help
ttp://www.codeproject.com/KB/threads/thread_win32.aspx

CodeProject: A C++ Thread Class. Free source code and programming help
ttp://www.codeproject.com/KB/threads/ThreadClass.aspx

CodeProject: Creating a C++ Thread Class. Free source code and programming help
ttp://www.codeproject.com/KB/threads/threadobject.aspx

 これの翻訳が↓
CodeZine:C++でのスレッドクラスの作成(オブジェクト指向)
ttp://codezine.jp/a/article/aid/1977.aspx?p=1

CodeGuru: Template Thread Library
ttp://www.codeguru.com/cpp/w-p/system/threading/article.php/c5687/

CodeGuru: A Generic C++ Thread Class
ttp://www.codeguru.com/cpp/misc/misc/threadsprocesses/article.php/c3793/

Chapter 15. Boost.Thread
ttp://www.boost.org
  ttp://www.boost.org/doc/html/thread.html

Class Poco::Thread
ttp://pocoproject.org/
  ttp://pocoproject.org/poco/docs/Poco.Thread.html

組木紙織

Re:マルチスレッドクラス

#26

投稿記事 by 組木紙織 » 17年前

Justy様のDate: 2008/02/23(土) 19:13に添付されたコードで一部不明な部分があるので、
質問させてください。
zthread.cppの中にある以下のコードです。

void ThreadDetail::BaseParam::Wait()関数が複数回呼ばれる可能性がないようにも思えるのにかかわらず、
if(started)return;
と、複数回呼ばれる可能性を考慮しているようなので、なぜ複数回呼ばれる可能性を考慮する必要が
あるのか?
ということです。

この関数は親スレッドで使用され、子スレッドの準備が終わるまで、親スレッドを待機させるという目的
で使われています。
void ThreadDetail::BaseParam::Wait()
	{
		if(started)	return;
		
		DWORD result = WaitForSingleObject(start_handle, INFINITE);
		if(result == WAIT_OBJECT_0)
		{
			CloseHandle(start_handle);
			start_handle =  0;
			started = true;
		}
		else
			throw ThreadLogicException();
	}

Justy

Re:マルチスレッドクラス

#27

投稿記事 by Justy » 17年前

>と、複数回呼ばれる可能性を考慮しているようなので、
>なぜ複数回呼ばれる可能性を考慮する必要があるのか?

 特にないですね。
 使われ方からすれば、複数回呼ばれる可能性はありません。

 ただ、BaseParamクラス"単体"で見た時に、一度でも trueを返した後、
呼ばれるとまずいから、そうしたのだと思います(うろ覚え)。

組木紙織

Re:マルチスレッドクラス

#28

投稿記事 by 組木紙織 » 17年前

回答ありがとうございます。

>複数回呼ばれる可能性はない。
ということで私のコードはそれを前提に構築していこうと思います。

ただ、Justy様のサンプルコードは何重にも安全策を施しているようにみえる部分があるので、
安全策を一つだけにせず、何重にも重ねて作っていく必要があるのかなと感じています。

Justy

Re:マルチスレッドクラス

#29

投稿記事 by Justy » 17年前

>安全策を一つだけにせず、何重にも重ねて作っていく必要があるのかなと感じています

 そうですね。
 
 作ってるときは必要ないと思っても他人が使う場合や設計が変更になったりした時に、
そのクラスや関数の設計コンセプトに反した使われ方をする可能性があったりします。
 
 それにあり得ないと思っていても起きてしまうのがプログラムですしね。

閉鎖

“C言語何でも質問掲示板” へ戻る