[말랑 퀴즈] 26/08/24 해답

게시:     수정

카테고리:

태그:

제 말랑말랑 퀴즈 생성기는 이곳에서 확인하실 수 있습니다.

말랑말랑 퀴즈 — 해답지 ✅

날짜: 2026-08-24 문제 수: 5문제 총점: 23.2/50 (46점) 🔴 다시 공부해요

📊 채점 결과

문항 유형 난이도 결과 점수
Q1 서술형 🔴 어려움 🔺 부분 정답 1.0 / 10
Q2 OX 🟡 보통 ❌ 오답 0 / 10
Q3 객관식 🟡 보통 ⭕ 정답 10 / 10
Q4 빈칸 🟢 쉬움 🔺 부분 정답 9.2 / 10
Q5 서술형 🔴 어려움 🔺 부분 정답 3.0 / 10
합계       23.2 / 50

총평

객관식(Q3)과 빈칸(Q4)은 거의 완벽했습니다. 특히 Q3은 오답 A·C·D가 전부 그럴듯하게 쓰인 문제였는데 “레코드 구조가 평면이라서 1억 개를 전부 건드려야 한다”는 인과를 정확히 골라냈습니다.

문제는 서술형 두 문항(Q1, Q5)에서 “모르겠다”가 다섯 번 나왔다는 점입니다. 그런데 답안을 뜯어 보면 실제로는 아는 게 더 있었습니다. Q5(2)에서 “스레드 1이 계속 실행되어 프로그램이 뚝뚝 끊긴다”고 쓴 건 바쁜 대기(busy wait) 를 정확히 짚은 것이고, Q5(3)의 “싱글스레딩처럼 동작한다”도 상호 배제라는 정답 근처까지 간 표현입니다. 반대로 Q5(1)은 코드에 답이 다 나와 있었습니다 — Release()PushBack() 뒤에, Wait()PopFront() 앞에 붙어 있으니 세마포어 값은 큐의 항목 수를 셀 수밖에 없습니다. 문제에 주어진 코드를 한 번 더 읽고 값이 오르내리는 순서를 따라가 보는 습관을 들이면 이런 문항은 건집니다.

Q1은 이 저장소에서 가장 자주 나오는 유형입니다 — “예외가 터지면 그 객체는 누가 지우는가?” 라는 한 가지 질문으로 풀립니다. Q2는 결론만 조금 더 의심했으면 맞힐 수 있었습니다(아래 피드백 참고). 지난 회차 87점에서 크게 떨어졌지만, 이번 세트는 어려움 두 문항이 모두 서술형이라 편차가 컸던 영향이 큽니다. 복습 포인트의 세 문항만 다시 보면 됩니다.


Q1. 🔴 어려움 — 🔺 1.0/10

문제: 아래 C++ 코드를 보자. processWidget은 첫 인자로 shared_ptr<Widget>을, 둘째 인자로 우선순위 정수를 받는다.

int priority();                                     // 예외를 던질 수 있다
void processWidget(std::shared_ptr<Widget> pw, int priority);

int main() {
    processWidget(std::shared_ptr<Widget>(new Widget), priority());
}

이 코드는 컴파일도 되고, 평소에는 잘 동작한다. 그런데 드물게 메모리 누수가 발생한다.

(1) 위 한 줄을 실행하기 위해 컴파일러가 해야 하는 연산 3가지를 쓰시오. (2) 그 연산들이 어떤 순서로 실행될 때 누수가 발생하는가? 그 순서를 쓰고, 왜 하필 그 순서에서 객체가 새는지 끝까지 설명하시오. (3) C++ 표준이 이 상황을 막아주지 않는 이유는 무엇인가? (4) 이 코드를 안전하게 고치시오. 그리고 고친 코드에서는 (2)의 시나리오가 왜 더 이상 누수로 이어지지 않는지 설명하시오.

📝 내 풀이:

(1) shared_ptr 생성자, Widget 생성자, new 객체 생성

(2,3,4) 이 부분에 대해서 잘 모르겠다.

