1) 인터럽트 서비스 금지 방법
cli ; entry 코드. 인터럽트 서비스 금지 명령 cli (clear interrupt flag)
...
임계구역 코드
...
sti ; exit 코드. 인터럽트 서비스 명령 허용 sti (set interrupt flag)
1) 장치로부터 인터럽트가 발생해도, CPU가 인터럽트 발생을 무시
2) 인터럽트가 발생해도 CPU는 인터럽트 서비스 루틴을 실행하지 않음
3) 인터럽트를 무시하면 임계구역을 실행하는 스레드가 중단되지 않음
2) 문제점
1) 멀티스레드 동기화란?
2) 대표적인 기법
lock 방식 : 뮤텍스(mutex), 스핀락(spilock)
- 상호배제가 되도록 만들어진 락(lock)활용
- 락을 소유한 스레드만이 임계구역 진입
- 락을 소유하지 않은 스레드는 락이 풀릴 때까지 대기
wait-signal 방식 : 세마포(semaphore)
- n개의 자원을 사용하려는 m개 멀티스레드의 원활한 관리
- 자원을 소유하지 못한 스레드는 대기(wait)
- 자원을 다 사용한 스레드는 알림(signal)
1) 뮤텍스
2) 구성요소
1. 락 변수
1) 뮤텍스를 이용한 동기화 특징
2) 뮤텍스 동기화를 위한 POSIX 표준 라이브러리
3) pthread를 이용한 뮤텍스 동기화 코딩 사례
pthread_mutex_t lock; //뮤텍스락 변수 생성
pthread_mutex_init(&lock, NULL); //뮤텍스락 변수 초기화
pthread_mutex_lock(&lock); //임계구역 entry 코드. 뮤텍스락 잠그기
...임계구역 코드...
pthread_mutex_unlock(&lock); //임계구역 exit 코드. 뮤텍스락 열기
#include <stdio.h>
#include <pthread.h>
int sum = 0;
pthread_mutext_t lock;
void* worker(void arg){
printf("%s 시작 \t %d\n", (char*)arg, sum);
for(int i = 0; i < 1000000 ; i++){
pthread_mutex_lock(&lock);
sum = sum + 10;
pthread_mutex_unlock(&lock);
}
printf("%s 끝 \t %d\n", (char*)arg, sum);
}
int main(){
char *name[] = {"황기태", "이찬수"};
pthread_t tid[2];
pthread_attr_t attr[2]; //스레드 정보를 담을 구조체
pthread_attr_init(&attr[0]);
pthread_attr_init(&attr[1]);
pthread_mutex_init(&lock, NULL);
pthread_create(&tid[0], &attr[0], worker, name[0]);
pthread_create(&tid[1], &attr[1], worker, name[1]);
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
printf("최종 sum = %d\n", sum);
pthread_mutex_destroy(&lock);
return 0;
}
1) 스핀락
2) 구성요소
1. 락 변수
- true/false 중 한 값
- true : 락을 잠근다. 락을 소유한다.
- false : 락을 연다. 락을 해제한다.
1) 스핀락을 이용한 동기화 특징
2) 스핀락 동기화를 위한 POSIX 표준 라이브러리
3) pthread를 이용한 스핀락 동기화 코딩 사례
pthread_spinlock_t lock; //스핀락 변수 생성
pthread_spin_init(&lock, NULL); //스핀락 변수 초기화
pthread_spin_lock(&lock); //임계구역 entry code. 스핀락 잠그기
... 임계구역 코드 ...
pthread_spin_unlock(&lock); //임계구역 exit code. 스핀락 열기
#include <stdio.h>
#include <pthread.h>
int sum = 0;
pthread_spinlock_t lock;
void* worker(void arg){
printf("%s 시작 \t %d\n", (char*)arg, sum);
for(int i = 0; i < 1000000 ; i++){
pthread_spin_lock(&lock);
sum = sum + 10;
pthread_spin_unlock(&lock);
}
printf("%s 끝 \t %d\n", (char*)arg, sum);
}
int main(){
char *name[] = {"황기태", "이찬수"};
pthread_t tid[2];
pthread_attr_t attr[2]; //스레드 정보를 담을 구조체
pthread_attr_init(&attr[0]);
pthread_attr_init(&attr[1]);
pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE);
//lock을 한 프로세스에 속한 스레드만이 공유하는 변수로 선언
pthread_create(&tid[0], &attr[0], worker, name[0]);
pthread_create(&tid[1], &attr[1], worker, name[1]);
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
printf("최종 sum = %d\n", sum);
pthread_mutex_destroy(&lock);
return 0;
}
뮤텍스 | 스핀락 | |
---|---|---|
대기큐 | 있음 | 없음 |
블록 가능 여부 | 락이 작겨 있으면 블록됨 | 락이 잠겨 있어도 블록되지 않고 계속 락 검사 |
lock/unlock 연산 비용 | 저비용 | CPU를 계속 사용하므로 고비용 |
하드웨어 관련 | 단일 CPU에서 적합 | 멀티코어 CPU에서 적합 |
주 사용처 | 사용자 응용 프로그램 | 커널 코드, 인터럽트 서비스 루틴 |
왜 알아야 하는가?
- 개발자로서 둘 중 하나를 선택하여야하고, 시스템의 성능 관점에서 볼 수 있어야 하기 때문이다.
1) 세마포의 정의
2) 구성요소
1. 자원 : n개
2. 대기 큐 : 자원을 할당 받지 못한 스레드들이 대기하는 큐
3. counter 변수
P 연산 {
counter--;
if counter < 0 {
... 현재 스레드들 대기 큐에 삽입 ...
}
... 자원 획득 ...
}
V 연산 {
counter++;
if counter <= 0 {
... 대기 큐에서 한 스레드 깨움 ...
}
}
2) busy-wait 세마포
P 연산 {
while counter <= 0;
counter--;
}
V 연산 {
counter++;
}
1) 세마포 구조체
2) 세마포 조작 함수들
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
sem_t toiletsem; // POSIX 세마포 구조체로 모든 스레드에 의해 공유
void* guestThread(void* arg){ // 고객의 행동을 묘사하는 스레드 코드
int cnt = -1;
sem_wait(&toiletsem); // P연산. 자원 사용 요청. 세마포의 counter 값 1 감소
sem_getvalue(&toiletsem, &cnt); // 세마포의 counter 을 cnt 변수로 읽어오기
printf("고객%s 화장실에 들어간다.. 세마포 conter = %d\n" ,(char*)arg, cnt); // 1초동안 화장실을 사용한다.
sem_post(&toiletsem); // V연산. 화장실 사용을 끝냈을음 알림
sem_getvalue(&toiletsem, &cnt); // 세마포의 counter 값을 cnt 변수로 읽어오기
printf("고객%s 화장실에서 나온다. 세마포 counter = %d\n", (char*)arg, cnt);
}
#define NO 0 // 자식 프로세스와 세마포 공유하지 않음
#define MAX_COUNTER 3 // 자원의 개수, 동시에 들어갈 수 있는 스레드의 개수
int main(){
int counter = -1;
char *name[] = {"1", "2", "3", "4", "5"};
pthread_t t[5]; // 스레드 구조체
//세마포 초기화 : MAX_COUNTER 명이 동시에 사용
sem_init(&toiletsem, &counter);
sem_getvalue(&toiletsem, &counter); // 세마포의 현재 counter 값 읽기
printf("세마포 counter = %d\n", counter);
for(int i = 0; i < 5; i++) pthread_create(&t[i], NULL, guestThread, (void*)name[i]); // 5명의 고객 스레드 생성
for(int i = 0; i< 5; i++) pthread_join(t[i], NULL); // 모든 고객이 소멸할 때까지 대기
sem_getvalue(&toiletsem, &counter); // 세마포의 현재 counter 값 읽기
printf("세마포 counter = %d\n", counter);
sem_destroy(&toiletsem); // 세마포 기능 소멸
return 0;
}
-> 3개의 칸이 있는 화장실을 5명의 고객이 사용하고자 할 때 세마포를 이용하여 3칸의 화장실을 5명의 고객 스레드가 활용할 수 있게 관리하는 예시
1) 카운터 세마포
3) 이진 세마포의 구성 요소
1. 세마포 변수 S
1) 우선순위 올림(priority ceiling)
2) 우선순위 상속(priority inheritance)
1) 생산자 소비자 문제란?
2) 생산자 소비자 문제를 코딩할 때 구체적으로 해결해야하는 3가지 문제
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <stdlib.h>
#define N_COUNTER 4 //공유 버퍼에 저장할 정수 공간의 개수
#define MILLI 1000
void mywrite(int n);
int myread();
pthread_mutex_t critical_section;
sem_t semWrite, semRead; //POSIX 세마포
int queue[N_COUNTER]; //공유버퍼
int wptr; //queue[]에 저장할 다음 인덱스
int rptr; //queue[]에서 읽을 다음 인덱스
void* producer(void* arg){ //생산자 스레드 함수
for(int i = 0; i<10; i++){
mywrite(i); //정수 i를 공유버퍼에 저장
printf("producer : wrote %d\n", i);
//m 밀리초 동안 잠을 잔다.
int m = rand()%10; //0~9 사이의 랜덤한 정수
usleep(MILLI*m*10); //m*10 밀리초동안 잠자기
}
return NULL;
}
void* consumer(void* arg){ //소비자 스레드 함수
for(int i =0; i<10; i++){
int n = myread(); //공유버퍼의 맨 앞에 있는 정수 읽어 리턴
printf("\tconsumer : read %\n", i);
//m 밀리초동안 잠을 잔다
int m = rand()%10; //0~9 사이의 랜덤한 정수
usleep(MILLI*m*10); //m*10 밀리초동안 잠자기
}
return NULL;
}
void mywrite(int n){ //정수 n을 queue[]에 삽입
sem_wait(&semWrite); //queue[]에 쓸 수 있는지 요청
pthread_mutex_lock(&critical_section); //뮤텍스 락 잠그기
queue[wptr] = n; //버퍼에 정수 n을 삽입
wptr++;
wptr% = N_COUNTER;
pthread_mutex_unlock(&critical_section); //뮤텍스 락 열기
}
int myread() { //queue[] 맨 앞에 있는 정수를 읽어 리턴
sem_wait(&semRead); //queue[]에서 읽을 수 있는지 요청
pthread_mutex_lock(&critical_section); //뮤텍스 락 잠그기
int n = queue[rptr]; //버퍼에서 정수를 읽는다.
rptr++;
rptr %= N_COUNTER;
pthread_mutex_unlock(&critical_section); // producer 스레드 깨우기
sem_post(&semWrite);
return n;
}
int main(){
pthread_t t[2]; //스레드 구조체
srand(time(NULL)); //난수 발생을 위한 seed 생성
pthread_mutex_init(&critical_section, NULL); //뮤텍스 락 초기화
//세마포 초기화 : N_COUNTER 개의 자원으로 초기화
sem_init(&semWrite, 0, N_COUNTER); //가용버퍼의 개수를 N_COUNTER로 초기화
sem_init(&semRead, 0, 0); //가용버퍼의 개수를 0으로 초기화
//producer와 consumer 스레드 생성
pthread_create(&t[0], NULL, producer, NULL); //생산자 스레드 생성
pthread_create(&t[1], NULL, consumer, NULL); //소비자 스레드 생성
for(int i = 0; i<2; i++)
pthread_join(t[i], NULL); //모든 스레드가 소멸할 때까지 대기
sem_destroy(&semRead); //세마포 기능 소멸
sem_destroy(&semWrite); //세마포 기능 소멸
pthread_mutex_destroy(&critical_section); //뮤텍스 락 소멸
return 0;
}
이 글이 문제가 된다면 삭제하겠습니다.
안녕하세요 좋은 글 잘 보고 있습니다, 궁금한점이 있어 댓글 남깁니다. 생산자 소비 문제를 다루는 응용 프로그램의 mywrite 함수 내에 sem_post(&semRead) 가 누락 되어 있는데 왜 그런 걸까요,,?