

문제점
해결책: 스레드 동기화
멀티스레드의 경쟁 상황 발생 빈도
매우 자주 발생
사용자의 멀티스레드 프로그램에서 자주 발생
커널 코드에서 매우 자주 발생 - 커널에 공유 데이터가 많기 때문
다중 코어에서 더욱 조심
임계구역(ciritical section): 공유 데이터에 접근하는 프로그램 코드들
상호배제(mutual exclusion): 임계구역을 오직 한 스레드만 배타적 독점적으로 사용하도록 하는 기술


1) 일반 코드(non-critical code)
2) 임계구역 진입 코드(entry code)
3) 임계구역 코드(critical code)
4) 임계구역 진출 코드(exit code)
1) 소프트웨어적 방법
알고리즘 수준에서 제시된 것들로, 구현 시 여러 문제 노출
peterson's 알고리즘 등
2) 하드웨어적 방법
하드웨어의 도움을 받는 방법
인터럽트 서비스 금지, 원자 명령 활용 등
대부분 하드웨어적 방법을 사용함
방법 1: 인터럽트 서비스 금지
인터럽트 서비스를 금지하거나 허용하는 CPU 명령 사용
방법 2: 원자 명령(atomic instruction) 사용
원자 명령은 CPU 명령
오늘날 상호배제 구현에 사용하는 방법
임계구역 진입 코드(entry code)에서 인터럽트 서비스를 금지하는 명령 실행
IF(Interrupt Flag): CPU 내부의 플래그 레지스터의 비트
비트값 1: 인터럽트 서비스 허용
비트값 0: 인터럽트 서비스 무시
cli(Clear Interrupt Flag)
sti(Set Interrupt Flag)
장치로부터 인터럽트가 발생해도, CPU가 인터럽트 발생을 무시
인터럽트가 발생하지 않는것이 아님, 발생을 무시
인터럽트가 발생해도 CPU는 인터럽트 서비스 루틴을 실행하지 않음
인터럽트를 무시하면 임계구역을 실행하는 스레드가 중단되지 않음
문제점
인터럽트를 금지하지 않은 경우

인터럽트를 금지시킨 경우


lock 변수로 상호배제 성공 사례

lock 변수로 상호배제 실패 사례




TSL을 원자명령이라 명명한 이유?
2개의 명령어를 합쳐 1개의 명령으로 만들고 TSL 명령 실행 중간에
인터럽트 서비스나 컨텍스트 스위칭이 발생하지 못하도록 하였다는 의미
1) locks 방식: 뮤텍스(mutex), 스핀락(spinlock)
상호배제가 되도록 만들어진 락(lock) 활용
락을 소유한 스레드만이 임계구역 진입
락을 소유하지 않은 스레드는 락이 풀릴 때까지 대기
wait-signal 방식: 세마포(semaphore)
n개 자원을 사용하려는 m개 멀티스레드의 원활한 관리
자원을 소유하지 못한 스레드는 대기(wait)
자원을 다 사용한 스레드는 알림(signal)
1) 락 변수
2) 대기 큐
3) 연산


임계구역의 실행 시간이 짧은 경우 비효율적
뮤텍스락 변수
pthread_mutex_t lock;뮤텍스 조작 함수
pthread_mutex_init(): 뮤텍스락 변수 초기화pthread_mutex_lock(): 뮤텍스락 잠그기pthread_mutex_unlock(): 뮤텍스락 풀기pthread_mutex_detroy(): 뮤텍스락 변수 사용 종료
busy-waiting lock 기법
스레드가 큐에서 대기하지 않고 락이 열릴 때 까지 계속 락 변수 검사
공격적인 뮤텍스(agressive mutex)라고도 함
뮤텍스와 거의 같고, busy-waiting이라는 점에서만 다름
대기 큐 없음
busy-waiting으로 인해 CPU를 계속 소모,
CPU가 다른 스레드를 실행할 수 없음
락을 소유한 스레드만 자원 배타적 사용, 동기화 기법
공유 자원 하나 당 하나의 스핀락 사용
1) 락 변수
2) 연산
lock 연산
임계구역에 들어갈 때 실행되는 entry 코드
락이 잠금 상태면, 락이 풀릴 때 까지 무한 루프 돌면서 lock 연산 시도
락이 열린 상태면, 락을 잠김 상태로 바꾸고 임계구역 실행
unlock 연산
임계구역을 나올 때 실행하는 exit 코드
락을 열림 상태로 변경


