Final

기말 정리 노트

  • Haram Lee
  • 2026-04-27
  • studies / 26-1 / operating-systems

Mutex

text
pthread_mutex_lock(&m);// shared data accesspthread_mutex_unlock(&m);

Bad spinlock

text
while (l->held)    ;l->held = 1;

틀린 이유:

text
check and set not atomic

Semaphore bounded buffer

text
Semaphore mutex = 1;Semaphore empty = N;Semaphore full = 0;produce:wait(empty)wait(mutex)insertsignal(mutex)signal(full)consume:wait(full)wait(mutex)removesignal(mutex)signal(empty)

Condition variable wait

text
pthread_mutex_lock(&m);while (!condition)    pthread_cond_wait(&cv, &m);// condition true, do workpthread_mutex_unlock(&m);

Condition variable signal

text
pthread_mutex_lock(&m);condition = true;pthread_cond_signal(&cv);pthread_mutex_unlock(&m);

Atomicity bug fix

text
pthread_mutex_lock(&m);if (ptr != NULL)    use(ptr);pthread_mutex_unlock(&m);

Ordering bug fix

text
pthread_mutex_lock(&m);while (ready == 0)    pthread_cond_wait(&cv, &m);pthread_mutex_unlock(&m);

1. 제일 먼저 외울 큰 분류

text
pthread_create / pthread_join
→ thread 만들고 기다리는 함수

pthread_mutex_lock / unlock
→ critical section 보호

sem_wait / sem_post 또는 wait / signal
→ resource 개수, event 순서 제어

pthread_cond_wait / signal / broadcast
→ 어떤 조건이 만족될 때까지 잠들기

2. Thread 함수들

강의 자료의 Pthreads API는 thread creation/termination 함수로 pthread_create, pthread_exit, pthread_join을 정리하고 있어.

2.1 pthread_create

형태:

c
int pthread_create(
    pthread_t *tid,
    pthread_attr_t *attr,
    void *(*start_routine)(void *),
    void *arg
);

시험에서는 보통 이렇게 씀:

c
pthread_t tid;

pthread_create(&tid, NULL, worker, arg);

인자 순서:

text
1. &tid
   생성된 thread ID를 저장할 곳

2. NULL
   thread attribute. 보통 기본값이면 NULL

3. worker
   thread가 실행할 함수 이름

4. arg
   worker 함수에 넘길 인자

thread 함수는 반드시 이런 모양이어야 함:

c
void *worker(void *arg) {
    // thread code
    return NULL;
}

예시:

c
void *hello(void *arg) {
    printf("hello\n");
    return NULL;
}

int main() {
    pthread_t tid;

    pthread_create(&tid, NULL, hello, NULL);
    pthread_join(tid, NULL);

    return 0;
}

헷갈리면 이렇게 외워:

text
pthread_create(&tid, NULL, 함수이름, 함수인자)

2.2 pthread_join

형태:

c
int pthread_join(pthread_t tid, void **thread_return);

자주 쓰는 형태:

c
pthread_join(tid, NULL);

인자:

text
1. tid
   기다릴 thread ID
   여기서는 &tid가 아니라 tid

2. NULL
   thread의 return value를 안 받을 거면 NULL

주의:

c
pthread_create(&tid, NULL, worker, NULL);
//              ↑ &tid

pthread_join(tid, NULL);
//           ↑ tid

create는 tid를 “채워줘야” 하니까 &tid.
join은 이미 있는 tid를 “지정”하는 거라 tid.


3. Mutex 함수들

강의 자료에서 mutex 함수는 pthread_mutex_init, pthread_mutex_destroy, pthread_mutex_lock, pthread_mutex_unlock으로 정리되어 있어.

3.1 기본 형태

c
pthread_mutex_t m;

초기화:

c
pthread_mutex_init(&m, NULL);

사용:

c
pthread_mutex_lock(&m);

// critical section

pthread_mutex_unlock(&m);

삭제:

c
pthread_mutex_destroy(&m);

3.2 인자 정리

