상호작용 프로세스 : 컴퓨터에는 여러 프로세스들이 집합을 이루고 있다. 그런 프로세스들이 서로 상호작용을 한다는 의미이다. 쓰레드끼리의 상호작용도 해당개념에 포함된다.
상호작용이 원활히 되면 좋겠지만 결정성, 상호배제와 동기화, 교착상태, 기아 등의 이슈가 존재한다.
해당 파트에서는 결정성, 상호배제와 동기화에 대해 중점적으로 학습한다.
프로세스 시스템
프로세스 시스템 : 상호작용 프로세스의 모델링이다. 프로세스의 집합과 이들의 선행 제약으로 정의된다.
선행제약 : 프로세스의 집합 내에서 프로세스 간의 제약 관계를 뜻한다. 예들 들어 순서에 대한 제약관계가 있다. 해당 순서관계는 부분 순서의 성질을 갖는다. 즉, 집합에 존재하는 모든 프로세스 간에 정의된 것이 아니고 그 중 일부에만 정의된다. 해당 순서관계를 가진 프로세스들 간에는 이행성이라는 성질이 생긴다. 예를 들어 p1이 p2에 선행하고 p2가 p3에 선행하면 이 두 관계를 통해 p1이 p3에 선행한다.
만약 선행제약 관계가 없는 두 프로세스가 있을 경우, '두 프로세스는 독립적이다'라고 표현한다.
단, 프로세스들은 서로 독립이거나 관계가 있다면 해당 선행제약관계는 비순환적이어야 한다.
'비순환적' 이란 프로세스 간 관계가 가위바위보와 같이 순환되는 관계가 되어선 안된다는 것이다. 즉, 관계는 단순한 일직선으로 이루어져야한다. 순환되는 부분이 단 한 부분이라도 있어선 안된다.
cf. 선행 그래프 : 프로세스 시스템 내의 프로세스간 선행 제약관계를 Directed 그래프로 표현한 것
간섭 : 프로세스 간의 공유변수가 존재하여 두 프로세스가 서로 영향을 주고 받는 것이다. 예를 들어 프로세스 p1과 p2는 서로 독립적인 관계이면서 변수 a와 b를 공유한다면, 실행순서에 따라 결과가 달라질 위험이 있다. 이는 프로세스 시스템이 온전히 구성되더라도 발생할 수 있다.
결정성과 프로세스 비간섭 관계
결정성 : 프로세스 시스템 내의 프로세스들 간의 코드 실행 순열이 매번 다를 수 있지만 같은 조건과 입력이 주어진다면, 항상 같은 결과를 산출해야 한다는 성질이다.
즉, 프로세스들이 선행제약 관계를 가지고 있더라도 독립적인 관계가 존재하기 때문에 실행 순서는 다를 수 있다. 하지만 같은 조건과 입력이 주어지면 반드시 같은 결과를 산출해야한다는 것이 결정성이다.
결정성을 유지하기 위해서는 프로세스간의 간섭이 존재해선 안된다. 이를 프로세스 비간섭 관계라고 한다.
정확히는 프로세스 시스템에 두개의 프로세스가 있을 때, 한 프로세스가 다른 프로세스를 아예 선행하거나 / 독립적일 경우 한 프로세스의 출력 장소가 다른 프로세스의 입력 장소나 출력 장소가 아니면, 이들은 비간섭 관계에 있다고 정의한다.
한 프로세스 시스템에서 모든 프로세스 쌍이 비간섭 관계를 만족하면 해당 시스템이 비간섭 관계를 만족한다고 한다. 이는 시스템이 결정적이도록 하는 필요충분적 조건이다. (비간섭을 보장하면 프로세스 시스템에 결정성이 생긴다)
결정성 지원 방법
개발자가 시스템을 구성할 때 결정성을 만족하도록 하는 방법에는 두가지가 있다.
첫 번째로는 선행관계를 명시하도록 지원하는 것이다. 예를 들면 parbegin과 parend를 사용하는 방법이 있다. parbegin과 parend를 사용하면 parbegin과 parend 사이의 각 문장들이 독립적으로 실행됨을 명시할 수 있다.
S0, parbegin, S1, S2, parend, S3의 순서로 명시하면, S1과 S2는 독립적이고 S3에 선행이며, SO는 그들보다 선행하는 관계임을 나타낼 수 있다.
fork와 join으로도 비슷한 형태를 만들 수 있다.
두 번째로는 공유 변수 간섭문제를 해결하도록 지원하는 것이다. 해당 기능의 구현을 위해 임계구역, 상호배제, 세마포어와 같은 수단을 사용한다.
이를 통해 '동기화된' 프로그램을 제공한다고 표현하는데, 여기서 '동기화된'이란 병렬로 돌던 프로그램을 순서있게 돌게하여 간섭을 배제한다는 의미이다.
FIFO, RR 쓰레드 사용 예제 주석 설명
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sched.h>
#include <linux/sched.h> // sched 관련 정보 사용가능하도록 라이브러리 호출
#include <sys/types.h>
#include <pthread.h>
#include <math.h>
#include <time.h>
#include <stdlib.h>
#define NUM_THREADS 20 // 일반적 쓰레드 20개
#define NUM_THREADS_FIFO 1 // FIFO 쓰레드 1개
#define NUM_THREADS_RR 1 // RR 쓰레드 1개
// 다른 쓰레드들이 도는 동안 FIFO와 RR의 수행시간이 어느정도 지켜지느냐?
int flag = 0; // 쓰레드가 동시에 시작될 수 있도록 방아쇠
char str_buf[16384]; // 자료 저장 버퍼
int buf_idx = 0;
char junk_buf[16384]; // 정크 버퍼
void set_my_sched_policy(int my_policy) // 내 쓰레드가 지금부터 이러한 정책으로 돌겠다.
{
int policy;
pthread_attr_t attr;
/* get the default attributes */
pthread_attr_init(&attr); // 쓰레드 특성 초기화
/* set the scheduling policy - real-time */
if (pthread_attr_setschedpolicy(&attr, my_policy) != 0) // 현재 policy 변경
fprintf(stderr, "Unable to set policy.\n");
/* get the current scheduling policy */
if (pthread_attr_getschedpolicy(&attr, &policy) != 0)
fprintf(stderr, "Unable to get policy.\n");
else { // 현재 policy 변경되었는지 내용 확인 후 출력
if (policy == SCHED_OTHER)
printf("SCHED_OTHER\n");
else if (policy == SCHED_RR)
printf("SCHED_RR\n");
else if (policy == SCHED_FIFO)
printf("SCHED_FIFO\n");
}
}
void *runner(void *);
void *runner_FIFO(void *);
void *runner_RR(void *);
int main(int argc, char *argv[])
{
int i, policy;
pthread_t tid[NUM_THREADS];
pthread_t tid_FIFO[NUM_THREADS_FIFO];
pthread_t tid_RR[NUM_THREADS_RR];
//
// EXECUTING NORMAL THREADS (SCHED_OTHER)
//
/* create SCHED_OTHER threads */
for (i = 0; i < NUM_THREADS; i++)
pthread_create(&tid[i],NULL,runner,NULL);
//
// EXECUTING FIFO THREADS
//
/* create real-time threads */
for (i = 0; i < NUM_THREADS_FIFO; i++)
pthread_create(&tid_FIFO[i],NULL,runner_FIFO,NULL);
//
// EXECUTING RR THREADS
//
/* create real-time threads */
for (i = 0; i < NUM_THREADS_RR; i++)
pthread_create(&tid_RR[i],NULL,runner_RR,NULL);
//
// RELEASE THREADS
//
flag = 1; // 쓰레드 홀딩하다가 실행
//
// DO 'JOIN' ON ALL THREADS
//
/* now join on each thread */
/*
for (i = 0; i < NUM_THREADS; i++){
pthread_join(tid[i], NULL);
} // FIFO와 RR이 끝나면 걍 끝나도록
*/
for (i = 0; i < NUM_THREADS_FIFO; i++){
pthread_join(tid_FIFO[i], NULL);
}
for (i = 0; i < NUM_THREADS_RR; i++){
pthread_join(tid_RR[i], NULL);
}
str_buf[buf_idx] = '\0';
printf("%s\n", str_buf);
return 0;
}
/* Each thread will begin control in this function */
void *runner(void *param)
{
int i, policy;
pthread_attr_t attr;
while(!flag) sleep(0); // sleep(0)은 스케줄링 한번만 시도해봐의 의미
set_my_sched_policy(SCHED_OTHER); // 일반 스케줄링 정책 설정
for(int i=0; i<=100; i++){
for(int j=0; j<=500; j++){
srand(time(0)); // random 넘버 시드 설정
sprintf(junk_buf, "%f", pow(rand(), 10000));
// 랜덤 넘버호출 후 그의 10000승 정크 버퍼에 출력
}
str_buf[buf_idx++] = '.';
}
}
void *runner_FIFO(void *param)
{
int i, policy;
pthread_attr_t attr;
while(!flag) sleep(0);
set_my_sched_policy(SCHED_FIFO); // FIFO 스케줄링 정책 설정
for(int i=0; i<=50; i++){
for(int j=0; j<=100; j++){
srand(time(0));
sprintf(junk_buf, "%f", pow(rand(), 10000));
}
usleep(55000); // 55ms sleep
str_buf[buf_idx++] = '!';
str_buf[buf_idx++] = '\n';
}
}
void *runner_RR(void *param)
{
int i, policy;
pthread_attr_t attr;
while(!flag) sleep(0);
set_my_sched_policy(SCHED_RR); // RR 스케줄링 정책 설정
for(int i=0; i<=50; i++){
for(int j=0; j<=100; j++){
srand(time(0)); sprintf(junk_buf, "%f", pow(rand(), 10000));
}
usleep(45000); // 45ms sleep
str_buf[buf_idx++] = '@';
str_buf[buf_idx++] = '\n';
}
}
// 완벽한 규칙은 아니지만, 어느정도의 규칙성. 즉 연성 실시간성을 보여준다.