マルチスレッドについての質問になります。
まずは仕組み的なことの確認から・・・
スレッドは、固有のスタック領域を持ち、forkシステムコール同様にcloneシステムコールを発行する。
forkシステムコールと同様に・・・ということは、カーネル側はtask_structとしてスレッドを管理しているはずです。
するとマルチスレッドにおいて、レジスタ、スタック領域は他のスレッドと競合することはないことになります。
C/C++などの標準ライブラリにはスレッドセーフではない関数があります。
スレッドセーフではない C ライブラリ関数
自分が知っているものは「静的記憶期間に配置されるグローバル変数に書き込みや読み込みを行う」ような関数で、排他的制御がされていないもの」はスレッドセーフではない・・・ということです。(これしか知りません。)
これ以外にもこんな記述はスレッドセーフにはならないというものがあったら教えてください。
思い当たるのは例外処理ですかね??
上記のリンクにある「exit()」の説明は少し難しいですね。(割り込みについてですね。)
リエントラントではない関数についても原因は「排他的制御がされていないグローバル変数」のような気がするんですが・・・
しかし、
rand()、srand()のようなグローバル変数で実装されている関数は単純に排他的制御(ロック機能)をプログラマ側で用意すればマルチスレッドでも安全に利用可能ですよね??
これ以外にも
スレッドセーフではない関数をスレッドで安全に利用する方法があれば教えてください。
あとは、ライブラリに頼らずに自分で関数を作るとか・・・くらいしか思いつかないですが・・・
そもそもライブラリとして提供しているものがスレッドセーフでない・・・という事自体問題であるような気します。
ライブラリとして提供するならスレッドセーフな関数で実装してほしいですよね?
分かる方教えてください。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答6件
0
「グローバル変数」だったらとか、ライブラリ関数のうちあるものだったらと言う考え方はやめた方が良いです。
まず、静的記憶期間に配置されているとか、グローバル変数とか、そんなものは関係ありません。ローカル変数であろうが、動的に確保した領域であろうが、どんな変数であろうが、スレッド間で共通で使用される領域を使用する物は全てスレッドセーフではない可能性があります。thread_local
を使用した領域のようにスレッド間で独立して確保された領域以外は全て危険です。
次に、ある関数は…という考えも辞めた方が良いです。単純な整数のインクリメント++
ですらスレッドセーフではありません。
https://wandbox.org/permlink/Jy4VupXEuZt5xF79
上記では1000000回インクリメントを4スレッドにわけておこなっていますが、多くの場合で4000000にはなりません(環境によってはなる場合もあります)。これを4000000になるように保証するには、std::atomic等を使う必要があります。
https://wandbox.org/permlink/BwTTPifBDcZWhgh0
このように、非常に単純な操作ですら、スレッドセーフであることが保証されていない場合があります。上記のstd::atomicのようにドキュメントや仕様でスレッドセーフであることが明記されているもの以外は全てスレッドセーフではないという前提でプログラミングを行うべきです。私の知る限り、標準のC++においては、std::threadやstd::mutex等周りのマルチスレッド操作に関する関数とアトミック操作を保証するstd::atomic関係以外に、どのような環境でもスレッドセーフであることが保証された物は見たことがありません。
話は別として、スレッドセーフではないライブラリをマルチスレッド環境でも安全に使いたい場合、通常は排他制御でライブラリ関数の多重呼び出しを防止します。しかし、排他制御自体が重いため、呼び出しの前後で毎回処理を無条件で入れていくのは非効率だという考えがあります。では、どうするかというと、考え方を転回させて、基本的には同時に一つのスレッドしか動かないようにし、スレッドセーフなライブラリの呼び出しのみ並列で動作しても良いとする、と言うのがあります。スレッドセーフではないコードがほとんどの場合は、こちらの方が効率が高いからです。
そんな実装している物があるのかというと、あります。PythonとRubyです。この二つの言語はマルチスレッドをサポートしていますが、グローバルインタープリンタロック(GIL)という機能を搭載しています。どんなにスレッドを増やしても、その時動いているのはたった一つのスレッドになるようにするというものです。この環境では、外部のC/C++で作られたライブラリの呼び出しは、常にたった一つのスレッドから行われるため、二重に呼び出して壊れることはありません。逆に、二重で呼び出しても良い物は、そのときだけ別スレッドを動かしても良いよとして、効率化を図ります。
これが良いのか悪いのかはわかりません。Rubyのコミュニティでは、GILは外すべきだ、という意見が時々出るそうです。ですが、GILを前提にした方が、実装もしやすく、シングルスレッドの処理で余計な排他処理を入れなくても良いので速いという利点があります。マルチコアを有意義に使いたい場合は、制限がある分安全になるマルチプロセスを使えばいいと言うことのようです。嬉しいことに、PythonもRubyも簡単にマルチプロセスで動かすためのライブラリが存在するので、そこまで問題ではないのかもしれません。
投稿2018/02/15 16:29
総合スコア21737
0
ベストアンサー
「静的記憶期間に配置されるグローバル変数に書き込みや読み込みを行う」ような関数で、排他的制御がされていないもの」はスレッドセーフではない
これ以外にもこんな記述はスレッドセーフにはならないというものがあったら教えてください。
上記理解で概ね正しいと思います。スレッドセーフでない関数の例としては、外部I/Oを伴うものが上げられます。
- 画面を制御する関数(標準入出力)
- ネットワーク通信を制御する関数
- 外部デバイスを制御する関数
- など
また、プロセスを制御する関数とマルチスレッド処理の組み合わせでは特別な注意が必要なケースがあります。UNIX系OSでは fork
関数 とマルチスレッド処理の組み合わせは避けたほうが無難です。
rand()、srand()のようなグローバル変数で実装されている関数は単純に排他的制御(ロック機能)をプログラマ側で用意すればマルチスレッドでも安全に利用可能ですよね??
適切に排他制御を行えば、マルチスレッド環境でも異なるスレッドから“安全”に呼び出し可能です。ただし、そのときの振る舞いが望ましい結果になるか否かは十分注意が必要です。現実的には、グローバル変数や静的変数に依存する関数を、マルチスレッド環境で利用することは出来ません。
(ここでいう“安全”とは、C/C++言語仕様的には問題無いという狭い意味にすぎません。)
乱数を生成する rand()
の場合、複数スレッドからどのような順序で関数が呼び出されても乱数列に代わりありません。リエントラント(reentrant)でない関数、例えば strtok()
などの「外から見えない内部状態を保持する関数」では、複数スレッドから任意の順序で呼び出されると各スレッド上で得られる振る舞いが予測不可能になってしまい、事実上使い物になりません。
そもそもライブラリとして提供しているものがスレッドセーフでない・・・という事自体問題であるような気します。
ライブラリとして提供するならスレッドセーフな関数で実装してほしいですよね?
一般論として、ある処理をスレッドセーフに実装するには何らかのオーバーヘッドを伴います。ライブラリ提供の関数をスレッドセーフにするか否かは、安全性と性能のトレードオフという ライブラリ仕様設計上の選択 です。
例えば、ほとんどのGUIフレームワークでは、UI部品を操作する関数はスレッドセーフな仕様になっておらず、特定のスレッド(≒UIスレッド)以外から関数呼出しを行うとエラーが発生します。これらの関数をスレッドセーフに実装することも不可能ではありませんが、多くのGUIフレームワークでは動作性能や内部実装の複雑化を避けるため、意図的に「スレッドセーフではない外部仕様」を採用します。
投稿2018/02/15 10:07
編集2018/02/15 10:18総合スコア6191
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/02/15 11:45 編集
2018/02/15 11:45
2018/02/15 12:50
2018/02/15 14:34
2018/02/15 14:43 編集
2018/02/15 14:46
2018/02/15 15:18 編集
2018/02/15 15:02
2018/02/15 15:45
2018/02/16 02:56 編集
2018/02/16 05:24
0
リンク先の関数は、あくまでも「ARM ソフトウェア開発ツールが提供するライブラリーの中でスレッドセーフではないもの」であり、同じ関数でもGCCやVC++ではスレッドセーフのものがあったりします。要するに、スレッドセーフかどうかは処理系(ライブラリーの実装)の問題であって、例えばVC++では/MTあるいは/MDオプションによってリンクされるマルチスレッド版ランタイムライブラリーでは、ほとんどの関数(すべてかどうかは未確認)がスレッドセーフです。
自分が知っているものは「静的記憶期間に配置されるグローバル変数に書き込みや読み込みを行う」ような関数で、排他的制御がされていないもの」はスレッドセーフではない・・・ということです。(これしか知りません。)
例えば、rand()、srand()は、一般的な実装ではグローバル(というか静的な領域)に変数を確保しているのでスレッドセーフではないことが多いですが、私の知る限りでは、VC++のマルチスレッド版ランタイムライブラリーでは、TLS(Thread Local Storage)という仕組みを利用することで排他制御なしにスレッドセーフを実現しています。他の静的領域に変数を持たせている関数も同様です。
rand()、srand()のようなグローバル変数で実装されている関数は単純に排他的制御(ロック機能)をプログラマ側で用意すればマルチスレッドでも安全に利用可能ですよね??
安全とは何かを定義する必要があります。少なくとも、期待通りに動作しないかもしれない、という危険性があります。その辺はyohhoyさんの説明の通りですね。
どうしても静的領域に変数を持たせないといけないような関数を実装する際、TLSの使い方を知っておくと、排他制御のような余分なコストをかけずにスレッドセーフにできます。もちろんすべてのケースでできるとは限りませんが。
投稿2018/02/15 13:00
総合スコア5944
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/02/15 13:04
0
こんにちは。
これ以外にもこんな記述はスレッドセーフにはならないというものがあったら教えてください。
思い当たるのは例外処理ですかね??
基本的には「スレッドセーフと書かれていないものは全てスレッドセーフではない」と考えるのが望ましいです。
同じメモリへの複数の同時アクセスで、少なくとも1つ書き込みがあるものが危険です。何も考えずにプログラムを書くと、この状況は極簡単に発生しますから、これを慎重に回避する必要があるからです。
静的領域(グローバル変数やstaticローカル変数)は、きっちり意識しないと複数のスレッドから簡単にアクセスできるのでハマります。(staticローカル変数はそれを定義している関数を複数のスレッドが同時に呼び出すと同時アクセスが発生する可能性があります。)
ヒープ領域を複数のスレッドで同時アクセスする場合、多少は起きにくいですが、複数のスレッドでポインタ経由で同じヒープ上のオブジェクトを共有することも油断すると発生しますので、やはり危険です。(例えば、C++ではないですが、C#のGUIオブジェクトが該当します。)
スタック領域のオブジェクトへのポインタを複数のスレッドで共有することはレアと思いますが、原理的には有りえます。
そこで、私自身は複数のスレッドで同じメモリへアクセスするものは全て危険と認識し、危険な状況を確実に回避するよう慎重に慎重に設計します。(回避しそこなうと再現性の非常に低い、むちゃくちゃ頭の痛いバグになります。デバッグは地獄です。)
上記のリンクにある「exit()」の説明は少し難しいですね。
機械翻訳されているような文章ですね。判りにくいというか日本語になっていないと思います。(割り込みではなくシグナル、もしくは、例外かも?)
rand()、srand()のようなグローバル変数で実装されている関数は単純に排他的制御(ロック機能)をプログラマ側で用意すればマルチスレッドでも安全に利用可能ですよね??
基本的にはその通りです。しかし、ロック機構はデッドロックのリスクがありますので、必ずしも安全になるわけではありません。ロックする時はデッドロックしないことに要注意です。経験的にはスレッドを終了させる際に発生しやすいです。(呼び出し側がスレッド終了を待ち、スレッドは呼び出し側スレッドの応答を待っているなど。)
とはいえ、同じメモリへの書き込みと読み出しがあるなら、通常は比較的安易にMutexでロックしてます。
そして、複数のロックを待つ処理がないことをチェックしています。
投稿2018/02/15 12:47
編集2018/02/15 12:54総合スコア23272
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/02/15 12:54
2018/02/15 12:58
2018/02/15 13:00
2018/02/15 13:00
2018/02/15 13:15 編集
2018/02/15 13:29 編集
2018/02/15 13:42
2018/02/15 14:44
2018/02/15 14:46
0
これ以外にもこんな記述はスレッドセーフにはならないというもの
いちばんかんたんな例で、printf
があります。標準出力は1つしかないので、それに対して複数スレッドから同時に書き込みを行えば、結果は保証されません。
そもそもライブラリとして提供しているものがスレッドセーフでない・・・という事自体問題であるような気します。
原理的に、標準入出力に依存するものはどうしようもありません。
rand()、srand()のようなグローバル変数で実装されている関数は単純に排他的制御(ロック機能)をプログラマ側で用意すればマルチスレッドでも安全に利用可能ですよね??
そもそもrand
は品質が悪すぎるので、はなから別なものを使うべきでしょう。
投稿2018/02/15 09:45
総合スコア145930
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/02/15 09:53 編集
2018/02/15 09:53
2018/02/15 09:54
2018/02/15 09:58
0
ここによりますと、
スレッドセーフな関数 = ロックを使用して共用リソースを同時アクセスから保護する
となっています。
つまり、「それ以外の場合はない」というのが結論のようです。
exit() 関数は保護されていないグローバル変数を使用しているため、スレッドセーフではない。
とのことです。
つまり、スレッドセーフという言葉の定義が共有資源に対して排他的制御がされているということなんですね!!
記述の仕方ではなく、共有資源に対する制御が言葉の定義になっているんですね!
難しいいいい!
勘違いをしていました。
「printf関数について」
printf() — 定様式の文字の出力
printf()はスレッドセーフのようですね。
投稿2018/02/15 14:11
編集2018/02/15 14:12総合スコア651
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/02/15 14:15
2018/02/15 14:17 編集
2018/02/15 15:11
2018/02/15 15:17 編集
2018/02/15 15:14
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/02/15 16:47
2018/02/15 16:54
2018/02/16 03:02