c
pthread_mutex_init(&m, NULL);
text
1. &m
   초기화할 mutex의 주소

2. NULL
   attribute. 기본값이면 NULL
c
pthread_mutex_lock(&m);
pthread_mutex_unlock(&m);
text
인자: &m
→ lock/unlock할 mutex의 주소

3.3 언제 쓰나?

공유 변수를 건드릴 때.

c
int g = 0;
pthread_mutex_t m;

void *worker(void *arg) {
    pthread_mutex_lock(&m);

    g++;

    pthread_mutex_unlock(&m);
    return NULL;
}

g++는 shared global variable을 write하므로 critical section이다. 강의 자료에서도 global variable은 thread들이 공유하고, g++가 여러 instruction으로 쪼개져 race가 생길 수 있다고 설명한다.


4. Semaphore 함수들

강의에서는 semaphore를 wait()signal()로 설명한다. POSIX에서는 보통 이름이 sem_wait()sem_post()다. 개념은 같아. Semaphore는 integer value를 가지며, wait는 감소시키고 필요하면 block, signal은 증가시키고 waiting thread 하나를 깨운다.

4.1 강의식 pseudo-code

c
wait(&S);
signal(&S);

4.2 POSIX식 이름

c
sem_wait(&S);
sem_post(&S);

시험/강의에서 wait, signal로 쓰면 그걸 따르면 됨.

4.3 의미

text
wait(&S)
→ S 값을 하나 감소
→ 값이 부족하면 기다림

signal(&S)
→ S 값을 하나 증가
→ 기다리는 thread가 있으면 하나 깨움

4.4 Binary semaphore

초기값 1.

c
Semaphore mutex = 1;

wait(&mutex);
// critical section
signal(&mutex);

의미:

text
한 번에 하나만 들어가게 함
mutex처럼 사용 가능

4.5 Counting semaphore

초기값 N.

c
Semaphore empty = N;

의미:

text
N개의 resource가 있음
동시에 최대 N개 thread가 통과 가능

5. Bounded buffer semaphore 패턴

이건 진짜 외워야 해.

초기값:

c
Semaphore mutex = 1;
Semaphore empty = N;
Semaphore full  = 0;

5.1 Producer

c
void produce(item x) {
    wait(&empty);
    wait(&mutex);

    buffer[in] = x;
    in = (in + 1) % N;

    signal(&mutex);
    signal(&full);
}

5.2 Consumer

c
item consume() {
    item x;

    wait(&full);
    wait(&mutex);

    x = buffer[out];
    out = (out + 1) % N;

    signal(&mutex);
    signal(&empty);

    return x;
}

강의 자료의 bounded buffer 정답도 producer는 wait(empty) → wait(mutex) → insert → signal(mutex) → signal(full), consumer는 wait(full) → wait(mutex) → remove → signal(mutex) → signal(empty) 순서야.

5.3 왜 이 순서인가?

Producer:

text
wait(empty)
→ 빈 칸이 있어야 넣을 수 있음

wait(mutex)
→ buffer, in을 혼자 수정

signal(mutex)
→ buffer 수정 끝

signal(full)
→ item 하나 생겼다고 알림

Consumer:

text
wait(full)
→ item이 있어야 꺼낼 수 있음

wait(mutex)
→ buffer, out을 혼자 수정

signal(mutex)
→ buffer 수정 끝

signal(empty)
→ 빈 칸 하나 생겼다고 알림

5.4 왜 wait(&mutex)를 먼저 하면 안 좋나?

잘못된 producer:

c
wait(&mutex);
wait(&empty);

만약 buffer가 full이면 producer는:

text
mutex를 잡은 채 empty를 기다림

consumer가 item을 꺼내야 empty가 생기는데, consumer는 mutex를 못 잡음.

text
producer는 empty 기다림
consumer는 mutex 기다림
→ deadlock 가능

그래서 resource semaphore 먼저, 그다음 mutex.

외우기:

text
Producer: empty 먼저, mutex 나중
Consumer: full 먼저, mutex 나중

