マルチスレッドクラス

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

マルチスレッドクラス

#1

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

過去ログ マルチスレッドクラス
ttp://www.play21.jp/board/formz.cgi?action=res&resno=13859&page=&lognum=46&id=dixq&rln=14641&vino=28

時間がかかりましたが、
色々な皆様(特にJusty様)のおかげで只今作成中のマルチスレッドクラスがだんだんと完成に近づいてきました。
欲しい機能を全て盛り込み、今までに指摘された問題についても注意して実装をしました。

色々と機能を盛り込みすぎて複雑になってしまいましたが、今回も前回と同様に批評を
お願いしたいと思います。

Justy

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

#2

投稿記事 by Justy » 17年前

 途中でいろいろと変わってきていると思うので、ここいらで現時点での設計のコンセプトとか
実現したいこと、したくないとなどを整理する為にも書いておくといいかもしれません。


 とりあえず、ざっくりと見てみました。

・ Threadクラスを使うと、ハンドルが1つ解放されないものがありそうです。
 多分 TK::_threadBase::threadBreak()の aliveThread判定が逆なのではと疑っています。

・ _threadBaseクラスのメンバ hEndedハンドルは何に使っているのでしょうか?
(今は削除されてしまっているスレッドのキャンセル機能かな、と推測されますが)


・ ThreadOpeのデストラクタの動作は自動で join()されるのですが、
Threadクラスみたいに選べないのでしょうか。


・ _threadBaseを継承している関係で、継承先の Threadクラスなどで start()など内部で使用する関数が
使用できてしまいますが、それは問題ないでしょうか?


・ using namespace TK;
 ヘッダでこれをすると、TK名前空間の意味が全く無くなってしまいます。

組木紙織

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

#3

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

気をつけたのに、頑張ったのに、ざっくり見られただけでも凡ミスが色々と発見されて、
こんなんじゃプログラマー目指せないなと自分に落ち込んでいます。
もっと頑張らないと。

コンセプトを書く前に、コメントについて返事をしておきます。

>・ Threadクラスを使うと、ハンドルが1つ解放されないものがありそうです。
>多分 TK::_threadBase::threadBreak()の aliveThread判定が逆なのではと疑っています。

はい。ifの判定が逆でした。falseのときにfalseとするような判定をしても意味がないです。


>・ _threadBaseを継承している関係で、継承先の Threadクラスなどで start()など内部で使用する関数が
>使用できてしまいますが、それは問題ないでしょうか?

あ、protectedにするのを忘れていました。


>・ using namespace TK;
> ヘッダでこれをすると、TK名前空間の意味が全く無くなってしまいます。

消去し忘れていました。


/**ここからがコンセプトなどなど********/

1.マルチスレッドを扱うクラスを作る。

2.子スレッドの生存期間は親スレッド中のこのマルチスレッドを扱うクラスを同じものとする。

3.マルチスレッドを扱うクラスと子スレッドの関係は1対1とする。

4.コピーコンストラクタやコピー代入演算子は禁止にする。

5.そのマルチスレッドを扱うクラスは、動作クラスをテンプレートにするもの(Thread)、関数を引数にして動作するもの(ThreadOpe)
の2つを用意する。

6.Threadクラスが破棄されるときに必ず子スレッドの終了を待つ(安全側)か、子スレッドを強制的に終了させる(危険側)のかの選択権を
 ユーザーが持つ。
 この選択権は明示的に示さない限り必ず安全側であるものとする。

7.ThreadOpeクラスはThreadクラスのような選択権を持たずに、必ず安全側であるものとする。

8.動作クラスはBaseRun<"引数","選択権">を継承するものとする。

9.Threadクラスのコンストラクタは明示的に定義しなければ引数を持たない。

10.ThreadOpeクラスのコンストラクタは次のようなものとする。
ThreadOpe<"引数の型">("動作関数へのポインタ","引数");
 なお動作関数はvoidを返し、引数を必ず1つもつものとする。

