인프런 커뮤니티 질문&답변

OgNiR0215님의 프로필 이미지
OgNiR0215

작성한 질문수

[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버

Sleep

SpinLock 구현 과정에 대해서 질문이 있습니다.

해결된 질문

작성

·

275

·

수정됨

0

SpinLock 에 사용되는 특의 함수,
atomic 키워드와
compare_exchange_strong() 메소드를 쓰면서 구현을 하는 과정에 대해서 들었는데,

의문점이

void Add(), Sud() 메소드에
순서상의 lock_guard() 를 먼저 실행하면 되지 않을까 생각합니다.

thread1,
thread2

두 개의 쓰레드가 하나의 locked 부울 데이터를 공유한 상태에서

동시에 lock
_guard() 를 할 수 있는 경우에서 locked 부울을 동시 접근하는 경우도 있겟지만,
=> 이러면 deadlock 이 발생하겠죠.

같은 말로,
thread t1(Add);
thread t2(Add);
같은 경우도 위에 deadlock 발생하는 같은 이치라고 보시면 될꺼 같습니다.

결국에는 locked 부울 데이터를 공통적으로 사용한다는 부분에서 생기는 문제니까요.

즉, thread 1 또는 thread 2 가 동시적으로 접근하지 않는 이상 deadlock 은 발생하지는 않을 것이며,
add 메소드에서 lock
_guard() 를 했기에, 모든 작업을 다 끝내고 그 다음 add() 메소드를 실행하지 않을까 하고 생각합니다.

하지만 main 에서 Add(), Sub() 를 순차적으로 실행했기에 Add() 에 먼저 thread1, thread2 가 동시에 수행을 한다해도 결국에는 Add() 메소드에서 lock_guard 를 둘 중 하나가 먼저 수행하기 때문에
Add() 메소드를 먼저 접하는 thread 가 수행을 하고,
그 뒤의 Sub() 메소드 또한 실행을 하지만,
이 또한 thread 1 또는 thread 2 둘 중 하나가 lock_guard() 를 하고 있는 상태이기 때문에 다른 스레드가 _locked 부울에 접근을 하지 못하는 이유로 deadlock 이 발생하지 않고 원하는 값이 출력되지 않을까 생각했고, 실제로도 0 값이 나오기는 했습니다.

좀 더 정확하게는,
멀티스레드가 순차적으로 수행하지 않고 동시 다발적으로 실행이 된다면 종류는 아마,

1. A thread Add() 실행
B thread Sub() 실행

2. B thread Sub() 실행
A thread Add() 실행

3. A, B thread Add(), Sub() 동시 실행

같은 경우의 수가 더 있을 수도 있지만 현재는 여기 까지 밖에 볼 수 없었습니다.

만약 main() 함수에서 3번 경우에서 두 메소드에서 동시 다발적으로 처리를 수행한다면 deadlock 이 테스트를 하면서 운이 좋게 안 뜬걸로 볼 수있지만,

순차적으로 Add() -> Sub() 식이라면, 즉 1번 경우라면 SpinLock 을 처음 방식처럼 구현하는 상태에서,
lock_guard() 메소드를 순서를 바꿔서 처리하는 방법도 옳은 방법으로도 되는지 알고 싶습니다.

int32 sum;

class SpinLock

{

public:

void lock(){

 

while (_locked){

}

_locked = true;

}

void unlock()

{

_locked = false;

}

private:

bool _locked = false;

};

void Add()

{

lock_guard<SpinLock> guard(spinLock);

for (int32 i = 0; i < 10'000; ++i)

{

sum++;

}

}

void Sub()

{

lock_guard<SpinLock> guard(spinLock);

for (int32 i = 0; i < 10'000; ++i)

{

sum--;

}

}

void main()
{

thread t1(Add);

thread t2(Sub);

t1.join();

t2.join();

cout << sum << endl;
}

.. 생략

답변 1

1

Rookiss님의 프로필 이미지
Rookiss
지식공유자

동시에 lock_guard() 를 할 수 있는 경우에서 locked 부울을 동시 접근하는 경우도 있겟지만,
=> 이러면 deadlock 이 발생하겠죠.

=> DeadLock을 잘못 이해하신 것 같네요. 경합 상황이 꼭 데드락이 아닙니다.
데드락은 정말 피치못할 사정으로 프로그램이 크래시 날 정도로 심각하게
락을 놔주지 않는 경우이고, 잠시 대기하는 것은 데드락이라고 하지 않습니다.
그리고 lock_guard도 단순하게 Lock을 Acquire하는 헬퍼 클래스이고
Acquire를 구현하려면 결국 CAS나 atomic 류의 변수가 필요합니다.

lock_guard<SpinLock> guard(spinLock);
락의 범위를 for문 밖으로 빼도 안 될 것은 없지만
그럴 경우 for문이 다 끝날 때까지 다른 쓰레드는 전혀 진행을 못하게 됩니다.
이게 의도한 바라면 상관없지만
보통 일감은 쪼개서 하나씩 실행하는 편이 여러모로 최적화할 방법이 많고
나중에 Job 방식의 일 분할 방법을 배우면 특히나 그렇습니다.

 

OgNiR0215님의 프로필 이미지
OgNiR0215
질문자

답변 주셔서 감사합니다 :),

for 문 도중에 다른 스레드가 작업을 할 수 없다는 것다는 것을 고려하지 않았습니다. 가장 간단한 예제로서 스레드 2개 를 소유한 상태에서 하나의 공통 데이터를 접근하는 경우였지만, 만약 수 많은 스레드, 데이터 인 경우에 강사님이 말씀하신 존버 메타는 더 이상 효율적이지 않게 되는 점에 동의합니다.

그렇다면 spinLock 의 경우에는

for 문 실행 도중 a 스레드가 작업이 끝나고 unlock 이 된 뒤, 다른 스레드 b가 같은 데이터 변수에 접근해서 수정이 가능한 원활한 작업과정을 의한 방법 중 하나로 spinLock 이 있습니다의 의도로서의 주제였다면 제가 미숙했던 점 같습니다.

마지막으로 deadLock 이라고 불릴 수 있는 상황은 스레드들이 다른 스레드를 기달리는 일반적인 상황이 아닌 프로그램이 제 기능을 수행하지 못할 정도로 프로세서가 거의 정지 되어있는 듯한 상태를 지칭하는 개념으로 제가 봐도 될까요?

한 가지 더 궁금한 점은, atomic 키워드는 강사님이 얘기하신 대로 하나의 작업 과정을 시작해서 끝이 날 때 까지 그 변수를 가지고 있는 상태로 유지하기 위한 변수 타입형이라고 볼 수도 있을까요?

감사합니다

 

 

 

 

Rookiss님의 프로필 이미지
Rookiss
지식공유자

마지막으로 deadLock 이라고 불릴 수 있는 상황은 스레드들이 다른 스레드를 기달리는 일반적인 상황이 아닌 프로그램이 제 기능을 수행하지 못할 정도로 프로세서가 거의 정지 되어있는 듯한 상태를 지칭하는 개념으로 제가 봐도 될까요?
-> 네 그렇습니다.

한 가지 더 궁금한 점은, atomic 키워드는 강사님이 얘기하신 대로 하나의 작업 과정을 시작해서 끝이 날 때 까지 그 변수를 가지고 있는 상태로 유지하기 위한 변수 타입형이라고 볼 수도 있을까요?
-> 비슷한데 사실 정확히는 CPU단에서 제공하는 명령어이고, 하드웨어 레벨에서 제어를 하는 것입니다. 데이터 버스를 일시적으로 막아서 정말 동시다발적으로 실행되는 것을 막는 등의 행동을 하지만 이거까지 우리가 알 필욘 없습니다.

OgNiR0215님의 프로필 이미지
OgNiR0215

작성한 질문수

질문하기