OS
[운영체제] 11강 - 16강 Concurrency; Concepts and Case Studies, Locks, Conditional Variables, Semaphore, Concurrent Data Structures, Deadlock
김치말이삼겹살
2024. 3. 29. 16:01
11강 Concurrency, Concepts and Case Studies
- Thread : 여러개의 PC + 자체 레지스터로 구성 → 스택에 보관
- Thread들은 서로 같은 주소 공간을 공유
- Thread를 쓰는 이유
- 병렬 처리 간단 → 하나의 작업 여러 스레드가 작업 → 시간 줄일 수 잇음
- 느린 I/O로 프로그램 수행되는 것이 차단되는 것을 방지
- TCD: thread register 상태 기록 (PCB와 유사)
- Race Condition : 자원 사용하기 위해 두 스레드가 레이스를 하듯 경쟁; 예상과 다른 결과
- Critical Section: 공유 변수에 접근하는 코드 부분 → Lock
- 함수 내 지역 변수를 리턴값으로 쓰면 dealloc 될 수 있으므로, 함수의 인자를 통한 리턴값 전달
- 모든 lock은 초기화 되어야 → 2가지 방식
- PTHREAD_MUTEX_INITIALIZER 이용
- pthread_mutex_init 활용
- lock, unlock 시 오류 코드 확인 못할 수 있음 → trylock, timelock(일정 time 안되면 return)
상태 변수 (state variable)
- while문 사용 → 다른 code에서 lock 풀어버리는 것 방지
12강. Locks
- Lock: critical section 작동 시 atomicity 보장해주는 것이 가장 중요 → 시험에서도 atomic하게 작동되는지를 코드를 주고 물어볼 것
- Lock의 평가 지표
- Mutual Exclusion : 한 놈만 쓰게 해주나?
- Fairness : 줄 선 순서대로 사용하게 해주나?
- Performance : lock을 사용 전후로 시간 차이가 큰가? (작을수록 좋음)
- interrupt control : lock의 원자성 구현하기 가장 쉬운 방법 → interrupt 차단해버리기
- interrupt control의 문제점
- 멀티 프로세서에서는 작동하지 않음
- application에 너무 많은 신뢰를 줘버림 → user level에서는 사용 불가 → 무한루프 갈기면 어쩌게?
- interrupt 끄면 disk I/O 완료 등의 옵션을 아예 차단해버리는데, 이럴 때도 문제가 생김
- 효율성 : interrupt 끄고 켜는 것 속도가 썩 좋진 않아
- flag : hardware의 도움을 받아서
- lock 함수 내에서 flag가 1이면 spin lock, while 문 탈출 후 flag를 1로,
- unlock 때 flag 0으로
- spinlock에서 interrupt 발생하면 flag가 1인 thread가 2개가 생길 수 있게 됨
- spinlock의 시간 손해 이슈
- TestAndSet : 인자로 주어진 포인터(*ptr)가 가리키는 값을 인자로 주어진 값(new)으로 바꾸고, 이전에 인자로 주어진 포인터가 가리키던 값(old) 리턴
- mutual exclusive 하나, 공정성을 보장할 수 없음. CPU 단일일 때 오버헤드 크다.
- 명령을 작성하고, 실제 Spin lock울 구현하시오
- Compare-And-Swap : 인자로 주어진 포인터(*ptr)가 인자로 주어진 기대하던 값(expected)와 같으면 인자로 주어진 새로운 값(new)로 바꾸고 1 리턴, 그렇지 않으면 냅두고 0 리턴
- 명령을 작성하고, 실제 Spin lock을 구현하시오
- Load-Linked and Store-Conditional
- load-linked : 인자로 주어진 포인터(ptr)가 가리키는 값(*ptr) 리턴
- Store-Conditional : 인자로 주어진 포인터가 가리키는 값 (*ptr)이 아무도 업데이트를 시키지 않았다면 인자로 주어진 값(value)로 (*ptr) 변경 후 1 리턴, 그렇지 않으면 0 리턴
- Load-Linked와 Store-Conditional을 이용한 명령 작성하고, 실제 Spin lock 구현하시오
- FetchAndAdd : 인자로 주어진 포인터가 가리키는 값(*ptr)을 1 증가시키고, 옛 값(원래 인자로 주어진 포인터가 가리키던 값) 리턴
- Ticket Lock → FetchAndAdd 사용:
- FetchAndAdd를 사용한 ticket lock 코드를 작성하시오.
- lock에서는 FetchAndAdd를 load→ticket; unlock에서는 FetchAndAdd를 lock→turn에 걸기,, (12강 19p)
- Hardward-based spin lock은 간단하지만, 도는 것으로 thread가 time slice 낭비해버려서 OS가 spinning을 방지하기 위해 도움
- queue와 park(), unpark() system call 사용
- lock에서 park()를 통해 다른 스레드가 사용을 마치면 깨워달라는 신호를 보내고 ready state로 변경, unlock에서는 unpark()을 통해 스레드를 threadID로 깨워줌.
- lock에서 queue에 thread를 넣고 park()를 할 때 문제를 서술하시오
- queue에 넣자마자 다른 thread에서 unpark() 해버리는 경우, 해당 스레드를 unpark()해줄 스레드가 없어짐. 무한대기 상태
- 이의 해결책 setpark() → 곧 park()한다는 신호를 주는 것이다.
13강. Conditional Variables
- Parent Waiting for child 코드에서, 조건 변수를 사용하고, 상태 변수까지 사용하는 이유?
- join이 먼저 실행될지 보장할 수 없음; exit가 먼저 실행되어 깨울 스레드가 없음에도 signal 보내버리고 끝나면 자고 있는 thread를 깨워줄 수 있는 장치가 전혀 없게 됨
- Producer/Consumer Thread에서, if를 써서 생기는 문제점
- 소비자가 2, 생산자가 1인 상황에서, context switch 당시에 생산자 스레드가 일어날지, 소비자 스레드가 일어날지 알 수 없어 context switch이후 버퍼의 상태가 변경되는 상황이 생김.
- while 사용
- Bounded Buffer
- Mesa Semantics
- Hoare Semantics
- 조건 변수가 1개일 때 생기는 문제점
- 생산자를 깨워야 하는데, 소비자가 소비자를 깨우는 경우가 생길 수 있음. 결국에 모두가 sleep 해버리는 상황 존재
- 생산자는 소비자만 꺠우고, 소비자는 생산자를 깨우도록 조건 변수를 2개 두어야 함
- producer를 꺠울 떄는 signal 날릴 때 &emtpy(1st 인자),consumer를 깨울 떄는 signal 날릴 떄 &full(1st 인자)
- 최종 final solution에서 빈칸 뚫는 문제
- 생산자는 buffer 가득 찼을 때만 cond_wiat
- 소비자는 buffer 쫄닥 굶었을 떄만 cond_wait
- Covering Conditions(p.30)
- 공간 allocate 할 때 조건 빈칸 뚫는 문제
- allocate는 원하는 size보다 공간이 적을 때만 cond_wait
- free는 반대
- 공간 allocate 할 때 조건 빈칸 뚫는 문제
14강. Semaphore
- Thread들 주고 semaphore 초기값이 얼마가 되어야 하는지 묻기
- sem_wait() 하면 일단 semaphore 값 깎고 들어간다는 것 잊지 말길!
- wake: sleeping state을 ready로 바꾸어 주는 것 의미
- parent thread가 child thread를 기다릴 때 semaphore 초기값 어떻게 두어야 하겠는지 묻기
- 0으로 두자
- Semaphore mutex 범위를 어떻게 둘 것인가
- Reader-Writer locks
- reader lock get → 1일때 write lock도 얻어버려
- reader lock release → 0일 때 write lock도 unlock 해버려
- Zemaphore 함수 구성 다시 확인
15강. Concurrent Data Structures
- 자료 구조 lock에 중요한 2가지 요건
- correctness : mutual exclusive 보장
- performance : 속도 보장
- mutual exclusion 적용하기 위해 자료구조에 일일히 lock갈겨버리면 → 속도가 개느려져
- Sloppy Counter: 그래서 local counter, global counter를 도입해버린다
- local 에서 주기적으로 global 으로 합산
- 임계값 S를 설정 → local counter가 S 이상이 될 경우 S를 global로 넘김 → local counter = 0으로 설정
- S값에 따른 trade-off
- S값 크면 성능 좋아지지만, lag 발생 가능 (update되는 값과 local 사이의 괴리 커짐)
- S값 작아지면 concurrency는 증가하나, 성능이 나빠짐
- Concurrent Linked List → lock 얻고 해제할 때, release를 두 군데에서 함 (에러 발생하는 부분에서 release 갈겨버리는 것
- 이 문제를 해결하시오.
- 새 노드를 리스트에 추가하는 부분만 lock하기
- lookup 시에는 초기에 lock을 걸고, while 문 끝나는 부분에만 unlock걸기
- queue에 lock걸어버리기
- node 단위로 lock걸기 → 다음 노드의 lock 잡고, 현재 노드의 lock 해제
16강
- Bug 종류 : Non-Deadlock >>> Deadlock
- Non-Deadlock
- 전체 non-deadlock bug의 97퍼가 아래 2개
- atomicity violation → mutual exclusive 하게 만들어
- order violation → 순서 지정 : lock + 조건 변수 + 상태 변수
- Deadlock : 교착 상태
- Non-Deadlock
- DeadLock이 발생하는 이유가 무엇인가?
- complex dependency
- encapsulation: 두 벡터 사이 덧셈 연산 시 두 벡터에 대한 lock을 모두 잡아야 하는데, 다른 thread/ process에서 한 벡터에 대한 lock을 쥐고 돌려주지 않는 경우
- Deadlock의 발생 요건
- circular wait : lock에 order 부여 → 개빡셈
- hold-and-wait : trylock : lock 시도해보고 안되면 sleep → 동시에 trylock하는 경우 livelock problem → random delay로 해결
- no preemption : 잡아야하는 lock 최상단에 lock을 또 둚으로써 해결
- mutual exclusion : lock을 이용하지 않고**(wait-free)** compare and swap을 통해 해결 → hardware 도움으로 atomic한 task 가능
반응형