임계 구역의 실행 시간이 짧은 경우 효율적
스핀락 변수
pthread_spin_t lock;스핀락 조작 함수
pthread_spin_init(): 스핀락 변수 초기화pthread_spin_lock(): 스핀락 잠그기pthread_spin_unlock(): 스핀락 풀기pthread_spin_detroy(): 스핀락 변수 사용 종료
하이브리드 뮤텍스(hybrid mutex)
스핀락과 뮤텍스를 혼합한 동기화 방식
처음에는 스핀락처럼 대기
일정 시간이 지날 때 까지 락을 획득하지 못하면
뮤텍스처럼 작동하여 스레드를 블록 상태로 만듦.
적응적 뮤텍스(adaptive mutex) 또는 적응적 스핀락(adaptive spinlock)
스레드 동기화
사용자 공간에 있는 공유 자우너과 임계구역 코드
커널 공간에 있는 커널 자원과 임계구역 코드
각각 배타적으로 스레드가 접근하도록 동기화되어야 함


1) 자원: n개
2) 대기 큐: 자원을 할당받지 못한 스레드들이 대기하는 큐
3) counter 변수
counter = n)4) P/V 연산
P연산(wait 연산): 자원 요청 시 실행하는 연산
자원 사용 허가를 얻는 과정
V연산(signal 연산): 자원 반환 시 실행하는 연산
자원 사용이 끝났음을 알리는 과정

P/V를 wait/signal로 표기하기도 함
P 연산: 자원 사용을 허가하는 과정, 사용 가능 자원 수 1 감소(couner--)
V 연산: 자원 사용을 마치는 과정, 사용 가능 자원 수 1 증가(counter++)
세마포의 종류 2가지: 자원을 할당받지 못했을 때 행동을 기준으로 구분
1) sleep-wait 세마포
P 연산: 대기 큐에서 잠자기, counter--;
V 연산: 사용 가능 자원이 있으면 잠자는 스레드 깨우기, counter++;

2) busy-wait 세마포
P 연산: 사용 가능 자원이 생길 때 까지 무한 루프, counter--;
V 연산: counter++;
무한 루프를 하며 계속 대기하기 때문에 counter ≤ 0

세마포 구조체
sem_t s; // counter 변수 등을 가진 세마포 구조체 세마포 조작 함수
sem_init(): 세마포 초기화
sem_detroy(): 세마포 기능 소멸
sem_wait()
P 연산을 수행하는 함수(blocking call)
sleep-wait 방식으로, 가용 자원이 없으면 대기 큐에서 잠을 잠
sem_trywait()
P 연산을 수행하는 함수(non-blocking call)
가용 자원이 있으면, counter 값을 감소시키고 0 리턴
가용 자원이 없다면, counter 값을 유지하고 -1 리턴
sem_post()
V 연산을 수행하는 함수
sem_getValue()
세마포의 현재 counter 값을 리턴하는 함수

busy-waiting, busy-looping, spinning
1) 세마포 변수 S
0과 1 중 하나를 가지는 전역 변수 S는 1로 초기화
2) 대기 큐
3) 2개의 원자 연산
wait 연산(P 연산) - 자원 허가를 얻는 과정
S를 1 감소 시키고, 0보다 작으면 대기 큐에서 잠듦.
0보다 크거나 같으면 자원 사용하는 코드 실행
signal 연산(V 연산) - 자원 사용이 끝났음을 알리는 과정
S를 1 증가 시키고, 0보다 크면 그냥 리턴.
0보다 작거나 같으면 대기 큐에 있는 스레드 중 하나를 깨움

T2의 실행 시간이 길어지게 되면 T1이 V를 호출하는 시점도 늦춰지므로,
더욱 심각한 문제가 발생하게 됨
실시간 시스템의 근본이 붕괴됨
우선순위 올림(priority celling)
스레드가 공유 자원을 소유하게 될 때,
스레드의 우선순위를 미리 정해진 높은 우선순위로 일시적으로 올림
선점되지 않고 빨리 실행되도록 유도
우선순위는 그 어떤 스레드보다 높게 설정됨
우선순위 상속(priority inheritance)
우선순위를 올리는 시점의 차이가 있으며, 공유 자원 할당이 끝나게 되면 우선순위는 원래대로 돌아옴


문제1) 상호배제
생산자들과 소비자들의 공유버퍼 사용에 대한 상호배제
문제2) 비어 있는 공유버퍼 문제
비어 있는 공유버퍼를 소비자가 읽을 때
문제3) 꽉 찬 공유버퍼 문제
꽉 찬 공유버퍼에 생산자가 쓸 때