6. Condition Variable 함수들

강의 자료에서 condition variable 함수는 pthread_cond_wait, pthread_cond_signal, pthread_cond_broadcast로 정리되어 있고, wait(cv, mutex)는 mutex가 잡힌 상태에서 호출되며 caller를 sleep시키고 mutex를 atomic하게 release한다고 설명한다.

6.1 기본 함수

c
pthread_cond_t cv;
pthread_mutex_t m;

초기화:

c
pthread_cond_init(&cv, NULL);
pthread_mutex_init(&m, NULL);

wait:

c
pthread_cond_wait(&cv, &m);

signal:

c
pthread_cond_signal(&cv);

broadcast:

c
pthread_cond_broadcast(&cv);

destroy:

c
pthread_cond_destroy(&cv);

6.2 인자 순서

c
pthread_cond_wait(&cv, &m);
text
1. &cv
   기다릴 condition variable

2. &m
   현재 잡고 있는 mutex

헷갈리면:

text
cond_wait(조건큐, 그 조건을 보호하는 락)

즉:

c
pthread_cond_wait(&not_empty, &m);

의미:

text
not_empty 조건을 기다리는데,
그 조건과 관련된 shared state는 m으로 보호한다.

6.3 pthread_cond_signal

c
pthread_cond_signal(&not_empty);

인자:

text
깨울 condition variable 하나

6.4 pthread_cond_broadcast

c
pthread_cond_broadcast(&not_empty);

인자:

text
깨울 condition variable 하나

차이:

text
signal
→ 기다리는 thread 하나 깨움

broadcast
→ 기다리는 thread 전부 깨움

7. Condition Variable 코드 패턴

7.1 Consumer

c
item consume() {
    item x;

    pthread_mutex_lock(&m);

    while (count == 0)
        pthread_cond_wait(&not_empty, &m);

    x = buffer[out];
    out = (out + 1) % N;
    count--;

    pthread_cond_signal(&not_full);

    pthread_mutex_unlock(&m);

    return x;
}

7.2 Producer

c
void produce(item x) {
    pthread_mutex_lock(&m);

    while (count == N)
        pthread_cond_wait(&not_full, &m);

    buffer[in] = x;
    in = (in + 1) % N;
    count++;

    pthread_cond_signal(&not_empty);

    pthread_mutex_unlock(&m);
}

7.3 구조만 외우면 됨

text
lock
while (bad condition)
    cond_wait(condition_variable, mutex)
do work
signal(other condition variable)
unlock

Producer에서 bad condition:

text
count == N
→ buffer full
→ 더 넣으면 안 됨
→ not_full 기다림

Consumer에서 bad condition:

text
count == 0
→ buffer empty
→ 꺼낼 게 없음
→ not_empty 기다림

8. while(변수) / while(조건) 읽는 법

C에서 while (...) 안에는 조건식이 들어간다.

c
while (조건) {
    반복할 코드
}

의미:

text
조건이 참이면 계속 반복
조건이 거짓이면 빠져나옴

C에서는:

text
0      → false
0이 아님 → true

그래서 이런 것도 가능함.

c
while (x) {
    ...
}

이건:

c
while (x != 0) {
    ...
}

와 같은 뜻.


9. while (l->held); 이건 뭐야?

c
while (l->held)
    ;

또는 한 줄로:

c
while (l->held);

여기서 ;는 empty statement야.

즉:

text
l->held가 1인 동안 아무것도 안 하고 계속 반복

뜻:

text
lock이 잡혀 있으면 기다려라

예:

c
void acquire(struct lock *l) {
    while (l->held)
        ;
    l->held = 1;
}

읽는 법:

text
while lock is held, spin.
when lock is not held, set held to 1.

문제는 이 구현은 multicore에서 틀림.
왜냐하면 while (l->held)로 검사하는 것과 l->held = 1로 잡는 것이 atomic하지 않기 때문. 강의 자료에서도 이 구현을 “initial attempt”로 보여주고, multicore에서는 atomic instruction이 필요하다고 이어진다.


