작성
·
600
0
안녕하세요.
강의 마지막 부분인 Job System 관련되서 이해하지 못한 부분이 많아서 질문을 남깁니다.
1. 유니티에서 제공하는 Job System의 개념을 서버에서 사용하는 것인가요?
자세히 알아보고 싶어서 검색해보니 유니티 엔진 내에서만 사용하는데 현재는 유니티 엔진에서가 아니라 C# 게임 서버에서
이 개념을 사용하고 있는데 유니티의 Job System 개념만을 빌려와서 서버에서 적용시켜본 것인지 궁금합니다.
그리고 유니티 내에서 사용되는 Job System은 유니티의 쓰레드 정책을 어떻게 해서 성능 향상이 되기 때문에 이해는 가는데
저희는 지금 유니티 엔진 내에서 사용한게 아니라 C# 서버에서 사용된건데 내부적으로 어떻게 좋아지는지를 잘 모르겠습니다.
클라이언트로부터 패킷이 올 때 마다 lock을 걸지 않고 Queue에다 쌓아놓는다는 것은 이해했습니다.
2. 그런데 Queue를 enqueue 하고 dequeue하는 부분에서는 어차피 lock이 걸려있는데 이게 어떠한 부분에서 좋다고
할 수 있는건가요?
단지 일련의 코드들을 Job 단위로 묶어주기만 한 것이지 어차피 해당 Job을 Queue에서 꺼내고 빼는 상황에서는 lock이 걸리는데
결국에는 똑같은 거 아닌가 그렇게 이해가 되서요..
3. 일련의 행위를 Job 단위로 나누어서 Queue로 enqueue하고 dequeue 하는 것이 쓰레드 차원에서 어떠한 부분때문에
성능 개선이 있는것인가요?
4. 클라이언트로부터 패킷이 올 때 lock을 걸지 않았기 때문에 Queue에다 일련의 Job 행위들을 넣어주는 것은 모든 워커쓰레드가
쉬지 않고 하게 되는 것이고 Queue에서 enqueue하고 dequeue하는 쓰레드는 따로 분리되는 것인가요?..
제가 이해를 잘 못해서 질문 자체부터 이상하게 느껴지실 수 있는점 죄송합니다...데 유니티의 Job System과 혼동되는 여지가 있고
어쨋든 Queue에서도 결국에는 lock을 건 상태로 넣게 되고 빼게 되는것인데.. 이 부분에 대해서 정말 이해가 가지 않습니다 ㅠㅠ
답변 2
0
안녕하세요 루키스님. 또 똑같은 주제로 질문을 드려 죄송합니다 ㅠㅠ
제가 이해한 줄 알았는데 계속해서 생각해보고 반복해서 읽어보는데 Part4에 대한 JobQueue
내용을 듣고 왔는데 한번 더 궁금증이 생겼습니다
C#에서는 일감이 계속 밀리면 ThreadPool 차원에서 쓰레드가 돌아오지 않으니까 쓰레드풀에서 계속해서 새로운 작업자 쓰레드를 가져와서 실행시키게 된다고 하셨습니다.
그리고 JobQueue를 이용하면 1개의 쓰레드만이 작업을 실행하는 것을 보장시켜준다고 하셨는데
JobQueue를 사용하는 이유는 C#의 쓰레드풀에서 계속해서 쓰레드 배치를 시키지 않기 위해서인건가요?
JobQueue를 사용하지 않으면 클라이언트로부터 패킷이 오면 lock을 잡고 브로드캐스팅하는 과정까지 기다리게 되므로
이 시간동안 C#의 쓰레드풀에서 계속해서 작업자 쓰레드를 가져오게 되는 것이고
JobQueue를 사용하게 되면 Job을 Push하는 쓰레드는 1개로 보장이 되며 Deque하는 쓰레드도 1개로 보장이 되므로
BottleNeck 현상이 줄어들면서 C#의 쓰레드풀이 작업자 쓰레드를 적게 배치하게 되는 것 인가요?
그리고 Job이라는 개념을 둔 시스템에서는 다른 애가 이미 Job 비우는 작업을 실행중일 경우
Job을 Push만 하고 바로 빠져나와 자기 할 일을 하러 간다고 하셨는데
Job에다가 일을 Enqueue할 때도 lock이 필요하고 Dequeue하는 상황에도 lock이 필요한데
Job을 비우는 작업을 실행중인 경우에는 lock을 획득하고 있는 쓰레드가 있을텐데 그 상황에서
Push를 하는 것이 어떻게 가능한 건가요?
이유라기보다는 코드를 그렇게 만든겁니다.
위에서 보면 Pop() 함수 내부에서 잠시 lock을 걸지만,
job.Execute() 부분은 lock을 걸지 않습니다.
이 부분이 추후 C++에서 예정된 Actor 모델이나 기타 MMO 원리를 이해하는데
가장 핵심적인 부분인데 이해가 안 가신다면
Part4에 나오는 내용을 복습하면서 더 고민을 해보시기 바랍니다.
Push와 Pop에서는 멀티쓰레드 환경에서 안전하게 넣고 빼기 위해 lock을 잠시 거는 것은 이해했습니다. 해당 job을 안전하게 꺼내온 이후에는 lock이 없게 되는데 해당 job을 실행하는 Execute() 메소드에서 어떻게 멀티쓰레드 환경에서 안전한 것인가요?
job도 어떻게보면 결국 일련의 과정들이고 그것을 멀티쓰레드 환경에서 실행하게 되면 Data Race가 발생하거나 꼬이게 되는 것이 아닌가요?
Part 7에서는 Flush()를 무조건 별도의 실행 쓰레드가 전담해서 실행하구요.
Part 4에서는 먼저 1번째 일감을 밀어넣는 애가 실행까지 담당하게 했습니다.
해당 부분의 코드를 복습해보시기 바랍니다.
0
유니티의 Job과는 아예 별개이니 잊으셔도 되고
제가 본 MMO 프로젝트에서 Job 혹은 Task라는 이름으로 사용되었기 때문에
강의에서도 해당 이름을 사용한 것 뿐입니다.
lock을 잡고 안 잡고의 문제는 둘째치고,
lock을 얼마나 오래 잡고 있느냐가 사실 더 중요합니다.
특정 쓰레드 A가 락을 잡은 상태에서 게임 로직을 실행하게 될 경우,
다른 쓰레드들은 A가 락을 풀때까지 하염없이 대기를 해야 하는데요.
게임 로직은 경우에 따라 꽤 오래 걸릴 수 있기 때문에 병목현상이 심해집니다.
Job이라는 개념을 둔 시스템에서는
다른 애가 이미 Job 비우는 작업을 실행중일 경우
Job을 Push만 하고 바로 빠져나와 자기 할 일을 하러 갑니다.
이렇게 병렬로 일을 처리할 수 있는지 여부가 핵심입니다.
처음 할 땐 이해하기 힘든 부분이니 잘 이해가 안 가시면 더 고민을 해보시기 바랍니다.
쓰레드가 Job을 Push 하는 행위따로 Pop 하는 행위따로 실행하면서
병렬적으로 처리한다고 이해하면 될까요?
1. 패킷이 오면 쓰레드는 JobQueue에 일감을 쌓기만 하고 다른일을 하러 간다.
2. Flush() 함수는 Update에서 루프를 돌면서 쓰레드가 JobQueue를 확인하면서 일감을 확인하고
없으면 그냥 바로 return 해버리고 다른 일을 하러 간다.
다만 JobQueue에 일감이 있는 경우에는 Pop()을 해서 해당 일감을 처리한다.
패킷이 도착했을때 쓰레드는 일감을 Queue에 넣기만 하고 해당 쓰레드는 다른 일을 하러 가며
또 루프를 돌면서 Queue를 봤는데 작업이 없으면 또 해당 쓰레드는 return 하고 다른 일을 하러
간다는 것이 핵심이 맞나요?
네 그렇습니다. [실행하는 쓰레드는 1개, 나머지 애들은 바로 빠져나감]이 핵심이고
이를 어떻게 구현할지는 본인의 취향대로 하면 됩니다.
잘 이해가 안 간다면 Job의 개념이 없이 코드를 만든다고 가정을 해보시고 실제로 해보시면 됩니다.
이동 패킷이 왔는데 '나중에' 처리할 방법이 없으니 Room에 lock을 잡고 Move 로직을 당장 실행해야겠죠? 그런데 다른 플레이어들도 동일하게 이동 패킷을 보내는 상황이라면 서로 로직을 처리하기 위해서는 lock을 잡는 상태가 될테고, 한쪽이 끝날때까지 나머지 쓰레드들은 대기를 해야 합니다. 지금은 간단하게 하기 위해 방을 하나로 테스트 해서 와닿지 않을 수도 있지만 방이 10000개라고 가정하면 차이를 실감할 수 있습니다.
JobQueue를 사용하는 이유는 C#의 쓰레드풀에서 계속해서 쓰레드 배치를 시키지 않기 위해서인건가요?
뭐 이것도 틀린 말은 아니지만, 이미 존재하는 쓰레드끼리의 병목을 줄이기 위해서가 더 큽니다.
Job을 비우는 작업을 실행중인 경우에는 lock을 획득하고 있는 쓰레드가 있을텐데 그 상황에서 Push를 하는 것이 어떻게 가능한 건가요?
이건 구현에 따라 다르지만, 핵심은 Job을 큐에서 꺼낼 때는 lock을 잡지만,
일감을 실행할 때는 lock을 안 잡습니다.