정답:

(1) 세 가지 연산

  1. new Widget 실행 (객체 생성)
  2. shared_ptr<Widget> 생성자 호출 (그 객체를 스마트 포인터에 담기)
  3. priority() 호출

(2) 누수가 나는 실행 순서

1. new Widget 실행          ← 객체가 생성되고, 그 포인터는 아직 아무도 소유하지 않은 상태
2. priority() 호출          ← 여기서 예외 발생!
3. shared_ptr 생성자 호출   ← 실행되지 못함

new Widget으로 만들어진 원시 포인터는 1번과 3번 사이 구간에서 어떤 객체도 소유하고 있지 않다. 이 상태에서 priority()가 예외를 던지면 스택 되감기(stack unwinding)가 일어나 processWidget 호출 자체가 무산되고, 3번인 shared_ptr 생성자는 영영 실행되지 않는다.

shared_ptr이 만들어지지 않았으니 그 소멸자도 없고, 소멸자가 없으니 delete도 호출되지 않는다. 결국 그 원시 포인터를 들고 있던 것은 컴파일러가 만든 임시값 하나뿐이었는데 그마저 사라져, 힙에 만들어진 Widget주소를 아는 코드가 아무도 없는 채로 프로그램이 끝날 때까지 남는다. 이것이 메모리 누수다.

(3) 표준이 막아주지 않는 이유

C++ 표준은 함수 인자의 평가 순서를 정의하지 않는다. 컴파일러는 최적화를 위해 순서를 자유롭게 정할 수 있으므로, 어떤 컴파일러/최적화 옵션에서는 위 순서대로 실행될 수 있다. 즉 이 버그는 코드가 문법적으로 틀려서가 아니라 컴파일러에 따라 나타났다 사라졌다 하는 성격이라, 재현이 어렵고 발견도 늦다.

(4) 안전한 코드

std::shared_ptr<Widget> pw(new Widget);  // 스마트 포인터 생성을 별도의 한 문장으로 분리
processWidget(pw, priority());           // 그다음 함수 호출

문장을 나누면 컴파일러가 순서를 마음대로 정할 여지가 사라진다. 세미콜론을 넘어가는 시점에 Widget은 이미 pw의 소유가 되어 있으므로, 그 뒤 priority()가 예외를 던져도 스택 되감기 과정에서 지역 객체 pw의 소멸자가 호출되고, 그 소멸자가 Widgetdelete 한다. 소유자가 생긴 뒤에 예외가 발생하기 때문에 누수가 발생하지 않는다.

참고: std::make_shared<Widget>()를 쓰면 객체 생성과 소유권 획득이 한 호출 안에서 끝나므로 같은 문제를 근본적으로 피할 수 있다.

🔍 피드백: (1)에서 shared_ptr 생성자를 짚은 것은 맞습니다(2.5점 중 1.0점). 다만 나머지 두 칸을 “Widget 생성자”와 “new 객체 생성”으로 채웠는데, 이 둘은 new Widget이라는 한 연산을 둘로 쪼갠 것이라 사실상 같은 항목입니다. 그 대신 빠진 것이 하필 priority() 호출 — 이 문제에서 예외를 던지는 유일한 주인공입니다. 세 연산을 셀 때 “이 줄에서 호출되는 함수가 몇 개인가”를 먼저 세어 보면 priority()를 놓치지 않습니다.

(2)~(4)는 미응답이라 0점입니다. 다만 이 문제는 사실 질문 하나로 풀립니다 — “지금 이 순간 예외가 터지면, 방금 new로 만든 객체를 delete 해 줄 코드가 존재하는가?” newshared_ptr 생성자 사이 구간에서는 그 답이 “없다”이고, 그래서 새는 겁니다. 그리고 이 사실이 곧 해법도 알려 줍니다 — 그 구간을 없애면 된다, 즉 new를 스마트 포인터에 담는 코드를 별도의 한 문장으로 만들면 됩니다. 항목 제목이 그대로 결론인 셈입니다.

