반응형

1

티어 : Silver 3
시간 제한 : 1 초
메모리 제한 : 128 MB
알고리즘 분류 : 수학, 브루트포스 알고리즘, 정수론

 

문제

2와 5로 나누어 떨어지지 않는 정수 n(1 ≤ n ≤ 10000)가 주어졌을 때, 1로만 이루어진 n의 배수를 찾는 프로그램을 작성하시오.

 

입력

입력은 여러 개의 테스트 케이스로 이루어져 있다. 각 테스트 케이스는 한 줄로 이루어져 있고, n이 주어진다.

 

출력

1로 이루어진 n의 배수 중 가장 작은 수의 자리수를 출력한다.

 

예제 입출력


Code

while True:
  try:
    n = int(input())
    num = 1
    while True:
      if int('1'*num) % n == 0:
        print(len('1'*num))
        break
      num += 1
  except:
    break

메모리: 30860 KB
시간: 2240 ms

반응형
반응형

가장 긴 감소하는 부분 수열

티어 : Silver 2
시간 제한 : 1 초
메모리 제한 : 256 MB
알고리즘 분류 : 다이나믹 프로그래밍

 

문제

수열 A가 주어졌을 때, 가장 긴 감소하는 부분 수열을 구하는 프로그램을 작성하시오.

예를 들어, 수열 A = {10, 30, 10, 20, 20, 10} 인 경우에 가장 긴 감소하는 부분 수열은 A = {10, 30, 10, 20, 20, 10}  이고, 길이는 3이다.

 

입력

첫째 줄에 수열 A의 크기 N (1 ≤ N ≤ 1,000)이 주어진다.

둘째 줄에는 수열 A를 이루고 있는 Ai가 주어진다. (1 ≤ Ai ≤ 1,000)

 

출력

첫째 줄에 수열 A의 가장 긴 감소하는 부분 수열의 길이를 출력한다.

 

예제 입출력


Algorithm

가장 긴 감소하는 부분 수열은 가장 긴 증가하는 부분 수열을 이용
dp[i] : i번째 원소를 마지막으로 하는 부분 증가수열의 길이
1. 입력 받는 수열을 reverse
2. 가장 긴 증가하는 부분 수열 찾기

 

Code

N = int(input())
A = list(map(int, input().split()))

# 수열 reverse
A.reverse()

dp = [1] * N

for i in range(1, N):
  for j in range(i):
    if A[j] < A[i]:
      dp[i] = max(dp[i], dp[j] + 1)

print(max(dp))

메모리: 30864 KB
시간: 196 ms

반응형

'백준' 카테고리의 다른 글

[백준 1446] 지름길 Python  (0) 2022.03.20
[백준 4375] 1 Python  (0) 2022.03.19
[백준 11053] 가장 긴 증가하는 부분 수열 Python  (0) 2022.03.19
[백준 9095] 1, 2, 3 더하기 Python  (0) 2022.03.19
[백준 9012] 괄호 Python  (0) 2022.03.19
반응형

가장 긴 증가하는 부분 수열

티어 : Silver 2
시간 제한 : 1 초
메모리 제한 : 256 MB
알고리즘 분류 : 다이나믹 프로그래밍

 

문제

수열 A가 주어졌을 때, 가장 긴 증가하는 부분 수열을 구하는 프로그램을 작성하시오.

예를 들어, 수열 A = {10, 20, 10, 30, 20, 50} 인 경우에 가장 긴 증가하는 부분 수열은 A = {10, 20, 10, 30, 20, 50} 이고, 길이는 4이다.

 

입력

첫째 줄에 수열 A의 크기 N (1 ≤ N ≤ 1,000)이 주어진다.

둘째 줄에는 수열 A를 이루고 있는 Ai가 주어진다. (1 ≤ Ai ≤ 1,000)

 

출력

첫째 줄에 수열 A의 가장 긴 증가하는 부분 수열의 길이를 출력한다.

 

예제 입출력


Algorithm