10. while (count == 0)은 왜 쓰는 거야?

Consumer 입장에서:

c
while (count == 0)
    pthread_cond_wait(&not_empty, &m);

count == 0의 뜻:

text
buffer가 비어 있음
꺼낼 item이 없음

그러면 consumer는 지금 진행하면 안 됨.

그래서:

text
while buffer is empty, wait.

즉, 좋은 조건을 기다리는 게 아니라, 나쁜 조건인 동안 기다리는 것이야.

text
count == 0
→ bad condition
→ wait

count > 0
→ good condition
→ consume

Producer는 반대:

c
while (count == N)
    pthread_cond_wait(&not_full, &m);

count == N의 뜻:

text
buffer가 꽉 참
더 넣을 수 없음

그래서:

text
while buffer is full, wait.

11. 왜 if가 아니라 while?

잘못된 코드:

c
if (count == 0)
    pthread_cond_wait(&not_empty, &m);

이게 위험한 이유:

text
깨어났다고 해서 count > 0이라는 보장이 없음

가능한 상황:

text
1. Consumer A가 count==0이라 wait
2. Producer가 item 넣고 signal
3. Consumer A가 깨어남
4. 그런데 Consumer B가 먼저 실행돼서 item을 가져감
5. Consumer A가 다시 실행될 때 count==0일 수 있음

그래서 깨어난 뒤에도 다시 검사해야 해.

정답:

c
while (count == 0)
    pthread_cond_wait(&not_empty, &m);

시험 문장:

Condition variables should be used with a while loop because waking up does not guarantee that the condition is true.

또 CV는 signal history가 없어서, shared state인 count, ready 같은 변수를 기준으로 판단해야 한다. 강의 자료에서도 CV signal은 기다리는 thread가 없으면 사라지고, semaphore와 달리 history가 없다고 설명한다.


12. pthread_cond_wait(&cv, &m) 내부에서 무슨 일이 일어나나?

이 한 줄이 제일 중요해.

c
pthread_cond_wait(&cv, &m);

이 함수는 다음을 함:

text
1. 현재 thread를 cv의 waiting queue에 넣음
2. mutex m을 release함
3. thread를 sleep시킴
4. 나중에 깨어나면 mutex m을 다시 acquire함
5. return

핵심:

text
release mutex + sleep

이 두 동작이 atomic해야 함.

왜냐면 atomic하지 않으면:

text
Thread A: count == 0 확인
Thread A: wait하려고 함
Thread A: mutex release
Thread B: item 넣고 signal
Thread A: 이제 sleep

이러면 A는 signal을 놓치고 잠들 수 있음. 이게 lost wakeup.


13. Ordering bug에서 while 패턴

예:

c
int ready = 0;
pthread_mutex_t m;
pthread_cond_t cv;

Thread 2가 object가 준비될 때까지 기다리는 코드:

c
pthread_mutex_lock(&m);

while (ready == 0)
    pthread_cond_wait(&cv, &m);

pthread_mutex_unlock(&m);

use_object();

Thread 1이 준비시키는 코드:

c
init_object();

pthread_mutex_lock(&m);
ready = 1;
pthread_cond_signal(&cv);
pthread_mutex_unlock(&m);

읽는 법:

text
while ready == 0
→ 아직 준비 안 됨
→ 기다려라

ready == 1
→ 준비됨
→ 진행해라

강의 자료의 ordering bug 해결 예시도 while (mtInit == 0) cond_wait(...) 형태야.


14. 헷갈리는 함수 인자 모음표

Thread

c
pthread_create(&tid, NULL, func, arg);
text
&tid  : thread ID 저장할 곳
NULL  : attr
func  : thread가 실행할 함수
arg   : func에 넘길 인자
c
pthread_join(tid, NULL);
text
tid   : 기다릴 thread
NULL  : return value 안 받음

Mutex

c
pthread_mutex_init(&m, NULL);
pthread_mutex_lock(&m);
pthread_mutex_unlock(&m);
pthread_mutex_destroy(&m);

