なまこの備忘録その2 -スレッドセーフ基礎-

# 前回の備忘録


https://qiita.com/Qiita_No_Namaco/private/20a457d45a3046bbf60e

# 前回のあらすじ

<details><summary>前回のコード</summary>

``` c++:main.cpp
#include <thread>
#include <iostream>

const int COUNT_MAX = 10000;

int main(void)
{
	int ret = 0;

	//マルチスレッド
	std::thread thread1([&]() {
		for (int c = 0; c < COUNT_MAX; c++)
		{
			++ret;
			std::cout << "thread1" << ret << std::endl;
		}
	});

	//マルチスレッド
	std::thread thread2([&]() {
		for (int c = 0; c < COUNT_MAX; c++)
		{
			++ret;
			std::cout << "thread2" << ret << std::endl;
		}
	});
	
	//スレッド1,2終了まで待つ
	thread1.join();
	thread2.join();

	std::cout << "return" << ret << std::endl;

	return 0;
}

```
</details>

前回は
「変数`ret`が非スレッドセーフなため、結果の整合性が取れない」
「そもそも加算処理自体が非スレッドセーフ」
なので、これらを**スレッドセーフ**にしたい
というところで終了しました。


# スレッドセーフの実現
現在、複数のスレッドが一つの変数(メモリ)に対して、同時に処理しているため
結果の整合性が取れていないという状態になっています。

理想は、1つのスレッドの作業中は、もう一つのスレッドは待機状態にする。
つまり、**排他制御**です。

スレッドセーフの実現には、排他制御を適切に行わなければなりません。


# std::atomic

スレッドセーフ実現のための最も基本的な方法として
`std::atomic`を使用する方法があります。

https://cpprefjp.github.io/reference/atomic/atomic.html

`std::atomic`を使用するためには、`<atomic>`をインクルードする必要があります。
~~今回のコードでは、既にiostream及びthread内でインクルードされているのか
やらなくても通ります。~~

``` c++:main.cpp
#include <thread>
#include <atomic>
#include <iostream>

const int COUNT_MAX = 10000;

//メインスレッド
int main(void)
{
	std::atomic<int> ret = 0;

	//サブスレッド1
	std::thread thread1([&]() {
		for (int c = 0; c < COUNT_MAX; c++)
		{
			++ret;
			std::cout << "thread1:" << ret << std::endl;
		}
	});

	//サブスレッド2
	std::thread thread2([&]() {
		for (int c = 0; c < COUNT_MAX; c++)
		{
			++ret;
			std::cout << "thread2:" << ret << std::endl;
		}
	});
	
	//スレッド1,2終了まで待つ
	thread1.join();
	thread2.join();

	//結果
	std::cout << "return" << ret << std::endl;

	return 0;
}
```
`std::atomic`で宣言した変数を**アトミック変数**と呼びます。

この変数への読込、書込は、**別スレッドに割り込まれることがありません。**

![MultThread_Atomic.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3940568/dc3d302c-3bae-adfb-0401-f60f4ce65fcb.png)


つまり、これで「変数`ret`がスレッドセーフ」になります。

:::note
アトミック操作(不可分操作とも)と呼ばれているそうです。
:::

## 注意点

組み込み型(int,floatなど)は特殊化が提供されていますが、
**任意の型**の場合、特殊化が提供されていないため、やや特殊な記述が必要になります。

<details><summary>任意の型の場合の実装例1</summary>

``` c++:std::atomic
    #include <atomic>

    const int COUNT = 1;
    
    /// <summary>
    /// 自作構造体(クラス)
    /// </summary>
    struct Vector2
    {
    	int x;
    	int y;
    
    	Vector2(void)
    		:x(0), y(0) {};

        void Add(void)
    	{
    		x += COUNT;
    		y += COUNT;
    	}
    };

    int main(void)
    {
        std::atomic<Vector2> vec = Vector2();

        //数値の読込
        Vector2 retVec = vec.load();
        
        retVec.Add();
        
        //数値の書込
        vec.store(retVec);

        ////これは書き込まれない
        //vec.load().Add();

        return 0;
    }
```
</details>

<details><summary>任意の型の場合の実装例2(ポインタ型)</summary>

``` c++:std::atomic
    #include <atomic>

    const int COUNT = 1;
    
    /// <summary>
    /// 自作構造体(クラス)
    /// </summary>
    struct Vector2
    {
    	int x;
    	int y;
    
    	Vector2(void)
    		:x(0), y(0) {};

        void Add(void)
    	{
    		x += COUNT;
    		y += COUNT;
    	}
    };

    int main(void)
    {
        //元の数値
        Vector2 vec = Vector2();

        std::atomic<Vector2*> vecAtomic = &vec;
        
        //書き込まれ、元の数値も変化する
        vecAtomic.load()->Add();

        return 0;
    }
```
</details>

### c++20以降

生ポインタに抵抗がある方は、`<memory>`をインクルードすることで、
`std::shared_ptr`、`std::weak_ptr`に対する特殊化が提供されるため、ご参考までに。

https://cpprefjp.github.io/reference/memory/atomic.html

# std::mutex

