SRW-Lock

J·2024년 9월 4일

테스트

목록 보기
3/11

CRITICAL_SECTION에 이어 Windows에서 제공하는 유저 동기화 객체인 SRW_LOCK의 동작에 대해서 몇 가지만 기록한다..

※ 개인적인 생각이 추가되어 정확하지 않은 정보일 수 있다.

SRWLOCK 구조체

typedef struct _RTL_SRWLOCK {                            
        PVOID Ptr;                                       
} RTL_SRWLOCK, *PRTL_SRWLOCK; 

정의되어 있는 SRWLOCK은 단지 void* 변수 하나를 의미한다.

테스트

#include <iostream>
#include <Windows.h>
#include <thread>

SRWLOCK srw;
#include <iostream>
#include <Windows.h>
#include <thread>
#include <vector>

SRWLOCK srw;
void ThreadFunc()
{
	AcquireSRWLockExclusive(&srw);

	std::cout << " 실행" << std::endl;

	ReleaseSRWLockExclusive(&srw);
}
int main()
{
	InitializeSRWLock(&srw);

	AcquireSRWLockExclusive(&srw);
	
	std::vector<std::thread> tv;
	for (int i = 0; i < 2; i++)
	{
		tv.push_back(std::thread(ThreadFunc));
	}
	Sleep(2000);
	ReleaseSRWLockExclusive(&srw);
	std::cout << "sleep종료" << std::endl;
	ReleaseSRWLockExclusive(&srw);
	AcquireSRWLockExclusive(&srw);
	ReleaseSRWLockExclusive(&srw);

	Sleep(INFINITE);
	AcquireSRWLockExclusive(&srw);
    for (std::thread& t : tv)
	{
		if (t.joinable())
			t.join();
	}
	return 0;
} 

값을 이리저리 바꾸고, 주석처리하면서 테스트했다.

InitializeSRWLock

  • 단순히 포인터값을 0으로 세팅해준다.

AcquireSRWLockExclusive

  1. lock bts 0을 통해 0번 비트값을 1로 변경해준다.
  2. 0번 비트가 0이 아니었다면 분기를 탄다. (이미 락을 획득했는데 다른 스레드가 접근한 경우)
  3. 적은 횟수를 루프를 돌면서 락을 획득하려는 시도를 한다.
  4. 횟수 안에 락을 획득하면 종료한다.(스핀)
  5. srw의 주소를 넣고 NtWaitForAlertByThreadId를 호출한 후 대기상태로 전환된다.

ReleaseSRWLockExclusive

  1. 포인터 값이 1이라면 lock cmpxchg를 통해 1->0으로 변경하고, 그렇지 않다면 eax에 이전 값이 담기기 때문에 아래 분기를 탄다.
  2. 이미 다른 스레드가 대기하고 있는 경우 내부적으로 값 계산을 통해 대기하고 있는 스레드의 id를 얻어서 깨워준다.
    ...(중간 생략)

또한 아무것도 없는 상태에서는 포인터값을 -1해주는 것을 확인했다.


Exclusive 테스트2

#include <iostream>
#include <Windows.h>
#include <thread>
#include <vector>

SRWLOCK srw;
void ThreadFunc()
{
	AcquireSRWLockExclusive(&srw);

	std::cout << "t 실행" << std::endl;

	ReleaseSRWLockExclusive(&srw);
}
int main()
{
	InitializeSRWLock(&srw);

	AcquireSRWLockExclusive(&srw);
	ReleaseSRWLockExclusive(&srw);
	ReleaseSRWLockExclusive(&srw);
	ReleaseSRWLockExclusive(&srw);
	ReleaseSRWLockExclusive(&srw);
	ReleaseSRWLockExclusive(&srw); // 
	
	std::vector<std::thread> tv;
	for (int i = 0; i < 1; i++)
	{
		tv.push_back(std::thread(ThreadFunc));
	}

	ReleaseSRWLockExclusive(&srw);
	AcquireSRWLockExclusive(&srw);

	std::cout << "메인 실행" << std::endl;
	ReleaseSRWLockExclusive(&srw);

	for (std::thread& t : tv)
	{
		if (t.joinable())
			t.join();
	}
	return 0;
}
}

또한 Release를 여러번 하고 Acquire를 시도했을 때
0번 비트가 1이냐 0이냐에 따라 잘 실행되고, 실행되지 않고를 확인할 수 있었다. 처음 lock 획득을 0번째 비트로만 확인하기 때문에 그렇지 않다면 후에 작업하는 과정에서 에러를 뱉을 수 있지만, 값이 ffff'fff0같은 상황이라면 락을 획득할 수 있다고 판단한다.(좋은 것인가? 나쁜 것인가?)


Shared 테스트

AcquireSRWLockShared

  1. 진입 시 srw의 포인터값을 0x00000011로 세팅한다. 여기서 상위 비트의 1이 의미하는 바는 shared를 획득한 횟수이다.

처음 진입했다면 그대로 종료한다.

2.그렇지 않다면 진입 횟수를 증가시킨다.

( 0x00000011 -> 0x00000021 ),진입 횟수가 늘어날 수록 카운팅도 늘어남.

ReleaseSRWLockShared

  1. 모든 반납이 완료되면 종료한다.

  2. 그렇지 않다면

shared 카운트 수를 줄인 후 리턴한다.


그 외..

srw를 exclusive형태로 사용하는데 shared 로 락을 획득하려고 하면 ..
(또는 shared로 사용하고 있는데 exclusive 형태로 사용하려고 하면..) WaitOnAddress 방식을 통해 대기한다.

CRITICAL_SECTION과 SRW_LOCK은 락을 획득한 스레드가 아닌 다른 스레드가 락을 해제할 수도 있다는 점도 테스트를 하면서 확인했는데 상당히 놀라웠다..


전체적인 로직은 실력이 부족해서 파악할 수 없었지만 동작에 대해서 간단하게 훑어봤다는 점에 의의를 둬야할 것 같다. 나중에 기억 안 날 때 보기 위해서 만들었는데 그때는 이 글을 보고 잘 떠올렸으면 좋겠다.

profile
낙서장

0개의 댓글