59
62

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

C++11プログラマが知っておくべき スレッドセーフなクラス構築

Last updated at Posted at 2015-10-17

c++11 からマルチスレッドに関する標準ライブラリが本格的に整備されました。
マルチスレッドセーフなクラスを実装する方法は様々ですが、
「とりあえずこれだけ抑えておけ」的なクラス宣言サンプルを紹介します。

ThreadSafe/SomeClass.cpp
#include <mutex>

// 任意のスレッドから new / delete / 全てのメンバ関数 の呼び出しが可能な
// スレッドセーフ・クラスの実装サンプル
class   SomeClass
{
    int Value;
    int Cnt;

	/*
		std::recursive_mutexクラスは
		std::mutex とは違って
		同一スレッドからの再帰的なロック取得を許可します。
	*/
	std::recursive_mutex _mutex;
	inline  std::unique_lock<std::recursive_mutex> Locker()
	{
		return  std::unique_lock<std::recursive_mutex>( _mutex );
	}

public:
	int	IncCnt()
	{
        std::unique_lock<std::recursive_mutex> locker = Locker();
		++Cnt;
		return	Cnt;
	}

    int GetValue()
    {
        std::unique_lock<std::recursive_mutex> locker = Locker();
		IncCnt();
        return  Value;
    }

    void SetValue( int v )
    {
        std::unique_lock<std::recursive_mutex> locker = Locker();
		IncCnt();
        Value = v;
    }

    SomeClass()
    {
		// コンストラクタでの排他制御操作は不要です。
		// コンストラクタで構築中のオブジェクトには、
		// 他スレッドから並行アクセスする手段が存在しないため、
		// コンストラクタ内処理が非staticメンバ変数への
		// アクセスのみならば排他制御の必然性がありません。
		Cnt = 0;
        SetValue(0);
    }
};

unique_lockテンプレートクラスは、std::mutex の lock() / unlock() を
コンストトラクタとデストラクタで確実に呼び出すための標準クラスです。

このサンプルではstd::mutexではなく、std::recursive_mutex を使用しています。
std::recursive_mutex は異なるスレッド間で使用するクラスメンバ変数の
Read/Writeを排他制御するためのクラスです。
std::mutexとの違いは、「同一スレッドで既にロックされている場合、そのまま通過させる」
という点です。

なぜ std::mutex を使わなかったのでしょうか?
ここでは、SomeClass::SetValue()を見てみましょう。

    void SetValue( int v )
    {
        std::unique_lock<std::recursive_mutex> locker = Locker();
		IncCnt();
        Value = v;
    }

SetValue()突入直後に recursive_mutex でlockしています。
自身のメンバ関数 IncCnt() を呼び出し後、
Valueの値をSetしています。

	int	IncCnt()
	{
        std::unique_lock<std::recursive_mutex> locker = Locker();
		++Cnt;
		return	Cnt;
	}

::IncCnt() はユーザに公開するpublicメンバ関数のため
当然、::SetValue() の突入直後も Locker() でロックしてます。
もし std::mutex を使っていた場合、既にロックされているため
ここで無限待機に陥ってしまいます。
また「正常無限待機」のため、デバッガなどでも捕捉しにくいです。

「複数スレッド間 排他制御だけをしたい」という当初の目的を達成したい場合、
std::recursive_mutex のほうが使い勝手が良いのです。
ただし、「同一スレッドチェック」が挿入されている分、
std::mutex よりも若干のオーバーヘッドがあります。

新規に、スレッドセーフなクラスを確実に手っ取り早く作りたい場合は
とりあえずstd::recursive_mutex を使用しておいて、
どうしても最適化が必要な場合のみ、後からstd::mutex に切り替えるといった
手法が無難かもしれません。

59
62
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
59
62

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?