UWSCで排他制御

以前、公式掲示板に「UWSC排他制御Javaのsyncronizedのようにできればよいのに」といった書き込みがあった。
ので、考えてみた。


単純なイベントなりでの排他制御の場合、最初に待ちに入ったスレッドが次に実行されるわけではない。
このモジュールでは、あわせてそこも解決してみた。



スクリプト

Stackモジュールに依存しています。
UWSCでスタックとキュー処理 - じゅんじゅんのきまぐれ
Synchro.uws

OPTION EXPLICIT

CALL Stack

IFB GET_UWSC_NAME = "Synchro.uws" THEN
	DIM i
	FOR i = 0 TO 9
		THREAD TestSynchroThread(i)
	NEXT

	WHILE Synchro.Count()	// 排他処理中の件数取得
		SLEEP(0.1)
	WEND
	SLEEP(0.1)	// 排他処理が終わって即終了して良いなら、いらない

	Synchro.Dispose()
ENDIF
PROCEDURE TestSynchroThread(num)
	DIM i
	PRINT num + " Start"
	FOR i = 0 TO 3
		PRINT num + " Start " + i + " " + Synchro.Start()
		SLEEP(1)	// ここ排他処理
		PRINT num + " End   " + i + " " + Synchro.End()
	NEXT
	PRINT num + " Fin!"
FEND


MODULE Synchro
	DIM _loop = FALSE, _event = NULL, _sync = NULL

	PROCEDURE Synchro
		IF _sync <> NULL THEN Stack.CloseHandle(_sync)
		_sync = Stack.CreateEventW(NULL, FALSE, TRUE, NULL)
		IF _event <> NULL THEN Stack.CloseHandle(_event)
		_event = Stack.CreateEventW(NULL, FALSE, TRUE, NULL)
	FEND

	PROCEDURE Dispose()
		DIM ret = Stack.Pop()
		WHILE ret <> EMPTY
			Stack.CloseHandle(ret)
			ret = Stack.Pop()
		WEND
		Stack.Dispose()
		IF _event <> NULL THEN Stack.CloseHandle(_event)
		_event = NULL
		IF _sync <> NULL THEN Stack.CloseHandle(_sync)
		_sync = NULL
	FEND

	PROCEDURE _StartController()
		_loop = Stack.Size()
		WHILE _loop
			Wait(_event)
			Stack.SetEvent(Stack.Top())
			_loop = Stack.Size()
		WEND
	FEND

	FUNCTION Wait(event, timeout=$7FFFFFFF, waitUnit=100)
		RESULT = Stack.WAIT_TIMEOUT
		WHILE RESULT = Stack.WAIT_TIMEOUT AND timeout > 0
			RESULT = Stack.WaitForSingleObject(event, waitUnit)
			timeout = timeout - waitUnit
		WEND
	FEND

	FUNCTION Start()
		DIM event = Stack.CreateEventW(NULL, FALSE, FALSE, NULL)
		Stack.Push(event)
		IFB !_loop THEN
			Wait(_sync)
			IF !_loop THEN THREAD _StartController()
			Stack.SetEvent(_sync)
		ENDIF
		RESULT = Wait(event)
		Stack.CloseHandle(event)
	FEND

	FUNCTION End()
		RESULT = _event <> NULL
		IF RESULT THEN RESULT = Stack.SetEvent(_event)
	FEND

	FUNCTION Count()
		RESULT = Stack.Size()
		DIM res = Stack.WaitForSingleObject(_event, 0)
		IFB res = 0 THEN
			Stack.SetEvent(_event)
		ELSEIF res = Stack.WAIT_TIMEOUT THEN
			RESULT = RESULT + 1
		ENDIF
	FEND

ENDMODULE

使い方

排他制御したい場所を、「Synchro.Start()」と「Synchro.End()」で挟めばOK。
全て終わったら、「Synchro.Dispose()」すること。

課題

排他制御が1種類しか使えない。
Stackモジュールを複数対応すれば、こちらも同様にやることは可能。