출처: game_dev/cpp/chapter3/2025-06-13-cpp_3_17.md


Q2. 🟡 보통 — ❌ 0/10

문제: 다음 명제가 참(O)인지 거짓(X)인지 판단하라.

배틀그라운드처럼 넓은 월드에 캐릭터가 많은 게임에서는, 클라이언트가 언덕 너머의 보이지도 않는 캐릭터 정보까지 받을 필요가 없다. 그래서 서버는 가시영역 안에 있는 것만 골라서 보내 네트워크 부하를 줄인다.

이를 위해 서버는 “플레이어 각각이 볼 수 있는 캐릭터 목록” 을 실시간으로 유지한다. 이 목록 하나만 제대로 관리하면 가시영역 기반 전송에 필요한 정보는 전부 확보되므로, 그 이상의 목록을 따로 둘 필요는 없다.

📝 내 답: O

정답: X

해설:

앞부분(가시영역만 전송해 네트워크 부하를 줄인다, 플레이어별 가시 캐릭터 목록을 유지한다)은 전부 맞다. 틀린 곳은 마지막 문장, “이 목록 하나면 충분하다”는 결론이다.

서버가 유지해야 하는 정보는 양방향 두 가지다.

  1. 플레이어 각각에 대해 — 그 플레이어가 볼 수 있는 캐릭터 목록
  2. 캐릭터 각각에 대해 — 그 캐릭터를 볼 수 있는 플레이어 목록

두 번째가 필요한 이유는 정보가 흐르는 방향이 반대이기 때문이다. 1번 목록은 “이 플레이어에게 무엇을 보내줘야 하나”를 답해 주지만, 실제 게임 서버가 매 틱마다 던지는 질문은 그 반대인 “캐릭터 X가 움직였다 — 이 갱신을 누구에게 보내야 하나?” 이다. 이 질문에 1번 목록만으로 답하려면 전체 플레이어의 목록을 매번 뒤져 X가 들어 있는지 확인해야 하고, 이는 가시영역 최적화로 아끼려 했던 비용을 그대로 되살린다. 2번 목록이 있으면 X를 키로 수신자 목록을 바로 얻을 수 있다.

또한 두 목록은 서로의 갱신을 검사하는 근거도 된다. 캐릭터가 이동해 시야에 새로 들어오거나 빠져나갈 때 양쪽을 짝지어 갱신해야 목록이 어긋나지 않는다.

🔍 피드백: 판단(7점)과 근거(3점) 모두 0점입니다. 이 명제는 앞의 두 문단이 전부 참이라 그대로 읽으면 O로 보이도록 만들어져 있습니다. 이런 OX 문제에서는 마지막 문장 하나만 따로 떼어 검사하는 습관이 필요합니다 — 여기서는 “그 이상의 목록을 따로 둘 필요는 없다”가 그 문장입니다.

검사하는 방법은 간단합니다. 서버 입장에서 실제로 던져야 하는 질문을 소리 내어 말해 보세요. “캐릭터 하나가 움직였다. 이걸 누구한테 보내지?” — 이 질문에 “플레이어가 볼 수 있는 캐릭터 목록”은 답을 못 합니다. 방향이 반대니까요. 그래서 “자기 자신을 볼 수 있는 플레이어 목록” 이라는 두 번째 목록이 필요합니다. 원문에서도 필수 정보를 1번, 2번으로 나란히 두 개 적어 두고 “양방향 목록 유지가 핵심”이라고 못 박고 있습니다.

출처: server/game_server/5/2026-02-15-game_server_5_4.md


Q3. 🟡 보통 — ⭕ 10/10

