나만의 공부 노트

Concurrency 본문

운영체제

Concurrency

va95 2021. 5. 15. 11:30

하나의 프로세스는 여러 개의 실행 흐름(Thread)을 가질 수 있다.

Thread는 개별 stack, PC, registers를 담는 TCB를 가지고 있다.

 

 Race Condition

-> execution이 atomic하지 않으면 발생

-> critical section(mutual exclusion/상호 배제)으로 보호가 필요

 

Lock

-> spinning or sleep을 이용해 lock 구현

 

Conditioin Variables

-> 초창기에는 spinning으로 lock을 구현(cpu 소모적)

-> wait을 통해 sleep pool로 이동시켜서 cpu를 할당받지 않도록 함(그러나 wait를 거는 와중에 unlock이 되버리면 문제 발생)

 


Lock의 구현

-> OS와 HW로 구현

-> 싱클 코어의 Disable Interrupts는 SW만으로도 구현 가능

-> 멀티 코어는 위와 같은 방법으로 해결 불가능

-> lock을 얻고자 flag를 사용하는 과정에서도 atomic하지 않기 때문에, 이를 HW에서 atomic으로 구현해야함(test-and-set)

-> Compare-And-Swap도 HW에서 하나의 instruction으로 구현되어 있음 

 

Sleeping

-> spinning은 구현은 쉽지만, cpu가 소모적임

-> sleep을 통해 lock이 해제될 때까지 cpu를 할당받지 않음

-> 그러나 thread를 park(=sleep)하는 도중에 unlock이 수행되면, 해당 thread는 영원히 sleep에 갇힐 수 있음

-> 이때, setpark를 통해 특정 변수의 값을 변경하고 unpark(=wakeup)에서 setpark가 일어났었는지 체크

 

Lock-based Concurrent Data Structure

-> 데이터를 동시에 접근하며, 쓰기를 포함한 명령을 수행한다면 lock이 필요하다.

-> 그러나 lock을 사용하면 correctness는 보장될지언정, 특히 멀티 코어 환경에서 performance는 급격히 떨어진다.

-> 현재 수행되고 있는 많은 연구들이 lock을 사용하지 않는 동시 접근에 대한 문제를 풀고 있다.

 

1. Sloppy counter

-> global 변수에 lock을 걸면 성능이 저하되므로, local 변수에 잠시 저장했다가 일정 threshold를 넘으면 global 변수에 더하는 방식

 

2. Linked Lists

-> Insert시 lock을 걸 수도 있지만, TAS(test-and-set) + 1 instruction을 이용하면 lock을 걸지 않아도 된다.

 

3. Scaling Linked List

-> Hand-over-hand locking(= lock coupling)이라고도 불리며, 링크드 리스트에 노드마다 lock을 두어 접근할 때 최대 2개까지 lock을 잡도록 한다.

 

4. Queue

-> Enqueue는 마찬가지로 lock 또는 TAS로 해결이 가능하다.

-> Dequeue는 TAS로 해결이 불가능하다.

 

5. Hash Table

-> Bucket 마다 lock을 두어, key가 다르다면 동시에 접근이 가능하다.

-> Bucket에 lock을 두지않고, value를 동시에 접근할 수 있도록 구현도 가능하다.


Condition Variables

-> 필요한 변수들

1. condition variable

2. mutex variable

3. state variable(= variable) : 3번째 그림과 같이 semantic이 없으면 parent는 무한히 기다리는 경우가 발생

 

The Producer / Consumer (Bound Buffer) Problem

-> 현실적인 문제들을 해결하기 위해 자주 사용됨(ex. 카카오톡)

-> 1:1인 경우 문제가 없던 코드가 다:다 인 경우 문제가 발생할 수 있다.

-> 특정 cv로 인해 thread가 깨어났지만, 다른 thread가 이미 소비해버려서 state가 변경되었을 때 문제 발생(recheck으로 해결)

-> 즉, thread가 깨어났음에도 불구하고 state가 만족한다고 보장할 수 없을 경우를 Mesa semantics

-> thread가 깨어났다면 무조건 state가 만족한다고 보장할 수 있을 때 Hoare semantics라고 한다.

-> cond queue를 여러 개를 두어 해결하거나, wakeup all을 통해 해결이 가능하다.

 

Semaphore

-> 일반적으로 binary variable과 같음

-> thread가 semaphore를 가져갈 때마다 -1씩, 반납할 때마다 +1씩 적용하여, semaphore가 음수일 때는 wait를 수행한다.

 

Reader-Writer Locks

-> read의 경우, 데이터를 변경하지 않음. 즉, lock을 read thread 각각에 걸면 오버헤드가 됨

-> read만 하는 thread들끼리는 lock을 사용하지 않음

-> 다만, 하나의 writer thread가 lock을 잡는다면 write를 수행하고, 하나의 reader thread가 lock을 잡는다면 모든 reader thread가 끝날 때까지 read를 수행한다.

 


Common Concurrency Problems

 

 

'운영체제' 카테고리의 다른 글

xv6 - virtual memory  (0) 2021.04.12
Memory Virtualization  (0) 2021.04.12
xv6 - trap & interrupts  (0) 2021.04.03
xv6 - System call  (0) 2021.03.19
CPU Virtualization  (0) 2021.03.12