[C++][Windows][mutex]順番を保障したlockクラスを作る３

# この記事の対象読者
　私は「**順番を保ったロックオブジェクト**」を作ることにしました。その奮闘記の続きです。今回は新たに、OrderedLockというAPCキューを使ったクラスの原理の解説です。
# 本文
## 順番を保障したロックオブジェクトとは
```.cpp
std::mutex mtx;
void add_value(int value){
    std::lock_guard<std::mutex> lock(mtx);
    // 何か作業をする。
}
```
　上記のコードは１つのスレッドだけ通すコードです。後から来たスレッドは`std::lock_guard<std::mutex> lock(mtx)`で待たされる事になります。
　この時、最初に待たされたスレッドが、次にmutexの所有権を持てそうな気しますが、実は、**次にmutexを所有できるスレッドの順番は保障されていません。**　Windowsでは、`CriticalSection`がXPまでは、順番が保障されていましたが、現在は保障されていません。
　ですから、その機能が欲しい場合、手作りするしかなさそうです。webで検索してもそういうのは見つかりませんでした。
　前々回、前回の実験を紹介します。

https://qiita.com/GoldSmith/items/c5133646afd3047322d3

https://qiita.com/GoldSmith/items/a7edee45eec4ffaabebd


## デモコード
### プログラムの事前説明
　このプログラムは、順番を保証できない同期オブジェクトで作った、**ロックする順番を保障するプログラムを作る為のテストプログラム**です。**Ordered Lock**クラスと命名しました。