문제: 이미 레코드가 1억 개 쌓인 서비스에서, 기획 변경으로 유저 데이터에 필드를 하나 추가해야 하는 상황이다. 이 상황과 관련해 관계형 데이터베이스(RDBMS)와 NoSQL을 비교한 설명으로 가장 올바른 것은?

  • A. 추가하는 필드가 null 값을 허용하도록 선언하면 실제 레코드를 건드릴 필요가 없으므로, 1억 개짜리 테이블이라도 서비스 중단 없이 즉시 필드를 추가할 수 있다.
  • B. RDBMS는 기존 레코드 1억 개 전체에 필드를 추가해야 하므로 장시간 작동이 멈출 수 있다. 이는 RDBMS의 레코드가 리스트·배열 같은 고정된 평면 구조라서 생기는 문제이며, NoSQL은 레코드가 트리나 구조체 형태를 가질 수 있어 이런 구조 변경에 상대적으로 유연하다.
  • C. 외래 키를 사용해 필드를 다른 테이블로 분리해 두면 구조 변경 비용이 사라지므로, 필드 구조가 자주 바뀌는 서비스라도 RDBMS로 계속 가는 것이 정답이다.
  • D. NoSQL이 유리한 이유는 레코드 구조가 유연해서가 아니라, 애초에 데이터를 디스크가 아닌 메모리에만 보관해 필드 추가 연산 자체가 필요 없기 때문이다.

📝 내 선택: B

정답: B

해설:

RDBMS는 필드 구조를 바꿔야 할 때마다 점점 복잡해진다. 레코드가 리스트나 배열 구조 — 즉 모든 레코드가 같은 필드 집합을 갖는 평면 구조 — 이기 때문에, 필드를 하나 추가한다는 것은 곧 기존 레코드 1억 개 전부에 그 필드를 추가한다는 뜻이다. 그래서 장시간 작동을 멈추게 된다.

반면 NoSQL은 레코드가 트리나 구조체 형태를 갖출 수 있다. 레코드마다 구조가 달라도 되므로, 새 필드는 앞으로 쓰는 레코드에만 넣으면 되고 기존 1억 개를 일괄로 뜯어고칠 필요가 없다.

  • A — 흔히 하는 오해다. null을 허용하더라도 장시간 작동을 멈춘다. 필드가 값을 갖느냐 마느냐와, 테이블의 물리적 구조를 1억 개 레코드에 걸쳐 바꾸는 작업량은 별개의 문제다.
  • C — 외래 키를 사용하더라도 마찬가지로 복잡해질 수 있다. 구조 변경 비용이 사라지는 것이 아니라, 테이블이 늘고 조인이 늘면서 형태만 바뀐 채 남는다.
  • D — NoSQL의 장점을 엉뚱한 곳에서 찾고 있다. NoSQL도 디스크에 데이터를 보관하며, 유리한 이유는 저장 매체가 아니라 레코드가 트리·구조체 형태를 가질 수 있다는 구조적 유연성이다.

🔍 피드백: 만점입니다. 이 문제는 A가 특히 함정이었습니다 — “null 허용이면 값이 없으니 건드릴 게 없다”는 논리가 언뜻 그럴듯하기 때문입니다. 그 함정을 피하고 “레코드가 평면 구조 → 그래서 1억 개 전부를 건드려야 함 → 그래서 장시간 멈춤” 이라는 인과 사슬이 온전히 들어 있는 B를 골랐습니다. 이번 회차에서 가장 잘한 문항입니다.

출처: server/game_server/8/2026-03-08-game_server_8_1.md


Q4. 🟢 쉬움 — 🔺 9.2/10

문제: 빈칸을 채우시오.

컴퓨터 네트워크 통신의 국제 표준 모델인 ① ___ 모델은 7개의 계층으로 나뉜다.

그중 2계층은 ② ___ 계층으로, 단말기들이 ③ ___ 하나에 연결된 근거리 네트워크(LAN) 안에서의 통신을 담당한다. 이 계층에서 각 단말기는 고유한 ④ ___ 주소를 가지며, 데이터는 헤더 + 페이로드 구조의 프레임 단위로 오간다.

하지만 이 방식은 한 기기가 연결할 수 있는 단말기 수에 한계가 있다. 그래서 서로 다른 LAN끼리 이어 주는 3계층, 즉 ⑤ ___ 계층이 필요하며, 이 계층에서는 ⑥ ___ 가 경로를 정해 데이터를 다른 네트워크로 전달한다.

