1. 安全に定義するにはどうすればよいですか?
意味的に。この場合、これは明確に定義された用語ではありません。それは単に「リスクなしでそれを行うことができる」という意味です。
2.プログラムを同時に安全に実行できる場合、それは常に再入可能であることを意味しますか?
番号。
たとえば、ロックとコールバックの両方をパラメーターとして取るC ++関数があるとします。
#include <mutex>
typedef void (*callback)();
std::mutex m;
void foo(callback f)
{
m.lock();
// use the resource protected by the mutex
if (f) {
f();
}
// use the resource protected by the mutex
m.unlock();
}
別の関数が同じミューテックスをロックする必要があるかもしれません:
void bar()
{
foo(nullptr);
}
一見、すべてが大丈夫のようです…しかし、待ってください:
int main()
{
foo(bar);
return 0;
}
mutexのロックが再帰的でない場合は、メインスレッドで次のようになります。
main
を呼び出しますfoo
。
foo
ロックを取得します。
foo
が呼び出されbar
、が呼び出されますfoo
。
- 2番目
foo
はロックを取得しようとして失敗し、ロックが解放されるのを待ちます。
- デッドロック。
- おっとっと…
わかりました、私はコールバックのことを使って、カンニングをしました。しかし、同様の効果を持つより複雑なコードの断片を想像するのは簡単です。
3.再入可能機能についてコードをチェックする際に留意すべき、上記の6つのポイント間の共通のスレッドとは正確には何ですか?
関数が変更可能な永続リソースへのアクセス権を持っている、または与えている場合、または匂いを嗅ぐ関数へのアクセス権を持っている、または与えている場合、問題の匂いを嗅ぐことができます。
(わかりました、コードの99%は臭いがするはずです...それを処理するには、最後のセクションを参照してください...)
したがって、コードを調べると、これらのポイントの1つが警告を発します。
- 関数には状態があります(つまり、グローバル変数、またはクラスメンバー変数にアクセスします)
- この関数は、複数のスレッドから呼び出すことができます。または、プロセスの実行中にスタックに2回現れる可能性があります(つまり、関数は、それ自体を直接または間接的に呼び出すことができます)。パラメータがコールバックをパラメータとしてとる関数は、多くの匂いがします。
非再入可能性はバイラルであることに注意してください。可能な非再入可能関数を呼び出す可能性のある関数は、再入可能とは見なされません。
また、C ++メソッドは、にアクセスできるためにおいがするのでthis
、コードを調べて、おかしな相互作用がないことを確認してください。
4.1。すべての再帰関数は再入可能ですか?
番号。
マルチスレッドの場合、共有リソースにアクセスする再帰関数が同時に複数のスレッドによって呼び出される可能性があり、その結果、データが不良または破損します。
シングルスレッドの場合、再帰関数は非再入可能関数(悪名高いのようなstrtok
)を使用するか、データがすでに使用されているという事実を処理せずにグローバルデータを使用できます。そのため、関数はそれ自体を直接または間接的に呼び出すため、再帰的ですが、再帰的に安全でない場合もあります。
4.2。すべてのスレッドセーフ関数は再入可能ですか?
上記の例では、明らかにスレッドセーフな関数が再入可能でないことを示しました。わかりました、コールバックパラメータのために不正をしました。ただし、非再帰的ロックを2回取得してスレッドをデッドロックする方法は複数あります。
4.3。すべての再帰的でスレッドセーフな関数は再入可能ですか?
「再帰的」とは「再帰的安全」を意味するのであれば、「はい」と言います。
関数が複数のスレッドによって同時に呼び出され、問題なく直接または間接的にそれ自体を呼び出すことができると保証できる場合、その関数は再入可能です。
問題はこの保証を評価しています…^ _ ^
5.再入可能性やスレッドセーフティなどの用語は絶対的なものですか、つまり、具体的な定義は固定されていますか?
私はそれらがそうであると信じていますが、その場合、関数の評価はスレッドセーフであり、再入可能であると難しい場合があります。これが、上記の臭いという用語を使用した理由です。関数は再入可能ではないことがわかりますが、複雑なコードが再入可能であることを確認するのは難しい場合があります
6.例
リソースを使用する必要がある1つのメソッドを持つオブジェクトがあるとします。
struct MyStruct
{
P * p;
void foo()
{
if (this->p == nullptr)
{
this->p = new P();
}
// lots of code, some using this->p
if (this->p != nullptr)
{
delete this->p;
this->p = nullptr;
}
}
};
最初の問題は、この関数が何らかの方法で再帰的に呼び出される場合(つまり、この関数が直接または間接的に自分自身を呼び出す場合)、this->p
最後の呼び出しの最後に削除され、おそらく終了前に使用されるため、コードがおそらくクラッシュすることです。最初の呼び出しの。
したがって、このコードは再帰的に安全ではありません。
これを修正するために参照カウンターを使用できます:
struct MyStruct
{
size_t c;
P * p;
void foo()
{
if (c == 0)
{
this->p = new P();
}
++c;
// lots of code, some using this->p
--c;
if (c == 0)
{
delete this->p;
this->p = nullptr;
}
}
};
この方法では、コードは再帰的なセーフになり...しかし、それはまだので、問題をマルチスレッドのリエントラントではありません。私たちは、でなければならないことを確認の修正c
とのp
使用、アトミックに行われます再帰的な mutexを(すべてではないミューテックスは再帰的です):
#include <mutex>
struct MyStruct
{
std::recursive_mutex m;
size_t c;
P * p;
void foo()
{
m.lock();
if (c == 0)
{
this->p = new P();
}
++c;
m.unlock();
// lots of code, some using this->p
m.lock();
--c;
if (c == 0)
{
delete this->p;
this->p = nullptr;
}
m.unlock();
}
};
そしてもちろん、これlots of code
はの使用を含め、自体が再入可能であることを前提としていますp
。
そして、上記のコードはリモートで例外的に安全ではありませんが、これは別の話です…^ _ ^
7.コードの99%が再入可能ではありません。
スパゲッティコードの場合は非常に当てはまります。ただし、コードを正しく分割すると、再入可能性の問題を回避できます。
7.1。すべての関数に状態がないことを確認してください
パラメータ、独自のローカル変数、状態のない他の関数のみを使用し、データが返された場合はデータのコピーを返す必要があります。
7.2。オブジェクトが「再帰的に安全」であることを確認してください
オブジェクトメソッドはにアクセスできるためthis
、オブジェクトの同じインスタンスのすべてのメソッドと状態を共有します。
したがって、オブジェクト全体を破損することなく、スタック内のあるポイント(つまりメソッドAの呼び出し)でオブジェクトを使用でき、次に別のポイント(つまりメソッドBの呼び出し)でオブジェクトを使用できることを確認してください。オブジェクトを設計して、メソッドの終了時にオブジェクトが安定していて正しいことを確認します(宙ぶらりんのポインターや矛盾するメンバー変数などがない)。
7.3。すべてのオブジェクトが正しくカプセル化されていることを確認してください
他の誰も内部データにアクセスできません。
// bad
int & MyObject::getCounter()
{
return this->counter;
}
// good
int MyObject::getCounter()
{
return this->counter;
}
// good, too
void MyObject::getCounter(int & p_counter)
{
p_counter = this->counter;
}
ユーザーがデータのアドレスを取得する場合、const参照を返すコードでさえも、const参照を保持しているコードに通知されずにそれを変更できるため、危険な場合があります。
7.4。オブジェクトがスレッドセーフではないことをユーザーが知っていることを確認してください
したがって、ユーザーはミューテックスを使用してスレッド間で共有されるオブジェクトを使用する必要があります。
STLのオブジェクトはスレッドセーフではないように設計されているため(パフォーマンスの問題のため)、ユーザーstd::string
が2つのスレッド間で共有したい場合、ユーザーは同時アクセスプリミティブでアクセスを保護する必要があります。
7.5。スレッドセーフなコードが再帰的に安全であることを確認してください
つまり、同じリソースが同じスレッドで2回使用できると思われる場合は、再帰的mutexを使用します。