전부 핵심은:

text
mutex는 &m 넘김

Condition variable

c
pthread_cond_init(&cv, NULL);
pthread_cond_wait(&cv, &m);
pthread_cond_signal(&cv);
pthread_cond_broadcast(&cv);
pthread_cond_destroy(&cv);

핵심:

text
wait만 인자가 2개
pthread_cond_wait(&cv, &mutex)

Semaphore

강의식:

c
wait(&S);
signal(&S);

POSIX식:

c
sem_wait(&S);
sem_post(&S);

핵심:

text
semaphore는 &S 넘김

15. 자주 나오는 코드 빈칸 패턴

15.1 Mutex critical section

c
pthread_mutex_lock(&m);

shared_data++;

pthread_mutex_unlock(&m);

15.2 CV consumer

c
pthread_mutex_lock(&m);

while (count == 0)
    pthread_cond_wait(&not_empty, &m);

remove_item();
count--;

pthread_cond_signal(&not_full);

pthread_mutex_unlock(&m);

15.3 CV producer

c
pthread_mutex_lock(&m);

while (count == N)
    pthread_cond_wait(&not_full, &m);

insert_item();
count++;

pthread_cond_signal(&not_empty);

pthread_mutex_unlock(&m);

15.4 Semaphore producer

c
wait(&empty);
wait(&mutex);

insert_item();

signal(&mutex);
signal(&full);

15.5 Semaphore consumer

c
wait(&full);
wait(&mutex);

remove_item();

signal(&mutex);
signal(&empty);

16. while 조건을 한국어로 번역하는 습관

c
while (count == 0)

count가 0인 동안
즉, buffer가 비어 있는 동안

c
while (count == N)

count가 N인 동안
즉, buffer가 가득 찬 동안

c
while (ready == 0)

ready가 0인 동안
즉, 아직 준비되지 않은 동안

c
while (l->held)

lock이 held인 동안
즉, lock이 잡혀 있는 동안

c
while (!done)

done이 false인 동안
즉, 아직 끝나지 않은 동안

c
while (ptr)

ptr이 NULL이 아닌 동안


17. 왜 while 안 조건이 “기다리는 조건”처럼 보이나?

이게 헷갈리는 진짜 이유야.

c
while (count == 0)
    wait();

이건 “count가 0이 되기를 기다려라”가 아니야.

정확히는:

text
count가 0인 동안 기다려라.
count가 0이 아니게 되면 진행해라.

즉 while 안에는 보통:

text
내가 진행하면 안 되는 조건

을 넣는다.

정리:

text
consumer:
while (buffer empty) wait

producer:
while (buffer full) wait

ordering:
while (not ready) wait

spinlock:
while (lock held) spin

18. 마지막 암기 카드

text
pthread_create(&tid, NULL, func, arg)
→ thread 생성

pthread_join(tid, NULL)
→ thread 끝날 때까지 기다림

pthread_mutex_lock(&m)
→ lock 잡기

pthread_mutex_unlock(&m)
→ lock 풀기

pthread_cond_wait(&cv, &m)
→ cv에서 기다림, m은 자동으로 release 후 reacquire

pthread_cond_signal(&cv)
→ cv에서 기다리는 thread 하나 깨움

wait(&empty)
→ 빈 칸 하나 확보

wait(&full)
→ 찬 칸 하나 확보

wait(&mutex)
→ critical section 입장

signal(&mutex)
→ critical section 퇴장

while (bad condition)
→ 나쁜 조건인 동안 기다려라

CV에서는 if 말고 while
→ 깨어나도 조건이 참이라는 보장이 없기 때문

진짜 시험장에서는 이것만 떠올리면 돼:

text
CV 패턴:
lock
while (bad condition)
    cond_wait(cv, lock)
work
signal(other_cv)
unlock

Semaphore bounded buffer:
producer: empty → mutex → insert → mutex → full
consumer: full → mutex → remove → mutex → empty
Discussion