web-dev-qa-db-ja.com

アプリケーションをスレッドセーフにする方法

特に、スレッドセーフとは、複数のスレッドが同じ共有データにアクセスする必要性を満たす必要があることを意味します。しかし、この定義では十分ではないようです。

誰でもお願いしますアプリケーションをスレッドセーフにするためにやるべきことをリストアップしてください。可能であれば、C/C++言語に関して回答してください。

55
ashmish2

関数をスレッドセーフにする方法はいくつかあります。

reentrantにすることができます。つまり、関数には状態がなく、グローバル変数や静的変数に触れないため、複数のスレッドから同時に呼び出すことができます。この用語は、別のスレッドがすでにその中にある間に、あるスレッドが関数に入ることを許可することに由来します。

クリティカルセクションを持つことができます。この用語はたくさん使われますが、率直に言って重要なデータを好みます。重要なセクションは、コードが複数のスレッド間で共有されるデータに触れるたびに発生します。ですから、私はその重要なデータに焦点を当てることを好みます。

mutex を適切に使用すると、スレッドの安全でない変更から適切に保護して、重要なデータへのアクセスを同期できます。ミューテックスとロックは非常に便利ですが、大きな力には大きな責任が伴います。同じスレッド内で同じmutexを2回ロックしないでください(つまり、自己デッドロック)。複数のミューテックスを取得する場合は、デッドロックのリスクが高まるため、注意する必要があります。ミューテックスを使用してデータを一貫して保護する必要があります。

すべての関数がスレッドセーフであり、すべての共有データが適切に保護されている場合、アプリケーションはスレッドセーフである必要があります。

Crazy Eddieが言ったように、これは大きなテーマです。ブーストスレッドを読んで、それに応じて使用することをお勧めします。

低レベルの警告:コンパイラはステートメントを並べ替えることができ、スレッドの安全性を損なう可能性があります。複数のコアでは、各コアに独自のキャッシュがあり、スレッドを安全に使用するにはキャッシュを適切に同期する必要があります。また、コンパイラーがステートメントの順序を変更しなくても、ハードウェアはそうするかもしれません。そのため、今日では完全に保証されたスレッドの安全性は実際には不可能です。ただし、そこから99.99%を得ることができます。この長引く警告を修正するために、コンパイラベンダーとCPUメーカーと作業が行われています。

とにかく、クラスをスレッドセーフにするためのチェックリストを探しているなら:

  • スレッド間で共有されているデータを特定します(見逃した場合、保護できません)
  • メンバーを作成するboost::mutex m_mutexそして、その共有メンバーデータにアクセスしようとするときはいつでもそれを使用します(理想的には、共有データはクラスに対してプライベートであるため、適切に保護していることがより確実になります)。
  • グローバルをクリーンアップします。とにかくグローバルは悪いものであり、グローバルでスレッドセーフなことをしようとすると幸運です。
  • staticキーワードに注意してください。実際にはスレッドセーフではありません。したがって、シングルトンを実行しようとしている場合、正しく機能しません。
  • ダブルチェックロックパラダイムに注意してください。それを使用するほとんどの人はいくつかの微妙な方法でそれを間違えます、そして、それは低レベルの警告によって破損する傾向があります。

それは不完全なチェックリストです。考えればさらに追加しますが、うまくいけば、あなたが始めるのに十分です。

56
Tim

2つのこと:

1.グローバルを使用しないようにしてください。現在グローバルがある場合は、それらをスレッドごとの状態構造体のメンバーにしてから、スレッドが構造体を共通関数に渡すようにします。

たとえば、次で始まる場合:

// Globals
int x;
int y;

// Function that needs to be accessed by multiple threads
// currently relies on globals, and hence cannot work with
// multiple threads
int myFunc()
{
    return x+y;
}

状態構造体を追加すると、コードは次のようになります。

typedef struct myState
{
   int x;
   int y;
} myState;

