공통 질문
프로세스와 스레드 차이
멀티스레딩과 동기화 문제 (뮤텍스, 세마포어, 데드락)
CPU 스케줄링 알고리즘 (FCFS, SJF, Round Robin 등)
메모리 관리 (페이징, 세그멘테이션, 가상 메모리)
파일 시스템 및 I/O 관리
1. 프로세스와 스레드 차이
프로세스
독립적인 실행 단위
운영체제에서 실행중인 프로그램
독립적인 메모리 공간(코드, 데이터, 스택, 힙)
프로세스와 프로세스는 메모리를 공유하지 않음
스레드
프로세스 내에서 실행되는 작은 실행 단위
동일 프로세스내에서 메모리(코드, 데이터,힙)공유
스레드별로 스택, 레지스터 소유
동기화 문제가 발생할 수 있음
2. 멀티스레딩과 동기화 문제
동시성은 cpu에서 여러 프로세스를 처리할때
병렬성은 프로세스에서 여러 스레드가 실행될때 발생한다
멀티스레드
멀티스레딩 == 병렬
여러 스레드가 동시에 작업을 처리(병렬)
cpu 활용도 증가, 응답 시간 감소
동기화 문제
스레드간 공유자원(힙) 접근시, 데이터 일관성이 깨짐
문제: Race Condition, Deadlock, Starvation
Starvation(기아 상태)
특정 스레드가 cpu 자원을 얻지 못해, 실행되지 못하는 상태
다른 스레드가 독점, 우선순위가 낮아서, 계속해서 스케줄링에서 배제될때 생성
해결 방법
공장한 스케줄링 정책 적용(FIFO, RR)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class StarvationExample {
private static final Lock lock = new ReentrantLock();
public static void main(String[] args) {
// 우선순위가 다른 3개의 스레드 생성
Thread highPriorityThread = new Thread(new Task(), "High-Priority-Thread");
Thread mediumPriorityThread = new Thread(new Task(), "Medium-Priority-Thread");
Thread lowPriorityThread = new Thread(new Task(), "Low-Priority-Thread");
// 우선순위 설정
highPriorityThread.setPriority(Thread.MAX_PRIORITY); // 높은 우선순위
mediumPriorityThread.setPriority(Thread.NORM_PRIORITY); // 보통 우선순위
lowPriorityThread.setPriority(Thread.MIN_PRIORITY); // 낮은 우선순위
// 스레드 시작
lowPriorityThread.start();
mediumPriorityThread.start();
highPriorityThread.start();
}
static class Task implements Runnable {
@Override
public void run() {
while (true) {
lock.lock(); // 락 획득
try {
System.out.println(Thread.currentThread().getName() + " is executing...");
// 자원을 점유한 스레드가 너무 오래 실행
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 락 해제
}
}
}
}
}
높은 우선순위 스레드가 계속 CPU 자원을 독점하고, 낮은 우선순위 스레드는 거의 실행되지 않아 Starvation이 발생.
Race Condition(경쟁 상태)
두개 이상의 스레드가 동시에 공유 자원에 접근하여 작업할때 발생, 실행 순서에 따라 결과 값이 달라짐
해결 방법
1) synchronized
private synchronized static void increment() {
counter++;
}
2) AtomicInteger
private static AtomicInteger counter = new AtomicInteger();
private static void increment() {
counter.incrementAndGet();
}
public class RaceConditionExample {
private static int counter = 0;
public static void main(String[] args) {
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
increment();
}
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 예상 결과는 2000이지만, Race Condition으로 인해 예상치 못한 값 출력
System.out.println("Final Counter Value: " + counter);
}
private static void increment() {
// 비동기적으로 counter를 증가
counter++;
}
}
counter를 증가시키는 작업이 두 스레드에서 동시에 실행되며, 중간에 값이 덮어씌워져 예상치 못한 결과가 출력됩니다.
Deadlock(데드락)
두개 이상의 스레드가 서로 자원을 기다리며 무한 대기에 빠짐
해결 방법:
1) 자원 할당 순서 정의
모든 스레드가 자원을 특정 순서대로 요청하도록 강제하면 교착 상태를 방지할 수 있습니다.
항상 자원을 A -> B 순서로만 요청하도록 설정
두 스레드가 동시에 B -> A를 요청하는 상황 방지
public class DeadlockSolutionOrder {
private static final Object resource1 = new Object(); // 자원 A
private static final Object resource2 = new Object(); // 자원 B
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (resource1) {
System.out.println("Thread 1: Acquired resource1");
try {
Thread.sleep(100); // 작업 중
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resource2) {
System.out.println("Thread 1: Acquired resource2");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (resource1) { // 항상 resource1을 먼저 요청
System.out.println("Thread 2: Acquired resource1");
try {
Thread.sleep(100); // 작업 중
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resource2) { // 다음에 resource2를 요청
System.out.println("Thread 2: Acquired resource2");
}
}
});
thread1.start();
thread2.start();
}
}
2) 타임아웃
3) 교착 상태 회피 알고리즘 적용 ex) 은행가 알고리즘
은행가 알고리즘
가용 자원과 요청 자원을 비교.
자원 할당 후 시스템이 여전히 안전 상태인지 확인.
안전하면 자원을 할당, 그렇지 않으면 요청을 대기 상태로 둠.
import java.util.Arrays;
public class BankersAlgorithmExample {
static int[] available = {3, 3, 2}; // 가용 자원
static int[][] max = { // 최대 필요 자원
{7, 5, 3},
{3, 2, 2},
{9, 0, 2},
{2, 2, 2},
{4, 3, 3}
};
static int[][] allocation = { // 현재 할당된 자원
{0, 1, 0},
{2, 0, 0},
{3, 0, 2},
{2, 1, 1},
{0, 0, 2}
};
static int[][] need; // 추가로 필요한 자원
public static void main(String[] args) {
calculateNeed();
int process = 1; // 자원을 요청하는 프로세스
int[] request = {1, 0, 2}; // 프로세스 1이 요청하는 자원
if (canRequestBeGranted(process, request)) {
System.out.println("Request can be granted safely.");
} else {
System.out.println("Request cannot be granted safely.");
}
}
static void calculateNeed() {
need = new int[max.length][max[0].length];
for (int i = 0; i < max.length; i++) {
for (int j = 0; j < max[i].length; j++) {
need[i][j] = max[i][j] - allocation[i][j];
}
}
System.out.println("Need Matrix:");
for (int[] row : need) {
System.out.println(Arrays.toString(row));
}
}
static boolean canRequestBeGranted(int process, int[] request) {
// 요청이 Need 및 Available을 초과하면 거절
for (int i = 0; i < request.length; i++) {
if (request[i] > need[process][i] || request[i] > available[i]) {
return false;
}
}
// 자원 할당 후 시스템이 안전 상태인지 확인
for (int i = 0; i < request.length; i++) {
available[i] -= request[i];
allocation[process][i] += request[i];
need[process][i] -= request[i];
}
boolean isSafe = isSystemSafe();
// 요청 처리를 취소 (원래 상태로 복구)
for (int i = 0; i < request.length; i++) {
available[i] += request[i];
allocation[process][i] -= request[i];
need[process][i] += request[i];
}
return isSafe;
}
static boolean isSystemSafe() {
int[] work = Arrays.copyOf(available, available.length);
boolean[] finish = new boolean[max.length];
for (int i = 0; i < max.length; i++) {
boolean canFinish = true;
for (int j = 0; j < work.length; j++) {
if (need[i][j] > work[j]) {
canFinish = false;
break;
}
}
if (canFinish) {
for (int j = 0; j < work.length; j++) {
work[j] += allocation[i][j];
}
finish[i] = true;
i = -1; // 다시 모든 프로세스를 검사
}
}
for (boolean f : finish) {
if (!f) return false;
}
return true;
}
}
해결방법
1. 뮤텍스(Mutex)
공유자원에 대한 상호배제를 보장하는 lock
하나의 스레드만 자원에 접근 가능
**상호 배제(Mutual Exclusion)**를 보장하는 동기화 기법
하나의 스레드만 임계 영역(Critical Section)에 접근할 수 있도록 자원을 보호
뮤텍스는 운영체제(OS) 또는 라이브러리 수준에서 제공되는 동기화 객체로, 스레드 간 상호 배제를 구현합니다.
뮤텍스의 특징
독점적 잠금: 자원을 잠근 스레드 외에는 다른 스레드가 해당 자원에 접근할 수 없음.
OS 기반: 뮤텍스는 커널 오브젝트로 관리되며, 커널 모드 전환을 필요로 함.
상태 유지: 뮤텍스는 잠금을 소유한 스레드를 추적하여 동일 스레드가 잠금을 해제할 수 있도록 강제함.
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MutexExample {
private static final Lock lock = new ReentrantLock();
public static void main(String[] args) {
Runnable task = () -> {
lock.lock(); // 뮤텍스 잠금
try {
System.out.println(Thread.currentThread().getName() + " is accessing the resource");
Thread.sleep(1000); // 자원 사용 중
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 뮤텍스 해제
}
};
Thread thread1 = new Thread(task, "Thread-1");
Thread thread2 = new Thread(task, "Thread-2");
thread1.start();
thread2.start();
}
}
비관적 락(pessimistic Lock)
자원에 접근하려는 모든 스레드가 항상 충돌이 발생할 가능성이 높다고 가정하며, 자원에 대한 접근을 미리 차단하는 방식.
데이터베이스 트랜잭션에서 발생
자원을 접근할 때마다 락을 걸어 변경 작업을 방지함.
강제 잠금: 데이터를 읽거나 수정할 때 다른 스레드/트랜잭션의 접근을 차단.
성능 비용: 락을 자주 걸고 해제하기 때문에 성능 저하가 있을 수 있음.
데드락 위험: 여러 스레드/트랜잭션 간 상호 대기 상태가 발생할 수 있음.
2. 세마포어
세마포어는 특정 자원의 동시 접근 허용 수를 관리하는 동기화 기법
자원에 접근할 수 있는 스레드(또는 프로세스)의 개수를 제한
동시성을 관리하는 카운터의 개념을 기반으로 동작
카운트 제한: N개의 스레드만 자원에 접근할 수 있음.
뮤텍스와의 차이점: 뮤텍스는 단일 스레드 접근만 허용하지만, 세마포어는 N개까지 허용 가능
응용: 네트워크 연결 제한, 데이터베이스 커넥션 풀 등 동시성 제어에 자주 사용.
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(2); // 동시에 2개 스레드만 접근 가능
Runnable task = () -> {
try {
System.out.println(Thread.currentThread().getName() + " is waiting for a permit.");
semaphore.acquire(); // 자원 획득
System.out.println(Thread.currentThread().getName() + " has acquired a permit.");
Thread.sleep(2000); // 자원 사용 중
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " is releasing a permit.");
semaphore.release(); // 자원 해제
}
};
for (int i = 0; i < 5; i++) {
new Thread(task).start();
}
}
}
// 동시에 2개 스레드만 자원을 사용할 수 있으며, 다른 스레드는 대기.
낙관적 락(optimistic lock)
낙관적 락은 데이터 충돌 가능성이 낮다고 가정하고, 동시성을 제어하는 방식입니다.
자원을 접근하기 전에 락을 거는 대신, 자원 수정 후 충돌 여부를 검증합니다.
보통 버전 번호나 타임스탬프를 사용하여 데이터 충돌을 확인합니다.
충돌 검증: 자원 접근 시 락을 걸지 않고, 업데이트 시점에 충돌 여부 확인.
성능 향상: 자원 접근 시 락을 거는 오버헤드를 줄임.
재시도 가능: 충돌 발생 시 데이터를 재읽고 다시 시도 가능.
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Version;
@Entity
public class Product {
@Id
private Long id;
private String name;
@Version
private int version; // 낙관적 락을 위한 버전 필드
// getters and setters
}
// Repository 사용 예시
public class OptimisticLockExample {
public static void main(String[] args) {
EntityManager em = /* EntityManager 생성 */;
em.getTransaction().begin();
try {
// 엔티티 로드
Product product = em.find(Product.class, 1L);
// 데이터 수정
product.setName("Updated Product");
// 커밋 시점에 충돌 여부 확인 (version 필드 사용)
em.getTransaction().commit();
} catch (Exception e) {
System.out.println("Optimistic Lock exception occurred!");
em.getTransaction().rollback();
} finally {
em.close();
}
}
}
@Version 필드가 변경되지 않았으면 업데이트가 성공.
다른 트랜잭션이 동일한 데이터를 수정하면 충돌이 발생하여 예외가 발생.
3. CPU 스케줄링 알고리즘
선점 == 시분할 시스템
작업의 시간 할당량이 정해짐
round robin
모든 프로세스에 동일한 cpu 시간을 할당
공정하다, 응답 시간이 짧다
컨텍스트 스위칭 오버헤드 발생 가능
shortest remain time
프로세스 도착시간, 처리하는 시간
프로세스의 평균 반환 시간은?
비선점
작업의 시간의 할당량이 정해지지 않음
first come first served
먼저도착한 프로세스부터 처리
Convoy Effec 인해, 평균 대기 시간이 증가할 수 있음
shortest job first
실행시간이 짧은 작업 우선 실행
priority scheduling
우선순위가 높은 프로세스 처리
Starvation 문제 발생 가능 → Aging 기법으로 해결
스케쥴링 큐
4. 메모리 관리
메모리
메모리가 클 수록 많은 프로그램을 실행하는데 유리합니다.
책상은 메모리
ram의 종류
![]() |
![]() |
주기억장치, 가장 많이 쓰임 소비전력이 낮고, 저렴, 집적도가 높아서, 대용량으로 설계하기 용이하다 |
캐시메모리에 사용됨, 소비전력이 높고, 가격이 높다, 집적도가 낮다 대용량일 필요는 없지만, 빨라야하는 장치에 사용됨 |
![]() |
![]() |
![]() |
![]() |
레지스터 vs 메모리(ram) vs usb 메모리
1. 레지스터 (Register)
정의: CPU 내부에 있는 매우 빠른 임시 데이터 저장소. CPU가 직접 접근할 수 있는 가장 빠른 기억 장치.
특징: 속도: 가장 빠름 (나노초 단위).
크기: 매우 작음 (수십~수백 개, 각 레지스터는 보통 32비트 또는 64비트 크기).
역할: 산술, 논리 연산과 데이터 전송 중 임시 데이터 저장.
접근성: CPU 내부에 있어 가장 가까운 위치.
휘발성: 전원이 꺼지면 데이터가 사라짐.
예시: 프로그램 카운터(PC), 스택 포인터(SP), 산술 연산용 레지스터(Accumulator) 등이 레지스터의 예.
2. 메모리 (RAM, Random Access Memory)
정의: CPU와 저장 장치 사이에 위치하는 휘발성 메모리. 실행 중인 프로그램과 데이터를 저장.
특징: 속도: 빠름 (레지스터보다는 느리고, 마이크로초 단위).
크기: 수 GB에서 수십 GB까지.
역할: 실행 중인 프로그램의 코드, 데이터, 그리고 운영 체제의 일부를 저장.
접근성: CPU에서 직접 접근 가능하지만, 레지스터보다는 느림.
휘발성: 전원이 꺼지면 데이터가 사라짐.
예시: DDR4, DDR5 같은 시스템 메모리가 대표적.
3. USB 메모리
정의: 플래시 메모리를 기반으로 한 비휘발성 저장 장치. 데이터의 장기 저장에 사용.
특징: 속도: 느림 (RAM보다 훨씬 느림, 밀리초 단위).
크기: 수 GB에서 수 TB까지.
역할: 데이터 백업, 외부 데이터 이동 및 저장.
접근성: CPU가 직접 접근하지 않으며, USB 포트를 통해 시스템에 연결.
휘발성: 비휘발성으로, 전원이 꺼져도 데이터가 유지됨.
예시: USB 3.0/3.1/3.2에 따라 속도가 다름. 일반적으로 USB 3.2가 더 빠름.
캐시메모리란?