11.Threadクラスは次のような公開メンバ関数を持つ。
void resume()
void suspend()
void interrupt()
int isEnd()
void join()
voi sleep(double)

12.ThreadOpeクラスはThreadクラスのinterrupt()を除いた公開メンバ関数を持つ。


13.公開メンバ関数interrupt()は安全側では動作クラス中にキャンセルポイントinterruptPoint()を必要とするが、
 危険側では必要としない。


/*ここまで*/

コンセプトというより仕様書っぽくなってしましましたが、こんな感じのを作りたいと考えています。
矛盾した部分はないとは思うのですが、自信はあまりありません。

まとめてみると少し実装し忘れていたのが残っいました。
続きを作らないと。

Justy

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

#4

投稿記事 by Justy » 17年前

>気をつけたのに、頑張ったのに、ざっくり見られただけでも凡ミスが色々と発見されて、
 テストファースト、とまでは言いませんが、全メソッドを使ったユニットテスト用のコードを用意して
実行するだけである程度エラーを検知できるようにしておくといいいかも。


>コンセプトというより仕様書っぽくなってしましましたが、こんな感じのを作りたいと考えています
 仕様が明確になったので、意図がよく分かりました。ありがとうございます。
 基本的には前のスレでも書きましたが、デストラクタでの子スレッド扱いに関すること以外は
仕様的に問題はないように思います。


>6.Threadクラスが破棄されるときに必ず子スレッドの終了を待つ(安全側)か、
>子スレッドを強制的に終了させる(危険側)のかの選択権を ユーザーが持つ
 「子スレッドを放置する」という選択肢はないのでしょうか?(でも現時点での実装は放置になってますよね?)
 
 ついでに言えばスレッドクラスとしての安全性を考慮するなら
逆に「子スレッドを強制的に終了」は要らないと思います。

組木紙織

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

#5

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

>>6.Threadクラスが破棄されるときに必ず子スレッドの終了を待つ(安全側)か、
>>子スレッドを強制的に終了させる(危険側)のかの選択権を ユーザーが持つ
> 「子スレッドを放置する」という選択肢はないのでしょうか?(でも現時点での実装は放置になってますよね?)

子スレッド放置というつもりで実装はしてないので、後で確認してみます。

子スレッドの終了に関してのコンセプトは、
子スレッドはSleep()で一定時間停止させて使うことが多いと考えており、基本的に子スレッドは安全に終了させるが、その場合には親スレッドは子スレッドが停止している時間待つ必要がある。
速度が必要な場合にはこの静止している時間がもったいないと思うことが出てくる可能性があるので、明示的な場合のみ子スレッドを強制的に終了させて速度を確保することが出来るようにする。
と、考えています。

また子スレッドの放置は、メモリーリークの可能性や、子スレッドが悪さをする可能性があると
考えており、また子スレッドを放置する利点が現時点で見つからないので子スレッド放置の実装は考えていません。

Justy

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

#6

投稿記事 by Justy » 17年前

>子スレッド放置というつもりで実装はしてないので、後で確認してみます。
 えーと、CanBreakが使われる(join()しない方)と放置になります。



>子スレッドの終了に関してのコンセプトは、
> ~
>と、考えています。
 なんとなく言わんとするところは理解できますが、問題は「放置する」選択肢がないこと
なんです。

 コンセプトとして、絶対にデストラクタが終わった時にはスレッドは終了していることを
保証し、それに反するような使い方をするスレッドを作る場合は別の関数・クラスを使うなりする
ということであればそのままでも問題はありません(その場合以下は無視して下さい)。


 もし、そうではなく汎用的なスレッドクラスを目指しているのであれば幾つか問題があります。

 まず、前スレでも指摘したのでご存じだと思いますが、無限ループ"的"な子スレッドの場合、
Threadクラスがデストラクタで join()を行うと復帰が困難になります。

 又、デストラクタで強制的に子スレッドを破壊することは、子スレッドの状況如何では
