LoginSignup
0
0

More than 1 year has passed since last update.

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

Last updated at Posted at 2020-11-27

mutex関連記事

やりたいこと

C++でmutexを使って排他制御を行えるようなコードを作成して、別途書いたC#のMutexの記事で作ったC#と連動させて、mutexの扱い方の練習をしたい。

その他、試すうえで引っかかったことなどは、C#のほうの記事を参照。

C++のサンプルコード

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

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

だけのアプリ。

image.png

#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を作る/所有する、ということができてしまう

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

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

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

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

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のページのここ)を見たから。
image.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をしてやればよいのでは?と思った。それも時間あるときに試したい。

0
0
0

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
0
0