cpu에 가까울 수록 빠르다


예상이 맞았다 캐시 hit



virtual memory(가상 메모리)
물리적 메모리 크키보다 큰 프로그램 실행 가능함
demand paging 필요한 페이지만 로드
스왑 영역 활용(디스크-메모리 간 데이터 교환)
방법: Paging, Segmentation
메모리 관리 기법
스와핑
현재 사용되지 않는 프로세스들을 보조기억장치로 쫒아냄
빈 공간에 새 프로세스 적재
효율적으로 메모리를 사용함
메모리 할당
외부단편화
프로세스별로 메모리 크기가 다르다
프로세스를 할당하기 어려울 만큼, 작은 메모리 공간들로 인해 낭비 발생!
메모리 사이에 빈공간이 발생하는데, 메모리 낭비를 발생시킨다.
![]() |
![]() |
![]() |
![]() |
Paging
메모리를 일정 크기인 페이지 단위로 나눔
페이지를 프레임에 할당하는 가상 메모리 기법
페이지 테이블 필요
- Page(논리적 메모리)--------------페논
프로세스의 논리 주소 공간으로 일정 단위로 자름
- Frame(물리적 메모리)------------프물
메모리의 물리 주소 공간을 일정 단위로 자름
모든 프로세스를 실행하기 위해서 모든 메모리를 적재할 필요가 없다는 것이다.
페이지 테이블
프로세스마다 페이지 테이블 존재
페이지 번호-------프레임번호 를 짝지어줌
cpu 입장에서 프로세스가 순서가 일정하지 않으니 빠르게 찾기 위한 이정표
메모리의 물리주소에 불연속적으로 배치 되더라도
cpu가 바라보는 논리 주소에서는 연속적으로 배치하도록 도와준다.
내부단편화
페이지가 고정이다보니, 검은색 부분이 낭비된다
그래도 외부단편화보다 낭비가 적다
PTBR(process table base register)
프로세스 별로 ------ 페이지 테이블 존재
페이지 테이블 어디에 있지?
페이지 테이블은 메모리에 있으면 느리다.
TLB
페이지 테이블의 캐시
demain paging
Segmentation
메모리를 가변 크기의 segment로 나누어 관리
프로그램 구조에 따른 메모리 할당(코드, 데이터, 스택)
내부 단편화는 줄어듦, 외부 단편화 발생
5. 파일 시스템, I/O 관리
파일 시스템
데이터를 저장하과 관리하는 방법
디스크 스케쥴링 알고리즘
first come first served
shortest seek time first
scan 엘레베이터 알고리즘으로 디스크 헤드가 한 방향으로 이동하며 요청 처리
c-scan
I/O관리
cpu와 주변 장치간 데이터 전송 관리
direct memory access: cpu를 거치지 않고, 메모리와 i/o 장치 간 직접 데이터 전송
buffering: 입출력 작업중 데이터를 입시 저장하여 처리 속도 향상
'학습 기록 (Learning Logs) > CS Study' 카테고리의 다른 글
database (0) | 2024.12.09 |
---|---|
C, C++, C# 차이 (0) | 2024.12.07 |
네트워크 (0) | 2024.11.24 |
8월 4주차 GC (0) | 2024.08.19 |
8월 4주차 JDK, JRE, JVM (0) | 2024.08.19 |