### この記事で使うソースコードへのリンク
[GitHubへのリンクはここです。Visual Studio 2022用に設定されたslnファイルもあります。](https://github.com/NewGoldSmith/SubProcess "https://github.com/NewGoldSmith/SubProcess")
　**TestProject**をスタートアッププロジェクトに設定し、ソリューションエクスプローラーから**TestOrdered.cpp**を選択し、プロパティの設定で**全般->ビルドから除外**項目を**いいえ**に設定し、**TestOrdered.cpp以外**は**はい**に設定し、ターゲットCPUをx64に設定し、`F5`を押下すると実行できます。

![TestOrdered1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3813628/1df5b980-226a-6976-4fa8-7f776035b3f0.png)

　また、記事中でいくつかのソースファイルを使いますが、その**都度**そのファイルを**全般->ビルドから除外** の設定を変更して、一つのファイルをビルド対象にしていただければ、ビルド可能になります。
　ソースコードの中にはデバッグ用のライブラリ、また、表示のギャップ時間を計測するクラス`OrCout`も含んでいます。本質ではない為、今回は説明を割愛いたします。

:::note info
 　この記事で紹介しているソースコードは、公開した時点から変更を加えている事があります。そのため、元の記事とは異なる結果を得る場合があります。また、ソースコードを機能別にディレクトリを分ける等の、改善を行う可能性があります。
:::

### フローチャート
　OrderLockクラスを使ったスレッドの流れです。これが全てと言っていいです。後は細かなルーチンを肉付けしていきました。
```mermaid
flowchart TB

    subgraph OrderedLock
        direction TB

    subgraph OverlappedThread
        direction TB

        subgraph "Lock" 
            direction TB
            style OverlappedThread fill:#00FFFF,stroke:#333,stroke-width:4px;
            QueueUserAPC1[QueueUserAPC]
            WaitForSingleObject1[WaitForSingleObject]
            QueueUserAPC1==８重なり合ったスレッド==>WaitForSingleObject1
        end
        
        subgraph "UnLock" 
            direction TB
            SetEvent1[SetEvent]
        end

        subgraph "Work"
            direction TB
            something1[somethings]
        end
        WaitForSingleObject1==９シングルスレッド==>Work
        Work==１０シングルスレッド==>SetEvent1
    
    end
    
    subgraph Secondary[Worker Thread]
        direction TB
        style Secondary fill:#ADFF2F	,stroke:#333,stroke-width:4px;
        APCQueue1[APCQueue]
        WaitForSingleObjectEx
        WaitForSingleObject2[WaitForSingleObject]
    
        subgraph コールバック関数
            direction TB
            SetEvent2[SetEvent]
        end
    
        WaitForSingleObjectEx==４ワーカースレッド==>APCQueue1
        APCQueue1-.３アラート.->WaitForSingleObjectEx
        APCQueue1==５ワーカースレッド==>コールバック関数
        コールバック関数==６ワーカースレッド==>WaitForSingleObject2
        WaitForSingleObject2==１２ワーカースレッド==>WaitForSingleObjectEx
    
    end



    end
    QueueUserAPC1--２コールバック関数-->APCQueue1
    ProcStart==１重なり合ったスレッド==>OverlappedThread
    ProcEnd1[ProcEnd]

    SetEvent1-.１１シグナル.->WaitForSingleObject2
    SetEvent2-.７シグナル.->WaitForSingleObject1

    SetEvent1==１３シングルスレッド==>ProcEnd1

```

## ソースコードの解説
　フロチャートが全てで、コードも短いのでさらっと解説します。
**OrderLock.cpp**
```OrderLock.cpp
#include "OrderLock.h"

OrderLock::OrderLock():

	__pBucket{ new bucket[NUM_LOCK] }

	,__mlBuckets(__pBucket, NUM_LOCK)

	,__hEventEndThread{[](){
		HANDLE h;
		if( !(h = CreateEvent(NULL, TRUE, FALSE, NULL)) ){
			throw std::exception("CreateEvent");
		} return h; }(), CloseHandle }

	, __hEventHost{ [](){
		HANDLE h;
		if( !(h = CreateEvent(NULL, TRUE, FALSE, NULL)) ){
			throw std::exception("CreateEvent");
		} return h; }(), CloseHandle }
	
	, __pAPCCallBack{ [](ULONG_PTR Parameter){
		bucket *pBucket = reinterpret_cast<bucket*>(Parameter);
		SetEvent(pBucket->hEvent.get());
		ResetEvent(pBucket->self->__hEventHost.get());
		WaitForSingleObject(pBucket->self->__hEventHost.get(), INFINITE);
	} }

	, __pThreadWarkerProc{ [](LPVOID pvoid)->DWORD{

		HANDLE hEvent = reinterpret_cast<HANDLE>(pvoid);
		for( ;;){
			DWORD dw = ::WaitForSingleObjectEx(hEvent, INFINITE, TRUE);
			if( dw == WAIT_IO_COMPLETION ){
				OutputDebugStringA("APC executed.\r\n");
			} else if( dw == WAIT_OBJECT_0 ){
				OutputDebugStringA("Event signaled.\r\n");
				return 0;
			} else{
				std::cerr << "err" << std::endl;
				return 123;
			}
		}
	} }
{
	if( !(__hThreadHost = CreateThread(
		NULL
		, 0
		, __pThreadWarkerProc
		, __hEventEndThread.get()
		, 0
		, NULL)) ){
		throw std::exception("CreateThread");
	};
}

OrderLock::~OrderLock(){
	SetEvent(__hEventEndThread.get());
	WaitForSingleObject(__hThreadHost, INFINITE);
	delete[]__pBucket;
}

void OrderLock::Lock(){
	bucket* pBucket = __mlBuckets.Lend();
	pBucket->self = this;
	pBucket->hThreadGest = GetCurrentThread();
	ResetEvent(pBucket->hEvent.get());
	QueueUserAPC(__pAPCCallBack, __hThreadHost, (ULONG_PTR)pBucket);
	WaitForSingleObject(pBucket->hEvent.get(),INFINITE);
	return ;
}

void OrderLock::UnLock(){
	SetEvent(__hEventHost.get());
	__mlBuckets.Return(__pCurrentBucket);
}

OrderLock::bucket::bucket():
	hEvent{ [](){HANDLE h; if( !(h = CreateEvent(NULL,TRUE,FALSE,NULL)) ){
		std::string str = debug_fnc::ENOut(GetLastError());
		throw std::exception(str.c_str());}	return h;}()
	,CloseHandle }

{}

```
**OrderLock.h**
```OrderLock.h
#include <Windows.h>
#include <iostream>
#include <memory>
#include <type_traits>
#include <exception>
#include "../CommonLib/MemoryLoan.h"
#include "../Debug_fnc/debug_fnc.h"

#pragma once
class OrderLock{
	static constexpr DWORD NUM_LOCK = 0x40;
public:
	OrderLock();
	OrderLock(const OrderLock&) = delete;
	OrderLock& operator=(const OrderLock&) = delete;
	OrderLock& operator()(const OrderLock&) = delete;
	OrderLock(OrderLock&&)noexcept = delete;
	OrderLock& operator=(OrderLock&&)noexcept = delete;
	OrderLock& operator()(OrderLock&&)noexcept = delete;
	~OrderLock();
	void Lock();
	void UnLock();
private:
	struct bucket{
		bucket();
		bucket(const bucket&) = delete;
		bucket(bucket&&) = delete;
		bucket& operator =(const bucket&) = delete;
		bucket& operator =(bucket&&)noexcept = delete;
		bucket& operator ()(const	bucket&) = delete;
		bucket& operator ()(bucket&&)noexcept = delete;
		OrderLock* self{};
		HANDLE hThreadGest{};
		std::unique_ptr<std::remove_pointer_t< HANDLE>,decltype(CloseHandle)*> hEvent;
	};
	bucket* __pBucket{};
	MemoryLoan<bucket> __mlBuckets;
	PAPCFUNC const __pAPCCallBack;
	LPTHREAD_START_ROUTINE const __pThreadWarkerProc;
	std::unique_ptr<std::remove_pointer_t<HANDLE>, decltype(CloseHandle)*> __hEventHost;
	std::unique_ptr<std::remove_pointer_t<HANDLE>, decltype(CloseHandle)*> __hEventEndThread;
	HANDLE __hThreadHost;
	bucket* __pCurrentBucket{};

};
```
　APCコールバックと共に渡される引数は、`bucket`と、いう構造体に収めています。この`bucket`のメンバー変数の、`hEvent`により、スレッドがAPCキューに、入る前に`WaitForSingleObject`によりブロックされます。キューから取り出された時、`hEvent`がシグナルにされ、ブロック解除される仕組みになっています。

# まとめ
* mutexや他の同期オブジェクトで複数のスレッドを扱う場合、コードでは見えて来ないシステムの背景も考慮しなければならない事もある事が判った
* APCキューが使えるシーンならば、これを使うのも、一つの選択肢に入るほど、軽量で確実に出来る事が判った

# 終わりに
　「[C++][Windows][mutex]順番を保障したlockクラスを作る３」の解説は以上となります。この記事が皆様の閃きや発想のきっかけになりましたら幸いです。
　また、ご意見、ご感想、ご質問など、お待ちしております。