📝 내 답: OSI, 데이터 링크, 스위치, MAC, IP, 라우터

정답:

① OSI ② 데이터 링크 ③ (네트워크) 스위치 ④ MAC ⑤ 네트워크 ⑥ 라우터

해설:

OSI 모델은 컴퓨터 네트워크 통신을 위한 국제 표준 모델로, 물리 계층(1)부터 응용 계층(7)까지 7개 계층으로 나뉜다.

  • 2계층 = 데이터 링크 계층 — LAN 내부 통신을 담당한다. 요즘 네트워크는 대부분 별 위상(Star Topology) 으로 구성되며, 단말기 사이에 네트워크 스위치가 있고 각 단말기가 스위치와 랜선으로 연결된다. 이 계층에서 단말기는 고유한 MAC 주소로 식별되고, 데이터는 헤더(송·수신자 주소) + 페이로드(실제 데이터)로 이루어진 프레임 단위로 오간다.
  • 한계 — 스위치 하나가 연결할 수 있는 단말기 수에 한계가 있고, 단말기가 많아지면 MAC 주소 관리도 어려워진다. 그래서 WAN(광역 통신망) 이 등장한다.
  • 3계층 = 네트워크 계층 — 여러 LAN 간 경로를 설정하고 데이터를 전달한다. 라우터(및 일부 L3 스위치)가 이 계층에서 동작하며, 라우팅·포워딩·세그멘테이션·인터네트워킹을 담당한다. 인터넷을 가능하게 만드는 핵심 계층이다.

정리하면 LAN → 스위치 → 2계층, WAN → 라우터 → 3계층 이다.

🔍 피드백: 빈칸 6개 중 5개(①②③④⑥)가 정확해 8.3점, ⑤에 부분 점수 0.8점을 더해 9.2점입니다.

⑤에 쓴 “IP” 는 완전히 틀린 답은 아닙니다. TCP/IP 모델에서는 이 계층을 실제로 “인터넷 계층 / IP 계층”이라 부르고, 여기서 동작하는 대표 프로토콜도 IP가 맞습니다. 다만 이 문항이 묻는 것은 OSI 7계층 표에 적힌 계층 이름이고, 거기서 3계층의 이름은 네트워크 계층입니다. 즉 IP는 그 계층에서 쓰이는 프로토콜의 이름이지, 계층 자체의 이름이 아닙니다. 같은 층을 가리키고 있다는 점은 인정해 부분 점수를 드렸습니다.

계층 이름과 그 층의 대표 주자를 짝지어 외워 두면 헷갈리지 않습니다 — 2계층(데이터 링크) → 스위치 / MAC 주소 / 프레임, 3계층(네트워크) → 라우터 / IP 주소 / 패킷. 이렇게 보면 ④에 MAC, ⑥에 라우터를 맞힌 것과 같은 줄에 IP가 놓인다는 게 보입니다.

출처: server/game_server/2/2025-06-30-game_server_2_1.md


Q5. 🔴 어려움 — 🔺 3.0/10

문제: 세마포어는 0 이상의 정수 상태 값을 가진다. Wait()를 호출하면 값이 1 감소하고, Release()를 호출하면 1 증가한다. 값이 0인 상태에서 Wait()를 호출한 스레드는 값이 1 이상이 될 때까지 대기한다.

아래는 세마포어로 만든 생산자-소비자 코드다. 세마포어의 초깃값이 0인 점에 주목하라.

Queue queue;
Semaphore queueIsNotEmpty;

void Main() {
    queueIsNotEmpty = new Semaphore(0);   // 초깃값 0
}

void Thread1() {                          // 소비자
    while (true) {
        queueIsNotEmpty.Wait();
        queue.PopFront();
    }
}

void Thread2() {                          // 생산자
    while (true) {
        queue.PushBack();
        queueIsNotEmpty.Release();
    }
}