// Function that needs to be accessed by multiple threads
// now takes state struct
int myFunc(struct myState *state)
{
   return (state->x + state->y);
}

ここで、パラメータとしてxとyを単に渡さない理由を尋ねることができます。その理由は、この例が単純化されているためです。実際には、状態構造体には20のフィールドがあり、これらのパラメーター4〜5の関数のほとんどを渡すのは困難です。多くのパラメータではなく、1つのパラメータを渡します。

2.共有する必要のある共通のデータがスレッドにある場合、クリティカルセクションとセマフォを調べる必要があります。スレッドの1つがデータにアクセスするたびに、他のスレッドをブロックし、共有データへのアクセスが完了したらそれらのブロックを解除する必要があります。

14
Theo

クラスのメソッドに排他的にアクセスする場合は、これらの関数でロックを使用する必要があります。

さまざまなタイプのロック:

atomic_flg_lck:の使用

class SLock
{
public:
  void lock()
  {
    while (lck.test_and_set(std::memory_order_acquire));
  }

  void unlock()
  {
    lck.clear(std::memory_order_release);
  }

  SLock(){
    //lck = ATOMIC_FLAG_INIT;
    lck.clear();
  }
private:
  std::atomic_flag lck;// = ATOMIC_FLAG_INIT;
};

atomic:を使用する

class SLock
{
public:
  void lock()
  {
    while (lck.exchange(true));
  }

  void unlock()
  {
    lck = true;
  }

  SLock(){
    //lck = ATOMIC_FLAG_INIT;
    lck = false;
  }
private:
  std::atomic<bool> lck;
};

mutex:を使用する

class SLock
{
public:
  void lock()
  {
    lck.lock();
  }

  void unlock()
  {
    lck.unlock();
  }

private:
  std::mutex lck;
};

Windowsの場合:

class SLock
{
public:
  void lock()
  {
    EnterCriticalSection(&g_crit_sec);
  }

  void unlock()
  {
    LeaveCriticalSection(&g_crit_sec);
  }

  SLock(){
    InitializeCriticalSectionAndSpinCount(&g_crit_sec, 0x80000400);
  }

private:
  CRITICAL_SECTION g_crit_sec;
};

atomicおよびおよびatomic_flagは、スレッドをスピンカウントに保持します。 Mutexはスレッドをスリープさせるだけです。待機時間が長すぎる場合は、スレッドをスリープ状態にすることをお勧めします。最後の「CRITICAL_SECTION」は、時間が消費されるまでスレッドをスピンカウントに保持し、その後、スレッドはスリープ状態になります。

これらのクリティカルセクションの使用方法

unique_ptr<SLock> raiilock(new SLock());

class Smartlock{
public:
  Smartlock(){ raiilock->lock(); }
  ~Smartlock(){ raiilock->unlock(); }
};

Raiiイディオムの使用。クリティカルセクションをロックするコンストラクターとロック解除するデストラクター。

class MyClass {

   void syncronithedFunction(){
      Smartlock lock;
      //.....
   }

}

変数ロックはスタックに保存されるため、この実装はスレッドセーフおよび例外セーフです。関数スコープが終了すると(関数の終了または例外)デストラクタが呼び出されます。

これがお役に立てば幸いです。

ありがとう!!

3
GutiMac

1つのアイデアは、プログラムをキューを介して交換されるスレッドの束と考えることです。各スレッドには1つのキューがあり、これらのキューは(共有データ同期メソッド(mutexなど)とともに)すべてのスレッドと共有されます。

次に、キューがアンダーフローまたはオーバーフローしないように、プロデューサー/コンシューマーの問題を「解決」します。 http://en.wikipedia.org/wiki/Producer-consumer_problem

スレッドをローカライズしたまま、コピーをキューに送信してデータを共有し、複数のスレッドの(ほとんどの)guiライブラリや静的変数などのスレッドの安全でないものにアクセスしない限り、問題ありません。

0
Lalaland