[C++] Mutexでリソースの排他制御をする

# mutex関連記事
https://tera1707.com/entry/2022/02/06/144447#Mutex

# やりたいこと

C++でmutexを使って排他制御を行えるようなコードを作成して、[別途書いたC#のMutexの記事](https://qiita.com/tera1707/items/b9c1d6bbda0e3054cf8d)で作ったC#と連動させて、mutexの扱い方の練習をしたい。

その他、試すうえで引っかかったことなどは、[C#のほうの記事](https://qiita.com/tera1707/items/b9c1d6bbda0e3054cf8d#%E5%AE%9F%E8%A3%85%E5%AE%9F%E9%A8%93%E3%81%99%E3%82%8B%E3%81%86%E3%81%88%E3%81%A7%E5%BC%95%E3%81%A3%E3%81%8B%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8)を参照。

# C++のサンプルコード

下記が、C++でmutexを使うサンプルコード。
(C++で画面を作るのが面倒だったのでコンソールアプリ)

- まず作るmutexをフルアクセスにするか通常アクセスにするかを選択
- そのあと、0:get  1:open  2:wait  3:release のどれを行うかを数字を入力して選択する

だけのアプリ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/0b2e5f1b-4aea-ac04-cc40-1afaa9830d28.png)

```cpp
#include <windows.h>
#include <iostream>

static HANDLE mutexh = INVALID_HANDLE_VALUE;
static HANDLE h = INVALID_HANDLE_VALUE;
std::wstring MutexName = L"Global\\MyMutex";

int main()
{
    bool end = false;

    std::wcout << L"mutex Jikken Kaishi" << std::endl;
    std::wcout << L"Full Access Mutex ni Shimasuka?" << std::endl;
    std::wcout << L"0:Normal Access   1:Full Access" << std::endl;
    int rights = 0;
    scanf_s("%d", &rights);

    std::wcout << L"0:get  1:open  2:wait  3:release  other:program end" << std::endl;

    while (!end)
    {
        int input = 0;
        scanf_s("%d", &input);

        switch (input)
        {
        case 0:
            // mutex取得
            // ※C#のnew Mutex(false, mutexName)と同じ
            if (mutexh == INVALID_HANDLE_VALUE)
            {
                if (rights != 0)
                {
                    // アプリ側がアクセスできるように、フルアクセスに設定する
                    SECURITY_ATTRIBUTES sa = { sizeof(sa) };
                    SECURITY_DESCRIPTOR SD;
                    InitializeSecurityDescriptor(&SD, SECURITY_DESCRIPTOR_REVISION);
                    SetSecurityDescriptorDacl(&SD, TRUE, NULL, FALSE);
                    sa.lpSecurityDescriptor = &SD;

                    mutexh = CreateMutex(&sa, FALSE, MutexName.c_str());
                    std::wcout << L"mutex(FullAccess) get" << std::endl;
                }
                else
                {
                    // 通常アクセス
                    mutexh = CreateMutex(NULL, FALSE, MutexName.c_str());
                    std::wcout << L"mutex get" << std::endl;
                }
            }
            else
            {
                std::wcout << L"mutex Already gotten..." << std::endl;
            }
            break;

        case 1:
            // 対象のmutexを開く
            // Openしたらその後から、他のスレッド/プロセスがWaitForSingleObject()したときに
            // そこで待ち(block)かかるようになる
            // ※C#側の「mutex.WaitOne();」と同じ
            if (h == INVALID_HANDLE_VALUE)
            {
                h = OpenMutex(MUTEX_ALL_ACCESS, FALSE, MutexName.c_str());
                std::wcout << L"mutex open" << std::endl;
            }
            else
            {
                std::wcout << L"mutex Already opened..." << std::endl;
            }
            break;

        case 2:
            if (h != INVALID_HANDLE_VALUE)
            {
                //シグナル状態になるまで待つ。
                std::wcout << L"mutex wait start" << std::endl;
                auto ret = WaitForSingleObject(h, 5000);
                std::wcout << L"mutex wait finished" << std::endl;

                if (ret == WAIT_OBJECT_0)
                {
                    std::wcout << L"mutex OK!!!d" << std::endl;
                }
                else if (ret == WAIT_ABANDONED)
                {
                    std::wcout << L"mutex Abandoned.." << std::endl;
                }
                else if (ret == WAIT_TIMEOUT)
                {
                    std::wcout << L"mutex WAIT_TIMEOUT..." << std::endl;
                }
                else if (ret == WAIT_FAILED)
                {
                    std::wcout << L"mutex Failed..." << std::endl;
                }
                else
                {
                    // CreateMutexをせずにOpenMutexしてるとFailになる
                    std::wcout << L"mutex something wrong..." << std::endl;
                }
            }
            else
            {
                std::wcout << L"mutex was not opened..." << std::endl;
            }
            break;

        case 3:
            // mutex解放
            if (h != INVALID_HANDLE_VALUE)
            {
                ReleaseMutex(h);
                CloseHandle(h);//CloseHandleも必要！(これしないとハンドルが増えていく)
                h = INVALID_HANDLE_VALUE;
                std::wcout << L"mutex(h) release" << std::endl;
            }
            if (mutexh != INVALID_HANDLE_VALUE)
            {
                CloseHandle(mutexh);
                mutexh = INVALID_HANDLE_VALUE;
                std::wcout << L"mutex(mutexh) release" << std::endl;
            }

            break;

        default:
            end = true;
            break;
        }
    }
}
```

# 実装・実験するうえで引っかかったこと

C#の記事の方で書いた「ひっかかったこと」は、同じ内容でC++側でも引っかかった。
同じような対処をしたので下記に対処方法を記載する。

#### 普通の名前のmutexにすると、他のユーザーがすでにつけているmutexの名前と同じ名前のmutexを作る/所有する、ということができてしまう

グローバルミューテックスにした。

```cpp
std::wstring MutexName = L"Global\\MyMutex";
```

#### 別のユーザーが作ったグローバルミューテックスと同じ名前のmutexを作ろうとすると、「アクセスが拒否されました」という例外になる

C++側では、下記のようにフルアクセスのmutexを作成した。

```cpp
SECURITY_ATTRIBUTES sa = { sizeof(sa) };
SECURITY_DESCRIPTOR SD;
InitializeSecurityDescriptor(&SD, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(&SD, TRUE, NULL, FALSE);
sa.lpSecurityDescriptor = &SD;

mutexh = CreateMutex(&sa, FALSE, MutexName.c_str());
```

# 以上

これでC++のmutexを使うアプリができたので、C#と連携して動かせる。
特に何をしているわけでもないが、片側でmutexを取ったあとにもう片方でmutexを取ったら待ちに入って、Releaseしたら待ちが終わって、とやっているだけでなぜか結構楽しい。

ただこれを製品等で実践してバグらせたらデッドロック等起きてえらいことになりそうなので、遊ぶだけでなくちゃんと実験するようにする。

# 追記(21/02/15) ToDo

CreateMutexを行って入れば、OpenMutexをする必要はないのでは？と思ったため、調べた。
(そう思ったのは、[MSのOpenMutexのページのここ](https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-openmutexw#remarks))を見たから。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/8d67ae0d-80f3-2a4f-5965-118617347c7a.png)

試してみると、OpenMutexを削除して、CreateMutex()のみでも、上のサンプルアプリが望んでる動きとしては、うまく動いた。下記がそのときのサンプル。

```OpenMutexなし.cpp
#include <windows.h>
#include <iostream>

static HANDLE mutexh = INVALID_HANDLE_VALUE;
std::wstring MutexName = L"Global\\MyMutex";

int main()
{
    bool end = false;

    std::wcout << L"mutex Jikken Kaishi" << std::endl;
    std::wcout << L"Full Access Mutex ni Shimasuka?" << std::endl;
    std::wcout << L"0:Normal Access   1:Full Access" << std::endl;
    int rights = 0;
    scanf_s("%d", &rights);

    std::wcout << L"0:get  1:open  2:wait  3:release  other:program end" << std::endl;

    while (!end)
    {
        int input = 0;
        scanf_s("%d", &input);

        switch (input)
        {
        case 0:
            // mutex取得
            // ※C#のnew Mutex(false, mutexName)と同じ
            if (mutexh == INVALID_HANDLE_VALUE)
            {
                if (rights != 0)
                {
                    // アプリ側がアクセスできるように、フルアクセスに設定する
                    SECURITY_ATTRIBUTES sa = { sizeof(sa) };
                    SECURITY_DESCRIPTOR SD;
                    InitializeSecurityDescriptor(&SD, SECURITY_DESCRIPTOR_REVISION);
                    SetSecurityDescriptorDacl(&SD, TRUE, NULL, FALSE);
                    sa.lpSecurityDescriptor = &SD;

                    mutexh = CreateMutex(&sa, FALSE, MutexName.c_str());
                    std::wcout << L"mutex(FullAccess) get" << std::endl;
                }
                else
                {
                    // 通常アクセス
                    mutexh = CreateMutex(NULL, FALSE, MutexName.c_str());
                    std::wcout << L"mutex get" << std::endl;
                }
            }
            else
            {
                std::wcout << L"mutex Already gotten..." << std::endl;
            }
            break;

        case 1:
            if (mutexh != INVALID_HANDLE_VALUE)
            {
                //シグナル状態になるまで待つ。
                std::wcout << L"mutex wait start" << std::endl;
                auto ret = WaitForSingleObject(mutexh, 5000);
                std::wcout << L"mutex wait finished" << std::endl;

                if (ret == WAIT_OBJECT_0)
                {
                    std::wcout << L"mutex OK!!!d" << std::endl;
                }
                else if (ret == WAIT_ABANDONED)
                {
                    std::wcout << L"mutex Abandoned.." << std::endl;
                }
                else if (ret == WAIT_TIMEOUT)
                {
                    std::wcout << L"mutex WAIT_TIMEOUT..." << std::endl;
                }
                else if (ret == WAIT_FAILED)
                {
                    std::wcout << L"mutex Failed..." << std::endl;
                }
                else
                {
                    // CreateMutexをせずにOpenMutexしてるとFailになる
                    std::wcout << L"mutex something wrong..." << std::endl;
                }
            }
            else
            {
                std::wcout << L"mutex was not opened..." << std::endl;
            }
            break;

        case 2:
            // mutex解放
            if (mutexh != INVALID_HANDLE_VALUE)
            {
                ReleaseMutex(mutexh);
                CloseHandle(mutexh);//CloseHandleも必要！(これしないとハンドルが増えていく)
                mutexh = INVALID_HANDLE_VALUE;
                std::wcout << L"mutex(mutexh) release" << std::endl;
            }
            break;

        default:
            end = true;
            break;
        }
    }
}
```

※アプリが動いている間はずっとMutexを使うような場合だと、毎回CreateMutexをせずに、最初に一回Createして、使うときはOpenMutexして、アプリ落ちる時にReleaseMutexとCloseHandleをしてやればよいのでは？と思った。それも時間あるときに試したい。