非常に危険を伴います。
 従ってこの場合、放置する(勿論ハンドルは解放しますが)か、キャンセルポイントによるスレッドの
キャンセルをするのが最適なのではないでしょうか。


 次に

> 必ず子スレッドの終了を待つ(安全側)か、子スレッドを強制的に終了させる(危険側)のか
 ということは
 
 Threadクラスのライフサイクル > 子スレッドのライフサイクル
 ということになります。

 これが便利なケースもありますが、逆にいつ終わるか判らない子スレッドの為に、
Threadクラスが存在していなければならないことになり、スレッドの本来の目的である
非同期性が失われ、運用が不便になる可能性もあります。

 例えばある関数の機能として、リクエストだけして戻っていき、
(一定時間で終わるだろう)処理そのものは子スレッドで行うような場合、
どうしたらいいでしょう。
[color=#d0d0ff" face="monospace]TK::ThreadOpe<int>* test()
{
    return new TK::ThreadOpe<int>(bar2,0);
}
[/color]
 とか、
[color=#d0d0ff" face="monospace]boost::shared_ptr< TK::ThreadOpe<int> > test()
{
    return new TK::ThreadOpe<int>(bar2,0);
}
[/color]
 なら、即座に関数を抜けていきますが、戻値を受け取って管理しないと
いけません。
[color=#d0d0ff" face="monospace]void test()
{
    TK::ThreadOpe<int> thread1(bar2,0);
}
[/color]
 というようなコードですと、現状の仕様ではbar2が終わるまで戻れませんし、
強制破棄するタイプだとしても、すぐに関数を抜けて戻っていきますが
それでは bar2()の処理が終わりません。

 これがもし「放置」するタイプであれば、最後のコードで旨く動きます。
 こういう、リクエスト系の使い方はしないのでしょうか?



>また子スレッドの放置は、メモリーリークの可能性や、子スレッドが悪さをする可能性があると
 もちろん、放置は万能ではありません。
 マズイのであれば、キャンセルポイントによるスレッドのキャンセルをするなり、
実行の終了を親スレッド側でまったり、或いは最悪スレッドの強制終了も視野にいれる必要はあります。



 ちなみに前スレで話題にした C++0xの std::threadなど多くのスレッドクラスのデストラクタは
基本的に実行している子スレッドに何もすることなく終えるようです。

組木紙織

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

#7

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

リクエスト系の使い方は今までしたことがなかったので、考えていませんでした。
子スレッドの放置を考えなかった一番の理由はスレッドをリークしたメモリみたいな
状態にすることに非常に違和感を感じたからです。

> ちなみに前スレで話題にした C++0xの std::threadなど多くのスレッドクラスのデストラクタは
>基本的に実行している子スレッドに何もすることなく終えるようです。

このことは知ってはいたのですが、上記のような理由で考慮していませんでした。
「放置」が実際に使われるテクニックであるのなら実装に含めてもいいと考えています。


汎用性をどこまで求めるかというのは、
そもそもマルチスレッドを扱うクラスを製作しようとした理由がダブルバッファリングをマルチスレッド
で扱いたいなと考え、WindowsAPIのスレッド関係の関数が使いにくそうだったので、c++でラップした
クラスを使ってマルチスレッドプログラミングの練習をしようかな思ったからです。

Justy

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

#8

投稿記事 by Justy » 17年前

>子スレッドの放置を考えなかった一番の理由はスレッドをリークしたメモリみたいな
>状態にすることに非常に違和感を感じたからです。
 あー、なるほど。
 そういわれると何かこう説明出来ない気持ち悪さはありますね(w


>「放置」が実際に使われるテクニックであるのなら実装に含めてもいいと考えています。
 スレッドクラスの一番重要な役割はスレッドの起動とスレッドハンドルの管理なわけで、
それを考えると理論的にはスレッドハンドルを使って何かする(スレッドの終了待ちとか休止とか)場合は
放置ではまずいですが、逆にハンドルを使わないのであれば放置でいいはずです。


>ダブルバッファリングをマルチスレッド で扱いたい
 ダブルバッファとスレッドがどう関係してくるのかはあまりよくわかっていないのですが、
どちらかという同期オブジェクトクラスも必要ですよね。


#あれ? そういえば前は同期オブジェクトを扱うクラスがあったと思ったのですが、
無くなりました?

組木紙織

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

#9

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

>スレッドクラスの一番重要な役割はスレッドの起動とスレッドハンドルの管理なわけで、
そう考えると、放置の実装もあるわけですね。
スマートポインタはメモリの確保から解放までを管理するものであるのに対し、スレッドクラスは
必要に応じて子スレッドの終了を子スレッド自身に任せてもよいということですか。


>ダブルバッファとスレッドがどう関係してくるのかはあまりよくわかっていないのですが、
前回のコメントは急いでいて意味が分からない文章を書いてしまってすみません。
えっと、要約すると、ダブルバッファリングが扱える程度の汎用性は欲しいというつもりで作っています。
ということです。


>#あれ? そういえば前は同期オブジェクトを扱うクラスがあったと思ったのですが、
同期クラスは完成したので、現在考えている部分のみをあげていました。

今まで安全側の動作を通常の終了の場合は安全なように、
例外が働いているときは危険側のように実装をさせてたのですが、
どちらも同じようにjoin()をして同じ動作をするようにした方が良いのかどうなのか
悩んでいます。

Justy

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

#10

投稿記事 by Justy » 17年前

>スマートポインタはメモリの確保から解放までを管理するものであるのに対し、
 結局スレッドクラスが何を管理するか、に依るわけですよ。

 多くのスレッドクラスはハンドルを管理するので、スマートポインタ同様デストラクタで
ハンドルを解放します。

 もしスレッドクラスがハンドルだけでなく“子スレッドの存在”も管理するのであれば、
放置は絶対にありえない設計となります。


 そういう意味では多くのスレッドクラスは std::auto_ptrとか boost::shared_ptrとかより、
std::fstreamに近いかと思います。

 内部のファイルハンドルをデストラクタで解放しても、別にファイルが
なくなってしまうわけではなく、あくまでそのファイルをハンドリング(制御)する権利を
失うだけですから。




>スレッドクラスは 必要に応じて子スレッドの終了を子スレッド自身に任せてもよいということですか。
 適切に設計されているなら、私はいいと思います。


>>#あれ? そういえば前は同期オブジェクトを扱うクラスがあったと思ったのですが、
>同期クラスは完成したので、現在考えている部分のみをあげていました。
 なるほど。そうでしたか。


>今まで安全側の動作を通常の終了の場合は安全なように、
>例外が働いているときは危険側のように実装をさせてたのですが、
>どちらも同じようにjoin()をして同じ動作をするようにした方が良いのかどうなのか悩んでいます。
 スレッドの存在”も管理するタイプの方ですね?
 例外に巻き込まれているのは Threadクラスのインスタンスがある方で、
子スレッドとは基本的には関係はありませんので、通常時が join()するなら、
例外時も join()した方がいいのではないでしょうか。

 たとえて言うなら、例外に巻き込まれて破棄されるスマートポインタがポインタを
破棄しない、なんて・・・。。

組木紙織

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

#11

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

> たとえて言うなら、例外に巻き込まれて破棄されるスマートポインタがポインタを
>破棄しない、なんて・・・。。

その例示を出されたら例外時と通常時の動作を同一にする理由がよくわかります。
ただ、私が気になったのは、"スレッドの存在も管理するクラス"で安全側にした時に、
例外に巻き込まれたら、デットロックが起こる危険性はを考えたからです。

例外時と通常時の動作を同じようにし、デットロックの危険性は実装者に回避してもらうような
実装にしようかな。

Justy

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

#12

投稿記事 by Justy » 17年前

>ただ、私が気になったのは、"スレッドの存在も管理するクラス"で安全側にした時に、
>例外に巻き込まれたら、デットロックが起こる危険性はを考えたからです。
 
 安全側は join()するだけですよね?
 普通に考えて、例外に巻き込まれた結果デストラクタで join()してデッドロックを
起こすようであれば、通常時に join()してもデッドロックするのではないでしょうか。


>例外時と通常時の動作を同じようにし、デットロックの危険性は実装者に回避してもらうような
>実装にしようかな。

 その方向でいいと思います。



 あ、キャンセルポイントの機能を作成する予定があるのでしたら、
今ちょっとアイデアが浮かびました。


 前のスレの私のサンプル Z::Threadのインターフェースでは
「Threadクラスの Interruptメンバ関数を呼ぶと、子スレッド側の InterruptPoint関数で
スレッドが終了する、でした。
 Z::Threadはハンドル管理型なのでこのままでいいのですが、スレッドの存在も管理するので
あれば、予期しない例外に巻き込まれた時の挙動として、もう1種類のキャンセルポイントを
用意してはどうでしょうか。


・ 通常のキャンセルポイント
 Threadクラスに Interruptメンバ関数(public)を用意して、ユーザーがこれを呼ぶと
子スレッド側の InterruptPoint() / InterruptPointEx()でスレッドが終了します。

・ 例外発生時のキャンセルポイント
 Threadクラスに ExceptionInterruptメンバ関数(private)を用意して、
例外に巻き込まれて Threadクラスのデストラクタが実行されたら join()する前に
これを呼ぶようにします。

 子スレッド側は InterruptPointEx()でスレッドが終了します。


 こうしておくと、子スレッド側で InterruptPoint()を使ったか、InterruptPointEx()を
使ったかで、例外に巻き込まれたときの挙動が変えられます。

 例外に巻き込まれてもそのまま実行して欲しいけどユーザーがキャンセルしたときは
実行を止めて欲しいときは InterruptPoint()で、例外時も実行を止めて欲しいときは
InterruptPointEx()を使用します。

 又、InterruptPointEx()は、通常のキャンセルポイントとしても機能するようにしておけば
両方に反応させたいとき2つ書く必要がありません。



 うーん、自分で書いておきながら蛇足的な感もありますが、Threadクラスのデストラクタで
join()するなら、こういう仕組みで子スレッド側に通知するのも有りかなと思います。

(ちょっと迷うのは ExceptionInterrupt()を呼ぶのは例外に巻き込まれたときだけに
した方がいいのか、デストラクタ実行時常に呼んだ方がいいのか、ですね)

組木紙織

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

#13

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

>>ただ、私が気になったのは、"スレッドの存在も管理するクラス"で安全側にした時に、
>>例外に巻き込まれたら、デットロックが起こる危険性はを考えたからです。
 
> 安全側は join()するだけですよね?

はい現在の実装はjoin()するだけです。
デットロックが起こる可能性があると思ったのは無限ループのスレッドで
通常側はinterrupt()をするようにしているが、例外側はinterrupt()をしないのでは。
と思ったからです。

#最終的な実装はinterrupt()をしてjoin()をする予定だったので、気にする必要はなかったです。


> あ、キャンセルポイントの機能を作成する予定があるのでしたら、
>今ちょっとアイデアが浮かびました。



通常時はjoin()で例外時は放置
通常時と例外時どちらもjoin()
をする中断ポイントを用意するのは、利点があまりないような気がします。
(利点があれば実装したいと思うのですが、利点がなければさらに複雑になるので。)



現在は単一のスレッドしか扱えるようになっていませんが、実装が安定してきたら
次は複数のスレッドをまとめて管理できるようなクラスも作ろうかなと考えています。

Justy

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

#14

投稿記事 by Justy » 17年前

>最終的な実装はinterrupt()をしてjoin()をする予定だったので、気にする必要はなかったです
 なるほど。


>をする中断ポイントを用意するのは、利点があまりないような気がします
 なければ、別にいいのですが。

閉鎖

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