web-dev-qa-db-ja.com

サービス層のサービスはスレッドセーフにする必要がありますか?

これはAndroidアプリ用ですが、サービスレイヤーで設計されたすべてのソフトウェアに当てはまると思います。

私たちのアプリは、UIとその下のサービスレイヤーを処理するプレゼンテーションレイヤーで構成され、いくつかのビジネスロジックを実行する必要があるときにUIレイヤーが呼び出す多くのサービスオブジェクトで構成されています。たとえば、ユーザーが何かをクリックしたときに記録するEventRecordingServiceがあり、多くのUIクラスが同じEventRecordingServiceオブジェクトへの参照を保持しているとします。

ここで、EventRecordingServiceが、表示する各イベントに増分する番号を割り当てる必要があるとしましょう。つまり、カウンタを維持する必要があります。明らかにステートレスである方が良いでしょうが、時にはそれは避けられません。イベントが2つの異なるスレッドから同時に記録された場合、カウンターへのアクセスが同期されていないと、混乱して誤った結果をもたらす可能性があります。

スレッドセーフティは高価で難しいため、通常、このようなことに対する私の態度は、クラスをスレッドセーフにする必要がない限り絶対に作成しないことです。現在、このサービスへのすべての呼び出しは同じスレッドで行われており、これ以上追加する予定はないので問題ありません。

私の同僚は、将来的には誰かが別のスレッドからサービスを呼び出す可能性があるため、スレッドセーフである必要があると主張しています。サービスは外部からはステートフルに見えませんが、カウンターは実装の詳細であり、現在さらにサービスを呼び出す計画はありませんが、将来発生することは容易に想像できます。この場合、スレッドセーフにしないと、新しい呼び出しが追加されたときに動作するように見えても、たまにひどく間違ってしまう可能性があるため、危険な場合があります。

私は彼がポイントを持っているように感じますが、それは私たちが何かをする必要がないとしても、私たちがすべてをスレッドセーフにするように思えます。

それで、このような場合に通常行うことは何ですか?スレッドセーフではないという警告をクラスに追加する必要がありますか、それともスレッドセーフにする必要がありますか?それとも、すべての犠牲を払ってそれをステートレスにする方法を見つける必要がありますか?

2
fluidj

現在、このサービスの呼び出しはすべて同じスレッドで行われており、これ以上追加する予定はないので問題ありません。