dp[i] : i 번째 원소를 마지막으로 하는 부분 증가 수열의 최대 길이
1. 리스트를 이중 for문으로 돌면서 자신보다 작은 index에 값이 작은 수가 몇 개 있는지 확인
    ➝ i : 1 ~ N-1, j : 0 ~ i
2. j번째 원소가 i번째 원소보다 작으면 i번째 dp Table의 값과 j번째 dp Table의 값 + 1 중 큰 값으로 dp Table 갱신

 

Code

N = int(input())
A = list(map(int, input().split()))

dp = [1] * N

for i in range(1, N):
  for j in range(i):
    if A[j] < A[i]:
      dp[i] = max(dp[i], dp[j] + 1)

print(max(dp))

메모리: 30860 KB
시간: 200 ms

반응형

'백준' 카테고리의 다른 글

[백준 4375] 1 Python  (0) 2022.03.19
[백준 11722] 가장 긴 감소하는 부분 수열 Python  (0) 2022.03.19
[백준 9095] 1, 2, 3 더하기 Python  (0) 2022.03.19
[백준 9012] 괄호 Python  (0) 2022.03.19
[백준 10773] 제로 Python  (0) 2022.03.19
반응형

1, 2, 3 더하기

티어 : Silver 3
시간 제한 : 1 초 (추가 시간 없음)
메모리 제한 : 512 MB
알고리즘 분류 : 다이나믹 프로그래밍

 

문제

정수 4를 1, 2, 3의 합으로 나타내는 방법은 총 7가지가 있다. 합을 나타낼 때는 수를 1개 이상 사용해야 한다.

  • 1+1+1+1
  • 1+1+2
  • 1+2+1
  • 2+1+1
  • 2+2
  • 1+3
  • 3+1

정수 n이 주어졌을 때, n을 1, 2, 3의 합으로 나타내는 방법의 수를 구하는 프로그램을 작성하시오.

 

입력

첫째 줄에 테스트 케이스의 개수 T가 주어진다. 각 테스트 케이스는 한 줄로 이루어져 있고, 정수 n이 주어진다. n은 양수이며 11보다 작다.

 

출력

각 테스트 케이스마다, n을 1, 2, 3의 합으로 나타내는 방법의 수를 출력한다.

 

예제 입출력


Algorithm

dp[i] : 1, 2, 3의 합으로 i를 만들 수 있는 개수
dp[i] = dp[i-3] + dp[i-2] + dp[i-1]

 

Code

T = int(input())
for _ in range(T):
  n = int(input())

  dp = [0]*(11)
  dp[1] = 1
  dp[2] = 2
  dp[3] = 4
  for i in range(4, n+1):
    dp[i] = dp[i-3]+dp[i-2]+dp[i-1]
  

  print(dp[n])

메모리: 30860 KB
시간: 72 ms

반응형
반응형

괄호

티어 : Silver 4
시간 제한 : 1 초
메모리 제한 : 128 MB
알고리즘 분류 : 자료 구조, 문자열, 스택

 

문제

