マルチスレッド、一般データ、およびミューテックス

はじめに


マルチスレッドアプリケーションを作成する場合、ほとんどの場合、共有データを操作する必要があります。共有データを同時に変更すると、非常に不快な結果を招く可能性があります。

同時アクセスから共有データをブロックするには、同期オブジェクトを使用する必要があります。

このトピックでは、ミューテックスの操作方法について説明します。これにより、作成/削除およびキャプチャ/リリースに関連する潜在的なエラーの数が大幅に削減されます。

ミューテックスの削除に失敗すると、メモリリークが発生し、不足するとデータが不正になり、リリースしないと共有データを操作するすべての機能がブロックされます。

以下は、WindowsおよびUnixでのmutexの動作です。他の同期オブジェクトを使用する場合も同様の考え方を使用できます。

このアイデアは、「リソース割り当て-初期化(RAII)」方法論の特殊なケースです。



ミューテックスの作成、構成、および削除


まず、CAutoMutexクラスを宣言します。これは、コンストラクターでミューテックスを作成し、デストラクターで削除します。
長所:
-プロジェクト全体でmutexコードの初期化、チューニング、削除の同様のフラグメントを作成する必要はありません
-ミューテックスの自動削除とそれが占有するリソースの解放

// -, (Windows)
class CAutoMutex
{
//
HANDLE m_h_mutex;

//
CAutoMutex( const CAutoMutex&);
CAutoMutex& operator =( const CAutoMutex&);

public :
CAutoMutex()
{
m_h_mutex = CreateMutex(NULL, FALSE, NULL);
assert(m_h_mutex);
}

~CAutoMutex() { CloseHandle(m_h_mutex); }

HANDLE get () { return m_h_mutex; }
};

* This source code was highlighted with Source Code Highlighter .


Windowsでは、ミューテックスはデフォルトで再帰的ですが、Unixでは再帰的ではありません。 ミューテックスが再帰的でない場合、同じスレッドで2回キャプチャしようとすると、デッドロックが発生します。
Unixで再帰的ミューテックスを作成するには、初期化中に適切なフラグを設定する必要があります。 対応するCAutoMutexクラスは次のようになります(コンパクトのため、戻り値のチェックは表示されません)。
// -, (Unix)
class CAutoMutex
{
pthread_mutex_t m_mutex;

CAutoMutex( const CAutoMutex&);
CAutoMutex& operator =( const CAutoMutex&);

public :
CAutoMutex()
{
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&m_mutex, &attr);
pthread_mutexattr_destroy(&attr);
}
~CAutoMutex()
{
pthread_mutex_destroy(&m_mutex);
}
pthread_mutex_t& get ()
{
return m_mutex;
}
};


* This source code was highlighted with Source Code Highlighter .


ミューテックスのキャプチャとリリース


前のクラスと同様に、CMutexLockクラスを宣言します。これは、コンストラクターでミューテックスを占有し、デストラクターで解放します。 このクラスの作成されたオブジェクトは、このスコープからの出口が何であったかに関係なく、自動的にミューテックスをキャプチャし、スコープの終わりに解放します。通常の出口、早期復帰、例外のスロー。 プラスは、ミューテックスを操作するための同様のコードフラグメントを作成できないことです。

// -,
class CMutexLock
{
HANDLE m_mutex;

//
CMutexLock( const CMutexLock&);
CMutexLock& operator =( const CMutexLock&);
public :
//
CMutexLock(HANDLE mutex): m_mutex(mutex)
{
const DWORD res = WaitForSingleObject(m_mutex, INFINITE);
assert(res == WAIT_OBJECT_0);
}
//
~CMutexLock()
{
const BOOL res = ReleaseMutex(m_mutex);
assert(res);
}
};

* This source code was highlighted with Source Code Highlighter .


さらに便利なように、次のマクロを宣言します。

// ,
#define SCOPE_LOCK_MUTEX(hMutex) CMutexLock _tmp_mtx_capt(hMutex);

* This source code was highlighted with Source Code Highlighter .


このマクロを使用すると、CMutexLockクラスの名前とその名前空間を覚えておく必要がなく、作成するオブジェクトの名前(_tmp_mtx_captなど)を毎回困惑させることもできません。

使用例


使用例を検討してください。

例を単純化するには、グローバルスコープでミューテックスと一般データを宣言します。
//
static CAutoMutex g_mutex;

//
static DWORD g_common_cnt = 0;
static DWORD g_common_cnt_ex = 0;

* This source code was highlighted with Source Code Highlighter .


共通データとSCOPE_LOCK_MUTEXマクロを使用する単純な関数の例:
void do_sth_1( ) throw ()
{
// ...
//
// ...

{
//
SCOPE_LOCK_MUTEX(g_mutex. get ());

//
g_common_cnt_ex = 0;
g_common_cnt = 0;

//
}

// ...
//
// ...
}

* This source code was highlighted with Source Code Highlighter .


do_sth_1()関数は、以下よりもエレガントに見えますか? do_sth_1_eq:
void do_sth_1_eq( ) throw ()
{
//
if (WaitForSingleObject(g_mutex. get (), INFINITE) == WAIT_OBJECT_0)
{
//
g_common_cnt_ex = 0;
g_common_cnt = 0;

//
ReleaseMutex(g_mutex. get ());
}
else
{
assert(0);
}
}


* This source code was highlighted with Source Code Highlighter .


次の例では、関数の出口点が3つありますが、mutexについては1つしか言及されていません(mutexロック領域の宣言)。
// -
struct Ex {};

// ,
int do_sth_2( const int data ) throw (Ex)
{
// ...
//
// ...

//
SCOPE_LOCK_MUTEX(g_mutex. get ());

int rem = data % 3;

if (rem == 1)
{
g_common_cnt_ex++;
//
throw Ex();
}
else if (rem == 2)
{
//
g_common_cnt++;
return 1;
}

//
return 0;
}

* This source code was highlighted with Source Code Highlighter .


注:私は1つの関数で複数の戻り値を使用することを支持していません。
もう少し明らかになります。
また、関数がより長く、約12の例外ポイントがある場合はどうなりますか? マクロがなければ、ReleaseMutex(...)を各マクロの前に配置する必要がありましたが、ここで簡単に間違いを犯すことができます。

おわりに


上記のクラスとマクロの例は非常に単純であり、複雑なチェックが含まれておらず、ミューテックスが無限にリリースされることを期待しています。 しかし、これでも多くの場合、生活が楽になります。 そして、それが人生を楽にするなら、それを使ってみませんか?

UPD:最初のCAutoMutexクラスは誤って作成されたのではなく、代わりに2番目のCMutexLockクラスを再宣言しました。 修正済み。

UPD2:クラス内のメソッドの宣言でインラインとして不要な単語を削除しました。

UPD3:Unix用の再帰的なmutexを持つCAutoMutexクラスのバリアントが追加されました。

UPD4:C ++ブログに移動

Source: https://habr.com/ru/post/J72929/


All Articles