この声明はそれをすべて言います。現在、クラスは作成されたニーズを満たしています。スレッドセーフティは現在の要件ではありません。 [〜#〜] yagni [〜#〜] (Ya Ai n't Gonna Need It)は、ここでの最良の指針です。スレッドの安全性は追加の作業であり、現時点では具体的なメリットのない追加の作業であるとあなたは言っています。

このサービスクラスを呼び出す複数のスレッドが必要な場合にのみ、このレベルの同時実行性の真の要件を発見できます。その時点で、このクラスをスレッドセーフにするための追加の努力は価値があります。

とりあえず、KISS it —シンプルに保つ。適切な単体テストを作成する。後でリファクタリングする。今すぐ時間を節約する。

3
Greg Burghardt

この質問に対する「正しい」答えはないと思います。
私の経験から、

すべての犠牲を払って無国籍にする方法を見つけたら

多くの場合、そのようなクラスは、状態なしでは想像できません。管理が必要なバックグラウンドキューがある場合。

スレッドセーフではないという警告をクラスに追加する必要があります

はい、潜在的な問題がある場合は、インターフェイスといくつかのランタイムログに警告を追加する必要があります。

またはスレッドセーフにする

マルチスレッドについて話すとき、次のような見方があります。

  1. 基本的なスレッドセーフ(共有データの状態はすべての不変条件に対して保護されています)

  2. きめの細かいコード:各スレッドは、可能な限り短い時間、ある種のロックを保持します。

1と2は両方ともrelativeです。実装を「悪い」、「十分に良い」、「完璧」などと評価できます。

現在のコードベースにはマルチスレッドの問題はないが、後で問題が発生する可能性があるとのことですが、「十分な」バージョンでは「1」のみを実装することをお勧めします。

また、インターフェースの基本的なテストカバレッジを追加して、シンプルなケースでうまく機能し、リグレッションをある程度制御できるようにします。

実装を最初から「完璧」にすることは困難ですが、多くの場合、それを必要としません。また、基本的なマルチスレッドサポート(ここではmutex、条件付きロック)を追加するのはそれほど高価ではなく、後で改善することができます。

1
Olha

たとえば、ユーザーが何かをクリックしたときに記録するEventRecordingServiceがあり、多くのUIクラスが同じEventRecordingServiceオブジェクトへの参照を保持しているとします。

あなたのUIが確かに特別なものでない限り、典型的なUIフレームワークはシングルスレッドです 多くの理由で 。マルチスレッドサービスを実際に使用することはないため、実際にはマルチスレッドサービスを必要としない可能性はありますか?もちろん、イベントを記録するためだけに、UIからスレッドを生成するつもりでない限り。

サービスは外部からステートフルに見えません、カウンターは実装の詳細です

状態は、「外部」から(つまり、パブリックAPIから)推定できるわけではなく、必ずしもそうである必要はありません。オブジェクト指向のサービスでうまくやれば、メソッドしか見えなくなります。 APIの機能に関する提案は、「直感」に基づくのではなく、明確に定義され、明確に伝達される意味に基づいている必要があります。インターフェースの実装者の状態は考慮に入れられるべきものではなく、それは実装者のビジネスです。インターフェースのコンシューマー(インターフェースのimplementation)は、基礎となる実装がステートレスであるかどうかにとらわれず、ベースの動作を変更しないでくださいそれが状態を保持しているかどうかについて。

イベントが2つの異なるスレッドから同時に記録された場合、カウンターへのアクセスが同期されていないと、混乱して誤った結果をもたらす可能性があります。

確かに、スレッドセーフは高価で複雑です。しかし、これが全体の要件である場合、多くのフレームワークは基本的な操作をアトミックにする機能を提供します。 Interlocked.Increment は例です。この場合、スレッドセーフを実現することは簡単なことです。そのため、フレームワークでサポートされている場合は、アトミックなインクリメント操作を見つけて使用することをお勧めします(可能性があります)。

私は彼がポイントを持っているように感じますが、それは私たちが何かをする必要がないとしても、私たちがすべてをスレッドセーフにするように思えます。

おそらく、基になる操作をスレッドセーフにするのは簡単なため、彼はポイントを作ろうとします。ただし、説明するシナリオは非常に単純であることを覚えておいてください。

それで、このような場合に通常行うことは何ですか?

それがglaringly簡単である場合、カウント変数をインクリメントする場合のように、アトミック操作ラッパーメソッドがフレームワークによってすでに提供されているかどうかを確認し、それを使用します。複数のスレッドからサービスを使用することは決してないでしょう。また、int(つまり32ビット変数)ではなくlongを使用している場合は、原子性に問題がない可能性があります。

非常に少なく、クラスが見事に失敗するように少し努力してください複数のスレッドからそれを使用しようとするとき、そのような時が来たときにそれを「アップグレード」することを忘れないように。どうやって?さて、小さなステップは、次のようなプライベートブール変数を使用して再入可能性をチェックすることです:


//Check for re-entrancy.
if (_isWorking == true)
    throw new TheWorldIsFallingApartException("Ooops, I meant NotSupportedException! This class is not thread-safe yet.");

_isWorking = true;

//Do whatever you need to do here

_isWorking = false;

もちろん、変数の割り当て 常にアトミック操作であることが保証されているわけではありません ですが、ブール値の問題はありません。だから、私がひどく誤解されない限り、誰かがマルチスレッド化することに決めたので、そのような小さな抜粋は、少なくともあなたが将来奇妙な結果を得ることからあなたを守るでしょう。

0
Vector Zita