괄호 문자열(Parenthesis String, PS)은 두 개의 괄호 기호인 ‘(’ 와 ‘)’ 만으로 구성되어 있는 문자열이다. 그 중에서 괄호의 모양이 바르게 구성된 문자열을 올바른 괄호 문자열(Valid PS, VPS)이라고 부른다. 한 쌍의 괄호 기호로 된 “( )” 문자열은 기본 VPS 이라고 부른다. 만일 x 가 VPS 라면 이것을 하나의 괄호에 넣은 새로운 문자열 “(x)”도 VPS 가 된다. 그리고 두 VPS x 와 y를 접합(concatenation)시킨 새로운 문자열 xy도 VPS 가 된다. 예를 들어 “(())()”와 “((()))” 는 VPS 이지만 “(()(”, “(())()))” , 그리고 “(()” 는 모두 VPS 가 아닌 문자열이다.

여러분은 입력으로 주어진 괄호 문자열이 VPS 인지 아닌지를 판단해서 그 결과를 YES 와 NO 로 나타내어야 한다. 

 

입력

입력 데이터는 표준 입력을 사용한다. 입력은 T개의 테스트 데이터로 주어진다. 입력의 첫 번째 줄에는 입력 데이터의 수를 나타내는 정수 T가 주어진다. 각 테스트 데이터의 첫째 줄에는 괄호 문자열이 한 줄에 주어진다. 하나의 괄호 문자열의 길이는 2 이상 50 이하이다. 

 

출력

출력은 표준 출력을 사용한다. 만일 입력 괄호 문자열이 올바른 괄호 문자열(VPS)이면 “YES”, 아니면 “NO”를 한 줄에 하나씩 차례대로 출력해야 한다. 

 

예제 입출력


Algorithm

1. ( 모양인 경우 리스트에 해당 문자 append
2. ) 모양인 경우 리스트에서 pop
2.1. pop이 불가능하면, 마지막에 리스트에 뭔가 남아있으면 NO 반환
2.2. 마지막에 리스트에 아무것도 없으면 YES 반환

 

Code

T = int(input())
for _ in range(T):
    string_ = list(input())
    
    list_ = []
    flag = False
    for chr in string_:
        # ( 모양인 경우 리스트에 해당 문자 append
        if chr == '(':
            list_.append(chr)
        else: # ) 모양인 경우 리스트에서 pop
            if len(list_) == 0: # 리스트에서 pop이 불가능한 경우 
                flag = True
                break
            list_.pop()
    
    # 마지막에 리스트에 뭔가 남아있으면 NO 반환
    if flag or len(list_) > 0:
        print('NO')
    else:
        print('YES')

메모리: 30864 KB
시간: 80 ms

반응형
반응형

제로

티어 : Silver 4
시간 제한 : 1 초
메모리 제한 : 256 MB
알고리즘 분류 : 구현, 자료 구조, 스택

 

문제

나코더 기장 재민이는 동아리 회식을 준비하기 위해서 장부를 관리하는 중이다.

재현이는 재민이를 도와서 돈을 관리하는 중인데, 애석하게도 항상 정신없는 재현이는 돈을 실수로 잘못 부르는 사고를 치기 일쑤였다.

재현이는 잘못된 수를 부를 때마다 0을 외쳐서, 가장 최근에 재민이가 쓴 수를 지우게 시킨다.

재민이는 이렇게 모든 수를 받아 적은 후 그 수의 합을 알고 싶어 한다. 재민이를 도와주자!

 

입력

첫 번째 줄에 정수 K가 주어진다. (1 ≤ K ≤ 100,000)

이후 K개의 줄에 정수가 1개씩 주어진다. 정수는 0에서 1,000,000 사이의 값을 가지며, 정수가 "0" 일 경우에는 가장 최근에 쓴 수를 지우고, 아닐 경우 해당 수를 쓴다.

정수가 "0"일 경우에 지울 수 있는 수가 있음을 보장할 수 있다.

 

출력

재민이가 최종적으로 적어 낸 수의 합을 출력한다. 최종적으로 적어낸 수의 합은 231-1보다 작거나 같은 정수이다.

 

예제 입출력

 

힌트

예제 2의 경우를 시뮬레이션 해보면,

  • [1]
  • [1,3]
  • [1,3,5]
  • [1,3,5,4]
  • [1,3,5] (0을 불렀기 때문에 최근의 수를 지운다)
  • [1,3] (0을 불렀기 때문에 그 다음 최근의 수를 지운다)
  • [1,3,7]
  • [1,3] (0을 불렀기 때문에 최근의 수를 지운다)
  • [1] (0을 불렀기 때문에 그 다음 최근의 수를 지운다)
  • [1,6]

합은 7이다.


Algorithm

1. 입력을 하나씩 받으면서 0이 아니면 append()
2. 0이면 pop()
3. 입력이 모두 끝난 후 sum(list) 출력

 

Code

K = int(input())
nums = []
for i in range(K):
    num = int(input())
    
    # 입력 값이 0이 아니면 append
    if num != 0:
        nums.append(num)
    else:
        nums.pop()

print(sum(nums))

메모리: 31640 KB
시간: 3896 ms

반응형
반응형

1. 교착상태(Deadlock)

: Process나 Thread가 결코 일어날 수 없는 특정 Event를 기다리는 상태

 

2. 교착 상태의 4가지 필요 조건

4가지를 모두 만족해야 교착 상태가 발생한다.

  • 상호 배제(Mutual Exclusion)
    : 여러 Process/Thread가 동시에 한 자원에 접근하지 못하도록 막는것
  • 점유와 대기(Hold and Wait)
    : Process/Thread가 자원을 점유한 상태로 다른 자원을 점유하기 위해 대기하는 것
  • 비선점(Nopreemption)
    : Process에서 자원을 할당 받으면 작업을 완료할 때까지 System에서 Process의 제어를 뺏을 수 없음
  • 순환 대기(Circular Wait)
    : 점유와 대기를 하는 형태로 Cycle에 갇혀 빠져나갈 수 없는 상태

 

3. 교착 상태 해결법

: 교착 상태의 해결법으로는 교착 상태 예방, 교착 상태 회피, 교착 상태 탐지 및 복구, 교착 상태 무시가 있다.

 

3.1. 교착 상태 예방

: 교착 상태 필요 조건 중 하나를 거부하는 방식으로 예방하는 방법이다.

 

교착 상태는 발생하지 않으면 좋지만, 교착 상태가 발생하지 않는 환경을 만들어버린다면 자원을 효율적으로 사용할 수 없다.

e.g., 필요 조건 중 점유와 대기 조건을 거부해 한 Process가 자신이 필요한 자원을 모두 사용할 수 있을 때만 작업을 시작한다면 자원을 효율적으로 사용할 수 없다.

 

3.2. 교착 상태 회피

: 교착 상태를 인정하고 피해가는 방법으로 자원을 요청할 때마다 System의 상태를 판단하고 회피하는 전략을 사용한다.

☞ Overhead가 심하다.

e.g., 은행원 Algorithm

 

은행원 Algorithm
: System을 안전 상태/불안전 상태(교착 상태가 발생할 가능성이 있는 경우)로 구분하고 불안전 상태일 때는 대기하고 안전 상태일 때만 자원을 빌려주는 Algorithm으로 할당한 자원 수 고정, Process 수 고정, 제한된 시간 안에 자원 반납 등의 많은 조건이 필요하다.
현실에서는 자원이 동적으로 왔다갔다해 현실적으로는 불가능한 Algorithm이다.

 

3.3. 교착 상태 탐지 및 복구

: 교착 상태가 발생한 것 같을 때 탐지하고 복구하는 방법으로 교착 상태가 자주 발생하는 System에서 일반적으로 사용하는 방법이다.

☞ 교착 상태 회피에 비해 Overhead가 적다.

 

  • 교착 상태 탐지
    : 교착 상태 존재 여부 및 교착 상태에 연관된 Process와 자원을 알아내는 전략을 사용하며 교착 상태의 4가지 필요 조건 중 순환 대기 존재 여부에 초점을 맞춰 교착 상태를 탐지한다.
    ☞ 탐지 Algorithm도 회피 Algorithm과 마찬가지로 Overhead가 있으므로 얼마나 자주(주기적으로, 자원 즉시 할당 여부에 따라, CPU 이용률에 따라 등) 탐지 Algorithm을 호출하는지가 관건이다.
    e.g., 자원 할당 그래프 소거
    더보기

    자원 할당 그래프 소거 방식

  • 교착 상태 복구
    : 순환 대기를 깨서 교착 상태로부터 회복하는 방식이다.
    e.g., 순환 대기가 깨질 때까지 Process 종료, 순환 대기에 포함된 Process 제어권 뺏고 Rollback
    ☞ 희생양이 될 Process는 남은 수행 시간, 자원 유형의 수 등 Systemp마다 다른 기준의 우선순위에 따라 결정된다.
        e.g., MySQL의 경우 Transaction Timeout시 교착상태이든 아니든 가장 작은 Transaction을 Rollback하며 Transaction의 크기는 삽입, 업데이트, 삭제된 행 수에 의해 결정된다.

 

3.4. 교착 상태 무시

: 교착 상태가 드물게 발생하는 System에서 일반적으로 사용하는 방법이다.

반응형
반응형

0. 용어 정리

  • Race Condition(경쟁 조건)
    : 여러 Process/Thread가 동시에 같은 Data를 조작할 때 타이밍이나 접근 순서에 따라 결과가 달라질 수 있는 상황
  • Synchronization(동기화)
    : 여러 Process/Thread를 동시에 실행해도 공유 Data의 일관성을 유지하는 것
  • Critical Section(임계 영역)
    : 공유 Data의 일관성을 보장하기 위해 한 번에 하나의 Precess/Thread만 진입해 실행가능한 영역
  • Mutual Exclusion(상호 배제)
    : 공유 Data의 일관성을 보장하기 위해 한 번에 하나의 Process/Thread만 진입해 실행할 수 있도록 하는 Algorithm

 

1. Mutual Exclusion을 보장할 수 있는 방법

: Lock을 사용한다.

// 실제 동작 Code가 아닌 개념 이해를 돕기위한 Code 입니다.

do{
	acquire lock // 여러 Process/Thread가 Lock을 획득하기 위해 경합
    	critical section // Lock을 획득한 단 하나의 Process/Thread만이 Critical Section에 들어가 실행
    release lock // Critical Section에서의 실행을 마치면 Critical Section을 빠져나오며 Lock 반환
    	reminder section
} while(TRUE)
Mutual Exclusion을 보장하는 상호 배제 기법으로는 Spin Lock, Mutex, Semaphore가 있다.

 

2. Spin Lock

: Lock을 획득할 수 있을 때까지 Lock 획득을 반복해서 시도하는 방식

☞ Lock을 획득하기 전까지 Lock이 다른 Process/Thread에 의해 선점되었는지 확인하는 것 자체가 CPU Cycle을 잡아먹는 일이기 때문에 CPU를 낭비한다.

 

Single Core, Multi Core에서의 Spin Lock
더보기

Single Core에서는 Spin Lock으로 Lock이 반환되기를 계속 기다리더라도 Lock을 획득하기 위해서는 이미 누군가 획득한 상태인 Lock을 반환해야하는데 이는 결국 Context Switching이 필요하다.
Single Core에서의 Spin Lock은 이점이 없다.

Multi Core에서는 Spin Lock으로 Lock이 반환되기를 기다리다가 Thread 1이 Lock을 반환하자마자 Thread 2가 Lock을 획득하므로 Context Switching이 일어나지 않는다.

 

 

2.1. 동작 예제

// 실제 동작 Code가 아닌 개념 이해를 돕기 위한 Code입니다.

volatile in lock = 0; // global -> 여러 Thread가 동시에 Lock에 접근 가능

// 여러 Thread가 호출할 함수
void critical(){
	
    while(test_and_set(&lock) == 1); // 여러 Thread가 while loop를 통해 Lock을 획득하려 시도
    ... critical section // Lock 획득에 성공한 하나의 Thread가 Critical Section에 들어와 실행
    lock = 0 // Critical Setcion에서 수행 완료 후 Lock 반환
    
}

// 실제 TestAndSet()의 동작 원리를 간단히 구현한 함수
// 공유되는 Lock에 대해 Lock의 원래 저장값을 반환하고 Lock 값은 1로 변경
int test_and_set(int* lockPtr){
	
    int oldLock = *lockPtr; // 원래 가지고 있던 Lock 값 가져오기
    *lockPtr = 1; // Lock 값을 1로 변경
    
    return oldLock; // 원래 가지고 있던 Lock 값을 반환
}

 

2.2. 동작 과정

Thread 1이 먼저 수행된 후 Thread 2가 수행되는 경우

 

1. Thread 1의 test_and_set()의 반환값 = 0 (원래 Lock은 0으로 초기화 되어있었기 때문) 이므로 while loop 탈출해 Critical Section에서 작업을 수행한다.

    ☞ test_and_set()을 호출했으므로 Lock은 1로 변경된다.

 

2. Thread 2의 test_and_set()의 반환값 = 1 (1번 과정에서 Thread 1에 의해 Lock이 1로 변경되었기 때문) 이므로 while loop에 갇힌다.

     test_and_set()을 호출했으므로 Lock은 계속 1로 갱신된다.

 

3. Thread 1이 Critical Section에서 작업을 마치고 Lock을 반환한다.

     Lock이 0이 된다.

 

4. Thread 1이 Lock을 반환함과 동시에 Thread 2의 test_and_set() 반환값 = 0 (3번 과정에서 Thread 1에 의해 Lock이 0으로 변경되었기 때문) 이 되므로 while loop 탈출해 Critical Section에서 작업을 수행한다.

     test_and_set()을 실행했기 때문에 Lock은 1로 변경된다.

 

5. Thread 2가 Critical Section에서 작업을 마치고 Lock을 반환한다.

     Lock이 0이 된다.

 

∴ test_and_set()을 통해 Thread 1과 Thread 2가 Critical Section 안에서 동시에 작업을 수행할 수 없다. (= 상호 배제)

 

TestAndSet()
더보기

: CPU에서 지원하는 atomic 명령어

Atomic 명령어의 특징
더보기

① 실행 중간에 간섭받거나 중단되지 않는다.
② 같은 Memory 영역에 대해 동시에 실행되지 않는다.
☞ 2개 이상의 Process/Thread에 의해 동시에 호출된다고 해도 CPU Level에서 알아서 먼저 하나 실행시킨 후 실행이 끝나면 이어서 다른 하나를 실행시키는 방식으로 동기화시켜 실행된다.

 

3. Mutex

: Lock이 다른 Process/Thread에 의해 획득된 상태라면 Lock이 반환될 때까지 Queue에서 대기하는 방식

☞ CPU Cycle을 불필요하게 낭비하는 것을 최소화한다.

 

3.1. 동작 예제

// 실제 동작 Code가 아닌 개념 이해를 돕기 위한 Code입니다.
Class Mutex{
	int value = 1; // 이 값을 획득해야 Lock을 획득할 수 있다. (공유되는 Data)
    		       // value를 변경할 때 Critical Section 안에서 변경하지 않으면 Race Condition이 발생할 수 있다.
    int guard = 0; // Critical Section에 포함되어있는 value를 변경하기 위해 사용되는 변수
}

Mutex::lock(){
	while(test_and_set(&guard)); // value 값 변경 전 guard를 획득하기 위해 여러 Process/Thread가 경합
    
    // guard를 획득한 하나의 Process/Thread만 value 변경 Logic을 수행한다.
    if (value == 0){ // 누군가 value를 이미 획득한 상태라면
    	현재 Thread를 Queue에 넣는다; // 작업을 잠시 멈추고 쉬고 있을테니 Lock이 풀리면 깨워달라며 Queue에 들어간다.
        guard = 0; & go to sleep // Process/Thread를 Queue에 넣고 guard 변경(guard 반환)
    }
    else{ // value를 획득할 수 있으면
    	value = 0; // value 획득하고 value를 0으로 갱신(value 획득한 상태를 표시)한다.
        guard = 0; // value 변경 후 guard 0으로 갱신(guard 반환)
    }

Mute::unlock(){
	while(test_and_set(&guard)); // value 변경 전 여러 Process/Thread가 guard를 획득하기 위해 경합
    
    // guard를 획득한 하나의 Process/Thread만 value 변경 Logic을 수행한다.
    if (Queue에 하나라도 대기 중이라면){
    	그 중 하나를 깨운다;
    }
    else{ // 대기중인 Process/Thread가 하나도 없으면
    	value = 1; // value를 1로 갱신(value 반환)
    }
    guard = 0; // value 변경 혹은 Queue에서 Process/Thread 꺼낸 후 guard 0으로 갱신(guard 반환)
}

mutex -> lock(); // 여러 Process/Thread가 Lock을 획득하기 위해 경합
... critical section // Lock을 획득한 하나의 Process/Thread가 Critical Section에 들어가 작업 수행
mutex -> unlock(); // Critical Section에서 작업을 마친 후 Lock 반환

 

항상 Mutex가 Spin Lock보다 좋을까?
더보기

NO !

Multi-Core 환경이고 Critical Section에서의 작업이 Context Switching보다 빨리 끝난다면 Spin Lock이 Mutex보다 더 이점이 있다.

 

Mutex와 Spin Lock의 Context-Switching

 

Mutex는 잠들고 깨는 과정(Queue에 넣고 빼는 과정)에서 Context Switching이 발생한다.

Spin Lock은 잠들고 깨는 과정(Queue에 넣고 빼는 과정)이 없기 때문에 Context Switching이 발생하지 않는다.

 

4. Semaphore

: Signal Mechanism을 가지는, 하나 이상의 Process/Thread가 Critical Section에 접근 가능하도록 하는 방법

 

4.1. 종류

  • Binary Semaphore(이진 세마포어)
    : 한 번에 1개의 Process/Thread만 Critical Section에 접근할 수 있다.
    ☞ Mutual Exclusion을 보장한다.
  • Counting Semaphore
    : 한 번에 value개의 Process/Thread만 Critical Section에 접근할 수 있다.

 

4.2. 동작 예제

// 실제 동작 Code가 아닌 개념 이해를 돕기 위한 Code입니다.
Class Semaphore{

    // 한 번에 value개의 Process/Thread가 Critical Section에 접근할 수 있다.
    int value = 1; // value를 획득해야 Lock을 획득할 수 있다. (공유되는 Data)
    			   // value를 변경할 때 Critical Section 안에서 변경하지 않으면 Race Condition이 발생할 수 있다.
    int guard = 0; // Critical Section에 포함되어있는 value를 변경하기 위해 사용되는 변수
}

Semaphore::wait(){
	while(test_and_set(&guard)); // value 변경 전 guard를 획득하기 위해 여러 Process/Thread가 경합
    
    // guard를 취득한 하나의 Process/Thread만 value 변경 Logic을 수행한다.
    if (value == 0){ // 이미 누군가 value를 획득한 상태라면
    	현재 Process/Thread를 Queue에 넣는다; // Process/Thread가 일을 잠시 멈추고 쉬고 있을테니 Lock이 풀리면 깨워달라며 Queue에 들어간다.
        
        guard = 0; & go to sleep; // Process/Thread를 Queue에 넣은 후 guard 변경(guard 반환)
    }
    else{ // value를 획득할 수 있다면
    	value -= 1; // value 획득한 후 value를 1 감소(value 획득한 상태를 표시)
        guard = 0; // value 변경 후 guard 0으로 갱신(guard 반환)
    }
}

Semaphore::signal(){
	while(test_and_set(&guard)); // value 변경 전 guard 획득하기 위해 여러 Process/Thread가 경합
    
    // guard를 획득한 하나의 Process/Thread만 value 변경 Logic을 수행
    if (Queue 안에 하나라도 대기 중이라면){
    	그 중에 하나를 깨워 준비시킨다;
    }
    else{ // 대기중인 Process/Thread가 하나도 없으면
    	value += 1; // value 1 증가(value 반환)
    }
    guard = 0; // value 변경 혹은 Queue에서 Process/Thread 꺼낸 후 guard 0으로 갱신(guard 반환)
}

Semaphore -> wait(); // 여러 Process/Thread가 Lock을 획득하기 위해 경합
... critical section // Lock을 획득한 하나의 Process/Thread가 Critical Section에 들어가 작업 수행
semaphore -> signal(); // Critical Section에서 작업을 마친 후 Lock 반환

 

Semaphore는 Process/Thread의 동작 순서를 정할 때도 사용할 수 있다.
더보기

Multi Core 환경에서 P1과 P2가 각각 Core를 할당받아 동시에 시작되는 경우

P1: task 1을 수행

P2: task 2를 수행한 후 task 3을 수행

 

Class Semaphore{
    int value = 0;
    int guard = 0;
}

P1이 task 1, P2가 task 2 수행이 동시에 진행되는 경우 task 1이 먼저 끝날지, task 2가 먼저 끝날지는 알 수 없지만 task 3은 task 1이 끝난 뒤에 수행된다는 것은 명확하다.

task 1이 먼저 끝나는 경우

1. P1이 task 1을 끝내고 signal() 호출함으로써 value = 1로 갱신

2. P2가 task 2를 끝내고 wait() 호출하면 value를 1 차감해 value는 0이 되고 wait() 반환되어 task 3 수행

 

task 2가 먼저 끝나는 경우

1. P2가 task 2를 끝내고 wait() 호출하지만 아직 value는 0이므로 자신을 Queue에 넣고 대기

2. P1이 task 1을 끝내고 signal() 호출하면 Queue에 들어있던 P2를 깨움

3. P2가 깨어나 task 3 수행

 

∴ signal()과 wait()가 같은 Process/Thread 안에서 실행될 필요가 없다.

 

5. Mutex와 Binary Semaphore의 차이점

  Mutex Binary Semaphore
Lock 해제 주체 Lock을 가진 Process/Thread만 Lock을 해제할 수 있다.
☞ 누가 Lock을 해제할지 예상할 수 있다.
Lock을 가지고 있지 않은 Process/Thread도 Lock을 해제할 수 있다.
(= wait()를 호출하는 존재와 signal()을 호출하는 Process/Thread가 다를 수 있다.)
Priority Inheritance Priority Inheritance 속성을 갖는다. Priority Inheritance 속성을 갖지 않는다.
사용되는 경우 상호 배제만 필요한 경우 작업 간 실행 순서 동기화까지 필요한 경우
Priority Inheritance
더보기

여러 Process/Trhead가 동시에 실행되게 되면 CPU에서 Context Switching이 발생된 이후 어떤 Process를 먼저 실행시켜야하는지 정한다. (= Scheduling)

여러 Scheduling 기법 중 Process/Thread의 우선 순위에 따라 우선 순위가 높은 Process/Thread를 먼저 실행시키는 Scheduling 방식에서 사용될 수 있다.

 

P2가 먼저 진행되가가 Lock을 획득한 후 Critical Section 안에서 작업을 수행하고 있는 상황 (우선 순위 : P1 > P2)

 

P1이 Lock을 획득하려했으나 P2가 먼저 Lock을 획득해 수행되고 있으므로 P1은 진행될 수 없다.

☞ 이 때부터 P1은 P2에 의존성을 갖게 된다. (= P1이 우선 순위가 더 높음에도 불구하고 P2가 Lock을 반환하지 전까지 P1은 아무것도 할 수 없다.)

 

Mutex는 이 문제(우선 순위가 높은 P1이 우선 순위가 낮은 P2에 의존하고 있는 문제)를 어떻게 해결할 수 있을까?

1. P2의 우선 순위를 P1의 우선 순위만큼 올려버린다. : Prioity Inheritance

2. Scheduler가 Scheduling을 할 때 P2의 우선순위를 보고 P2부터 실행한다.

3. P2가 빠르게 Critical Section을 빠져나올 수 있다.

 

[참고]

https://youtu.be/gTkvX2Awj6g

 

반응형

+ Recent posts