web-dev-qa-db-ja.com

グローバル変数は非同期プログラムで使用できますか?

これは愚かな質問かもしれません。

このサイトでは、グローバル変数の欠点についてたくさん読んだことがあります。私はようやく、多くの人がレビューする大規模プロジェクトのコード品質を向上させようとしています。グローバル変数を使用すると、変数がどこで変更される可能性があるか(スパゲッティコードを作成する)がわからないため、コードのデバッグが難しくなることを理解しています。

ただし、グローバル変数を回避する方法を実際に考えることができないかなり特殊なケースがあります。私は組み込みデバイス(FreeRTOSのもとではすべてCで)で実行されているプログラムを持っています。このプログラムには約20の合理的に複雑な関数があり、おそらく10個のグローバル変数が渡されます。コードの約3/4はリアルタイムで、残りは非同期で、他の関数が操作する変数を使用して、その内容をサーバーに送信します。

擬似コードのスニペットは次のとおりです。

float important_var = 0;

void some_synchronous_function(){
   do_something();
   math.operation(important_var);
}
void do_something(){
   important_var+=5;
}

void async(){
   if(important_var > 5){
       //whatever
   }
}

main(){
    some_synchronous_function();
}

これはこれを行う合理的な方法ですか? (恐れる人々)

3
0xDBFB7

あるスレッドで変数を変更し、別のスレッドでそれを読み取ることは常に問題があります。 2つのスレッドで変更するほうが悪いです。

アトミック変数を使用できます。アトミック変数は、たとえば、リーダーがimportant_var = 0、次にimportant_var = 5.0、次にimportant_var = 10.0というようになることを保証します。重要な変数がアトミックでない場合、別のスレッドで変更されているときにjustを読み取ると、-anyの結果が生成される可能性があります。

OSがアトミック操作をサポートしている可能性があります。最も強力なのは、基本的に「変数xをaからbに変更する」であり、失敗する可能性があります。たとえば、important_varを5ずつ増やす代わりに、tmp = important_varにして、「important_varをtmpからtmp + 5に変更」します。その間に重要な変数が変更された場合、操作は失敗し、再試行します。

それ以外に、ミューテックス、シーケンシャルキュー、または別のスレッドから変更されている間に変数が変更されないようにするあらゆる種類のメカニズムを使用します。

3
gnasher729

グローバルではコードのデバッグが難しくなるほどで​​はありませんが、非常に大きなプログラムとグローバル状態が使用されている場所から遠く離れて定義されている場合、悪影響が生じる可能性があります。

グローバルな状態の問題は、そのグローバルな、単一の、拡張が難しいことです。

これについて考えてみましょう。関数で変更したい変数がいくつかあるので、すべての関数がそれらを共有できる場所に変数を配置しました。将来、2つのシステムを並行して実行できるようにプログラムを拡張したいとします。共有するグローバル状態は1セットしかないため、簡単に修正できません。したがって、多分あなたのすべてのグローバルな状態を単一のクラスに入れて、代わりにそれを共有してください。プログラムを拡張する場合は、実行中のサーバーごとに1つずつ、そのクラスの2つのインスタンスを作成できます。これにより、特定のサーバーで使用するオブジェクトを決定するという問題が発生します。

これで、よりローカルに保持されたオブジェクトのグローバル状態が得られました。作成時にそれらのオブジェクトをサーバーに渡して、さらに分離することもできます(これは依存性注入です)。これで、プログラムの動作方法に大きな変更を加えずに、効果的にグローバルをまったく使用しないサーバーができました。

2
gbjbaanb

important_varのスコープを単一のソースファイルに制限し、すべての重要な動作をcスタイルのクラスにカプセル化するこの代替案を検討してください。

重要なクラス.c

static float important_var = 0.0;

void do_important_operation(void)
{
   math.operation(important_var);
}

void do_important_adjustment(float amount)
{
   important_var += amount;
}

bool does_important_condition_exist(void)
{
   return (important_var > 5.0);
}

重要なクラス.h

void do_important_operation(void);
void do_important_adjustment(float amount);
bool does_important_condition_exist(void);

main.c

#include "important_class.h"

void some_synchronous_function(){
   do_something();
   do_important_operation();
}

void do_something(){
   do_important_adjustment(5.0);
}

void async(){
   if(does_important_condition_exist()){
       //whatever
   }
}

main(){
    some_synchronous_function();
}

実装は、important_varにあまり多くの場所からアクセスしない比較的単純なアプリケーションではおそらく問題ありません。ただし、このクラススタイルの実装はより堅牢で拡張性が高く、新しい機能や複雑さを追加するときに保守しやすくなります。

複数のスレッドやISRがimportant_classメソッドを呼び出している場合は、このクラススタイルの実装をスレッドセーフにする必要があることに注意してください。

0
kkrambo