카테고리 없음

면접 정리

LetsCoding 2026. 10. 6. 00:40

헷갈리기 쉬운개념                                            결정적인 차이

Remove(x) vs RemoveAt(x) 값 삭제 vs 인덱스 삭제
TryParse() vs Parse() 변환 실패를 false로 처리 vs 잘못된 형식이면 예외 발생 가능
Where() vs ToList() 일반적으로 지연 실행되는 쿼리 vs 그 시점의 결과를 리스트로 생성
override vs new 가상 메서드 재정의 vs 멤버 숨기기
Thread.Sleep() vs await Task.Delay() 현재 스레드를 멈춤 vs 일반적으로 스레드를 점유하지 않는 비동기 대기
Task vs Thread 작업 및 완료 상태의 추상화 vs 실제 실행 흐름
Concurrency vs Parallelism 여러 작업을 함께 진행하도록 구성 vs 같은 시점에 실제 동시 실행
Ready vs Blocked CPU 할당만 기다림 vs I/O 등의 별도 조건을 기다림
Race Condition vs Deadlock 실행 순서에 따른 잘못된 결과 vs 서로 기다려 진행 불가
lock vs SemaphoreSlim(2, 2) 한 번에 하나의 진입 보호 vs 최대 두 개의 동시 진입 허용

 

마지막으로, 코드 문제에서 특히 주의할 세 가지

① 코드를 한 줄씩만 읽지 말고 상태 변화를 추적하기

List 삭제, Queue/Stack, Round Robin, ++ 경합 문제는 머릿속으로만 계산하기보다 현재 값이나 남은 시간을 작은 표로 적으면 실수가 크게 줄어들어.

 

② 비슷한 이름의 개념은 목적을 중심으로 구분하기

멀티프로그래밍·멀티태스킹·멀티스레딩·멀티프로세싱은 서로 함께 사용될 수 있는 개념이야. 이름을 외우기보다는 각각 CPU 활용률, 작업 전환, 실행 흐름, 물리적 병렬 실행 중 무엇을 강조하는지 기억하면 돼.

 

③ 비동기와 멀티스레딩을 동일하게 생각하지 않기

이건 이번 문제 은행에서 여러 번 등장한 핵심이야.

await Task.Delay(1000);                 // 비동기 대기
await Task.Run(() => HeavyCalculation()); // CPU 계산을 ThreadPool에 전달

두 코드 모두 await를 사용하지만 내부 목적과 실행 방식은 달라.

 

단계: Task 객체 생성 및 스레드 풀 스케줄링
Task.Run(Action)을 호출하면 내부적으로 다음과 같은 일이 일어납니다.
  1. Task 객체 생성: 실행할 코드 블록을 담은 새로운 Task 인스턴스가 만들어집니다.
  2. TaskScheduler.Default 사용: .NET의 기본 스케줄러인 스레드 풀 스케줄러를 가져옵니다.
  3. 큐(Queue)에 대기: 스케줄러는 이 작업을 스레드 풀의 글로벌 작업 큐(Global Work Queue)에 밀어 넣습니다(Enqueue).

2단계: 스레드 풀의 작업 처리 방식 (두 가지 큐)
스레드 풀은 작업을 효율적으로 분배하기 위해 두 종류의 큐를 관리합니다.
  • 글로벌 큐 (Global Queue): Task.Run처럼 외부에서 들어온 새로운 작업들이 먼저 쌓이는 곳입니다. 선입선출(FIFO) 방식으로 동작합니다.
  • 로컬 큐 (Local Queue): 스레드 풀에 속한 개별 스레드들이 각자 하나씩 가지고 있는 비밀 주머니입니다. 어떤 스레드가 작업을 하다가 추가로 파생 작업을 만들면 이 로컬 큐에 쌓입니다. 후입선출(LIFO) 방식으로 캐시 효율을 높입니다.

3단계: 스레드 할당과 작업 훔치기 (Work Stealing)
  1. 글로벌 큐에 작업이 들어오면, 놀고 있는(Idle) 스레드 풀의 스레드가 작업을 하나씩 꺼내어 실행합니다.
  2. 만약 특정 스레드에 작업이 몰려있고 다른 스레드는 할 일이 없다면, 노는 스레드가 다른 스레드의 로컬 큐 맨 뒤에서 작업을 훔쳐와서 대신 처리합니다. 이를 Work Stealing 알고리즘이라고 하며, 이를 통해 모든 스레드가 골고루 일하게 만듭니다.

4단계: 상태 완료 및 반환
스레드가 Task.Run 내부의 코드를 모두 실행하고 나면, Task 객체의 상태를 RanToCompletion(완료), Faulted(예외 발생), 또는 Canceled(취소)로 업데이트합니다.
이후 해당 Task를 await하고 있던 메인 흐름이나 다른 코드가 그 결과를 받아 다음 작업을 이어 나가게 됩니다. 작업을 마친 스레드는 파괴되지 않고 다시 스레드 풀로 돌아가 대기합니다.

4. 핵심 요약: 무엇을 써야 할까?
작업 유형대표 예시올바른 구현 방법
CPU 작업 알고리즘, 이미지 연산, 복잡한 계산 Task.Run(() => DoHeavyMath())
I/O 작업 파일 읽기/쓰기, DB 쿼리, 네트워크 API 호출 await File.ReadAllTextAsync(...) (제공되는 Async 메서드 그냥 사용)
await 이후에 돌아오는 스레드가 원래 스레드인지, 아니면 다른 스레드인지 결정하는 ConfigureAwait(false)라는 개념이 있습니다. UI 프로그램(WPF/WinForms)이나 성능 최적화에서 아주 중요한데, 이 개념도 함께 알아볼까요?
1. ConfigureAwait(false)의 핵심 의미
await가 붙은 비동기 작업이 끝나면, 남은 코드를 이어받아 실행할 스레드가 필요합니다. 이때 .NET은 기본적으로 SynchronizationContext(동기화 문맥)라는 지도를 보고 원래 어떤 환경(스레드)에 있었는지 확인한 뒤, 웬만하면 원래 스레드로 돌아가려고 노력합니다.
  • ConfigureAwait(true) (기본값): "나 원래 있던 스레드(환경) 기억해 둬. 비동기 작업 끝나면 반드시 원래 그 스레드로 돌아가서 남은 코드 실행할래."
  • ConfigureAwait(false): "원래 스레드가 어디였는지 관심 없어. 작업 끝난 시점에 스레드 풀에서 노는 스레드 아무나 잡아서 남은 코드 실행해 줘."

2. 왜 굳이 다른 스레드에서 실행하나요? (장점)
원래 스레드로 돌아가려면 컨텍스트를 전환하는 비용(스케줄링 오버헤드)이 듭니다. false를 주면 이 과정을 생략하므로 성능이 향상됩니다.
더 중요한 이유는 데드락(Deadlock, 교착 상태) 방지입니다.
WPF나 WinForms 같은 UI 애플리케이션에서는 메인 UI 스레드가 하나뿐입니다. 메인 스레드가 비동기 작업을 기다리면서 멈춰(Blocking) 있는데, 비동기 작업은 끝난 뒤 "나 메인 스레드로 돌아갈래!" 하고 문을 두드리면 서로를 기다리며 프로그램이 영원히 멈추는 데드락이 발생합니다. ConfigureAwait(false)는 아예 다른 스레드에서 남은 일을 처리하므로 이 데드락을 원천 차단합니다.