(1) 이 코드에서 세마포어의 상태 값은 무엇과 같아지는가? 그리고 초깃값을 0으로 준 이유는 무엇인가? (2) 만약 소비자 스레드에서 queueIsNotEmpty.Wait(); 한 줄을 지운다면 어떤 일이 벌어지는가? 프로그램에 실제로 나타나는 증상까지 설명하시오. (3) 세마포어의 초깃값을 1로 주면, 이 세마포어는 사실상 무엇처럼 동작하게 되는가? (4) 이 상황을 세마포어 대신 이벤트(Event)로 구현할 수도 있는데, 그럼에도 세마포어가 더 자연스러운 이유는 무엇인가?

📝 내 풀이:

(1) 모르겠다.

(2) 스레드 1이 계속 실행되어 멀티스레딩의 의미가 없어진다. 프로그램이 뚝뚝 끊긴다.

(3) 싱글스레딩처럼 동작한다.

(4) 모르겠다.

정답:

(1) 상태 값의 의미와 초깃값 0의 이유

세마포어의 상태 값은 큐에 들어 있는 항목의 개수와 동기화된다. 생산자가 PushBack()Release()로 값을 1 올리고, 소비자가 Wait()로 값을 1 내린 뒤 PopFront()로 항목을 꺼내기 때문에, 값은 곧 “아직 처리되지 않은 항목 수”를 뜻한다.

초깃값을 0으로 준 이유는 프로그램 시작 시점에 큐가 비어 있기 때문이다. 세마포어의 초깃값은 “지금 허락할 수 있는 접근 횟수”인데, 꺼낼 항목이 하나도 없는 상태에서는 소비자에게 허락해 줄 것이 없다. 따라서 소비자는 Wait()에서 대기하고, 생산자가 항목을 하나 넣어 Release()를 호출해야 비로소 깨어난다. 여기서 세마포어는 “최대 동시 접근 스레드 수 제한”이 아니라 “몇 개나 준비되었는지 세는 카운터” 로 쓰이고 있다.

(2) Wait()를 지웠을 때

소비자를 막아 주던 유일한 장치가 사라진다. Thread1while (true) 안에서 큐가 비어 있든 말든 무조건 PopFront()를 호출하게 된다.

증상은 두 가지로 나타난다.

  1. 빈 큐에서 꺼내기 시도 — 아직 아무것도 들어오지 않았거나 이미 다 꺼낸 상태에서 PopFront()가 호출되면, 큐 구현에 따라 없는 원소를 반환하거나 잘못된 메모리를 참조한다. 즉 쓰레기 값 처리 또는 크래시로 이어진다.
  2. 바쁜 대기(busy wait) — 대기 없이 루프가 최대 속도로 돌기 때문에 소비자 스레드가 CPU 코어 하나를 100% 점유한다. 아무 일도 하지 않으면서 자원만 태우고, 그 결과 다른 스레드까지 느려진다.

즉 세마포어가 하던 일은 단순한 “카운트 관리”가 아니라, 꺼낼 것이 생길 때까지 소비자를 재워 두는 것이었다.

(3) 초깃값을 1로 주면

동시에 접근할 수 있는 스레드가 최대 1개가 되므로, 이벤트나 뮤텍스와 거의 비슷하게 동작한다. 즉 상호 배제(mutual exclusion) 용도의 잠금처럼 쓰이게 된다.

(4) 세마포어가 더 자연스러운 이유

이 문제의 본질이 카운팅이기 때문이다. 큐에 항목이 3개 쌓이면 소비자는 3번 꺼낼 수 있어야 하는데, 이벤트는 기본적으로 “신호가 왔다 / 안 왔다”는 이진 상태만 표현한다. 그래서 이벤트로 같은 동작을 만들려면 항목 개수를 별도 변수로 관리하고 그 변수와 이벤트 상태를 손으로 맞춰 주어야 한다 — 생산자가 연달아 2개를 넣었는데 신호는 한 번만 남아 하나가 처리되지 않는 식의 실수가 쉽게 난다.