ここでは、「加算処理そのものをスレッドセーフにする」方法の一つとして、
`std::mutex`を使用します。

`std::mutex`を使用するためには、`<mutex>`をインクルードする必要があります。

https://cpprefjp.github.io/reference/mutex.html

前準備として
メンバに変数`ret`と`std::mutex`を宣言された変数`mutex_`(以降`mutex`)を持ち、
加算処理を行う`Count`クラスを作成しています。

<details>
<summary>Countクラス</summary>

```c++:Class Count
    
    /// <summary>
    /// 加算処理を行うクラス
    /// </summary>
    class Count 
    {
    
    public:
    	void AddRet(const char* threadName )
    	{
     		for (int c = 0; c < COUNT_MAX; c++)
    		{
    			//ロック
        		mutex_.lock();
          
        		++ret;
        		std::cout << threadName << ret << std::endl;
        		
                //ロック解除
        		mutex_.unlock();
    		}
    	}
    }
    
    private:
        const int COUNT_MAX = 10000;
        int ret = 0;
    	std::mutex mutex_;
};
```
</details>


<details>
<summary>main.cpp</summary>

```c++:main.cpp
#include <thread>
#include <iostream>
#include <mutex>
#include <functional>

//メインスレッド
int main(void)
{
    //加算処理を行うクラス
	Count counter;

	//サブスレッド1
	std::thread thread1([&]() {
         counter.AddRet("thread1:");
	});

	//サブスレッド2
	std::thread thread2([&]() {
        counter.AddRet("thread2:");
	});
	
	//スレッド1,2終了まで待つ
	thread1.join();
	thread2.join();

	//結果
	counter.PrintRet();

	return 0;
}
```
</details>

ここで重点となるのは、`Count::AddRet`関数内になります。

``` c++:Count::AddRet
    	void AddRet(const char* threadName )
    	{
      		for (int c = 0; c < COUNT_MAX; c++)
    		{
    			//ロック
        		mutex_.lock();
          
        		++ret;
        		std::cout << threadName << ret << std::endl;
        
                //ロック解除
        		mutex_.unlock();
    		}
    	}
```

`mutex_.lock()`と`mutex_.unlock()`という記述が存在しています。

`std::mutex`では、
１つのスレッドが`lock`関数を実行すると、`unlock`関数が実行されるまで、
**その処理を占有**します。

この時、別のスレッドが`AddRet`を実行しても、その処理は`lock`関数で**待ち状態**になります。

![MultThread_Mutex.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3940568/6786300d-27f4-0a99-bceb-083da412243f.png)


よって、別スレッドと同時に実行されることが無くなり、
変数`ret`の整合性も保たれているため、スレッドセーフになります。


::: note

### std::recursive_mutex

`std::mutex`と似ているものとして、`std::recursive_mutex`というものがあります。
コードの記述はほぼ変化しませんが、大きく異なる点として、
**同一スレッドで既にロックされている場合、ロック処理をそのまま通過させることができます。**

ただし、同一スレッドか判定を行っているため、
`std::mutex`より、若干のオーバーヘッドが存在しています。

:::


## ロックに関係しているクラス

`<mutex>`内には、ロック管理に関するクラスも存在しています。

基本的に、`std::mutex`の`lock`関数、及び`unlock`関数は
こちらから呼び出すことになります。


### std::lock_guard


https://cpprefjp.github.io/reference/mutex/lock_guard.html

`std::lock_guard`を使用することで、
ロック、ロック解除を**確実に**行うことが出来ます。

コンストラクタで`mutex`を渡さなければならないことに注意してください。

``` c++:Count::AddRet
    	void AddRet(const char* threadName )
    	{
          	for (int c = 0; c < COUNT_MAX; c++)
    		{
                 //lock_guardのコンストラクタでロック
        		std::lock_guard<std::mutex> lock(mutex_);
        		
                ++ret;
        		std::cout << threadName << ret << std::endl;
                
                //lock_guardのデストラクタでロック解除
    		}
    	}
```

スコープの先頭で、所有している`mutex`の`lock`関数を行い、
スコープを抜ける際に、**デストラクタ**で`mutex`の`unlock`関数を行っています。


::: note
### std::unique_lock

https://cpprefjp.github.io/reference/mutex/unique_lock.html

「ロックとロック解除を確実に行う」という点では、
`std::lock_guard`と同じですが、

こちらの方が、**より高度な操作**が可能になっています。

具体的には、
・コンストラクタではなく、後からロックを取得する(遅延ロック)
・`mutex`の所有権に関する操作
・任意のタイミングでの`mutex`のロック操作
・`try_lock`の存在
などがあります。
:::

# 最後に

`std::timed_mutex`などは、まだ勉強中です。
間違っている部分、理解が浅い部分等は、指摘して頂けると大変助かります。

# 余談

**メモリバリア**または**メモリフェンス**なる方法もあるそうです。
`std::atomic`はこれと組み合わせるべきものなんでしょうか...

https://cpprefjp.github.io/reference/atomic/memory_order.html


# 次回の備忘録
マルチスレッドを利用した簡易的な衝突判定(特殊な指摘などが無ければ)