세마포어는 상태 값 자체가 개수를 세어 주므로 큐의 항목 수와 자동으로 동기화된다. 이런 카운팅이 필요한 상황에서는 세마포어 쪽이 훨씬 자연스럽고 구현도 깔끔하다.

🔍 피드백: 논점 4개 중 (2)에서 1.5점, (3)에서 1.5점을 받아 3.0점입니다.

(2)가 이번 답안 중 가장 좋았습니다. “스레드 1이 계속 실행되어… 프로그램이 뚝뚝 끊긴다”는 건 정답의 두 번째 논점인 바쁜 대기(busy wait) 를 정확히 짚은 겁니다. 대기 없이 루프가 최대 속도로 도니 CPU를 다 잡아먹고, 그 여파로 다른 스레드가 밀려 화면이 끊기는 것 — 인과가 맞습니다. 다만 더 먼저 터지는 증상을 놓쳤습니다. Wait()가 사라지면 큐가 비어 있는데도 PopFront()를 호출하게 되어 쓰레기 값을 읽거나 크래시가 납니다. Wait()는 “카운트를 줄이는 줄”이기 이전에 “꺼낼 게 없으면 여기서 멈춰”라는 줄이었습니다.

(3) “싱글스레딩처럼 동작한다”는 방향은 맞지만 표현이 부정확해 절반만 드렸습니다. 스레드가 하나가 되는 게 아니라, 스레드는 여전히 여러 개인데 보호 구간에 들어갈 수 있는 스레드가 한 번에 하나로 제한되는 겁니다. 이걸 부르는 이름이 상호 배제이고, 그래서 원문은 “자원 개수를 1로 설정하면 이벤트나 뮤텍스와 거의 비슷하게 동작한다”고 적고 있습니다. 세마포어(N) ⊃ 뮤텍스(1) 라는 포함 관계로 기억하세요.

(1)은 코드만 따라가면 풀 수 있었습니다. 값이 언제 오르고 내리는지만 보세요 — PushBack() 뒤에 Release()(+1), PopFront() 앞에 Wait()(−1). 항목이 들어올 때 +1, 나갈 때 −1이면 그 값은 큐에 남아 있는 항목 수일 수밖에 없습니다. 그리고 프로그램이 막 시작했을 때 큐는 비어 있으니 초깃값도 당연히 0입니다. (4) 도 같은 이야기의 연장입니다 — 세마포어는 개수를 셀 수 있고 이벤트는 “왔다/안 왔다”만 알려주므로, 항목이 3개 쌓이는 상황을 다루려면 세마포어가 자연스럽습니다.

이 문항의 핵심은 하나입니다: 여기서 세마포어는 “동시 접근 인원 제한”이 아니라 “재고 수량 카운터”로 쓰이고 있다. 이 관점만 잡으면 (1)(3)(4)가 한꺼번에 풀립니다.

출처: server/game_server/1/2025-06-18-game_server_1_14.md


📌 복습 포인트

  • Q1 스마트 포인터와 예외 안전성new 결과가 아직 아무 소유자도 없는 “빈틈 구간”이 왜 생기는지, 문장을 나누면 그 구간이 왜 사라지는지 → game_dev/cpp/chapter3/2025-06-13-cpp_3_17.md
  • Q2 가시영역 양방향 목록 — “누구에게 보내야 하나?”라는 질문에 답하려면 왜 목록이 두 개여야 하는지 → server/game_server/5/2026-02-15-game_server_5_4.md
  • Q4 OSI 계층 이름 vs 프로토콜 이름 — 3계층의 이름은 “네트워크 계층”, IP는 그 계층의 프로토콜 → server/game_server/2/2025-06-30-game_server_2_1.md
  • Q5 카운팅 세마포어 — 세마포어 값 = 큐의 항목 수, Wait()는 “없으면 멈춰”라는 뜻, 초깃값 1이면 뮤텍스 → server/game_server/1/2025-06-18-game_server_1_14.md

MallangQuiz 카테고리 내 다른 글 보러가기

댓글남기기