Thread-state하다는 의미는 두 개 이상의 스레드가 경쟁 상태에 들어가거나 같은 객체에 동시에 접근해도 연산 결과는 정합성이 보장될 수 있게 메모리 가시성이 확보된 상태이다.
두 개 이상의 프로세스의 동기화 문제를 해결하는 방법 중 하나이다.
// flag: 신호, 공유자원을 사용하고 싶다라고 표현하기 위한 변수, 임계구역에 들어갈 때는 true, 나올 때는 false로 설정
// turn: 차례, 누구 차례인지를 명시하는 변수, turn = 0 이면, 0번째 프로세스가 임계 구역에 들어가고, 1번째 프로세스가 기다린다.
bool flag[2];
int turn;
while(true) {
// i번째 프로세스가 공유자원을 사용하고 싶다는 신호를 전달하기 위해 flag를 true로 바꿔준다.
flag[i] = true;
// i 번째 프로세스가 쓰기 전에 먼저 쓰고 싶어했던 프로세스가 있는지 확인하고 수행시켜주는 코드
// 예를 들어 j번째 프로세스가 이 공유 자원을 쓰고 싶어 했으면 i번째 프로세스는 j번째 프로세스에 차례를 양보한다.
turn = j;
// busy waits 상태. turn이 j일 경우, 내 차례가 아니고 j가 자원까지 쓰고 싶어하면 나는 spinlock에 머무른다.
// 이제 내 차례거나 j가 자원을 쓰고 싶지 않은 경우(!flag[j]), spinlock을 빠져나와 임계 구역에 진입할 수 있다.
//turn과 flag를 통해 동시 접근을 막는다.
while(flag[j] && turn == j);
// critical section
// 임계 구역에 진입해서 작업을 한다.
flag[i] = false; // 다 썼으면 flag를 false로 바꿔준다.
//remainder section
}
그러나 Peterson’s Algorithm은 현대 컴퓨터 아키텍처에서는 보장되지 않은 작동을 할 가능성이 있다. 예를 들어 시스템 성능을 향상시키기 위해서 프로세서 또는 컴파일러가 종속성이 없는 읽기 및 쓰기 작업의 순서를 멋대로 바꿀 수 있다. 또한 공유 데이터가 있는 멀티스레드 응용 프로그램의 경우, 명령 순서를 변경하면 일관성이 없거나 예상하지 못한 결과가 발생할 수 있다.
공유 자원에 대하여 여러 프로세스가 동시에 접근을 시도할 때, 타이밍이나 순서 등이 결과값에 영향을 줄 수 있는 상태이다. 즉 실행 순서에 따라 결과값이 달라지는 일관적이지 못한 상태를 의미한다.
반드시 락을 구현하는 것이 아닌, 설계 자체를 Thread-Safe하게 할 수 있다.
어떤 함수가 한 스레드에 의해 호출되어 실행 중일 때, 다른 스레드가 그 함수를 호출하더라도 그 결과가 각각 올바르게 주어지도록 설계하는 것이다.
공유 자원을 사용할 경우 해당 자원에 대한 접근을 Lock으로 통제하는 것이다. Lock도 Thread-safe를 가져가는 방법 중 하나이다.
공유 자원에 접근할 때 원자 연산, 원자적으로 정의된 접근 방법을 사용하는 것이다.
객체 생성 이후에 값을 변경할 수 없도록 하는 것이다.
정리하자면 공유 변수는 최소화하고, 공유 변수가 있다면 최대한 캡슐화하며, 관련한 문서화를 잘 이루는 설계로 Thread-safe한 설계를 할 수 있다.
병렬 작업 처리 증가는 스레드 개수 증가와 같다. 스레드 생성과 스케줄링으로 인해 CPU가 과부하되고 애플리케이션 성능이 저하되는 것을 방지하기 위해, 작업 처리에 사용되는 스레드를 제한된 개수만큼 정해 놓고 작업 큐에 들어오는 작업을 스레드가 하나씩 맡아 처리하는 것이다.
작업 요청이 폭증해도 작업큐에서 작업이 대기하다가 여유 있는 스레드가 그것을 맡아서 처리하기 때문에 스레드의 전체 개수는 일정하다. 애플리케이션의 성능 저하도 일어나지 않는다.
Monitor은 여러 스레드가 객체로 동시에 접근하는 것을 막아 상호 배제를 도와주는 Semaphore의 발전 버전이다. Monitor은 두 가지의 큐를 사용한다.
분할 정복 알고리즘을 사용해 재귀적으로 병렬 처리를 해 작업을 수행하는 것이다. 작업 단위가 작으면 해당 작업을 수행하고, 그렇지 않으면 작업을 반으로 쪼개서 두 개의 작업으로 나눈 뒤 두 작업을 동시에 실행시킨다. 이때, Work Stealing 알고리즘을 사용한다.
Work Stealing
: 여러 개의 Deque에서 작업 처리 시, 하나의 Deque는 바쁘고 하나는 여유롭다면, 한가한 Deque가 바쁜 Deque의 꼬리에 있는 일을 가져가서 처리한다. 이 방식은 스레드가 작업을 위해 경쟁하는 것을 방지할 수 있다.
Active Users = TPS * Response Time
1명의 사용자가 시스템에 요청을 보낼 때 응답이 1초에 처리 → 초당 처리건수(TPS)는 1건
초당 처리건수(TPS)가 1건이면 2명이 요청하였을 때 응답 시간은 2초가 발생
1명이 요청을 보낼 때 응답이 0.5초에 처리 → 초당 처리 건수(TPS)는 2건
2명이 요청을 보낼 때 응답이 0.5초에 처리 → 초당 처리 건수(TPS)는 4건
적정 스레드 수 = CPU 개수 * (1 + 대기나 유휴 시간 / 서비스 시간)
리틀의 법칙 등과 같은 이론으로 시스템의 성능을 측정하고, 목표치에 맞는 시스템 성능에 도달하도록 스레드 개수를 조절한다.
정렬 알고리즘은 다양하고 많아서 무엇이 안정적이고 좋은 성능이라고 말하기는 어렵다. 다만 요구되는 상황과 여러 실험을 통해 정렬 알고리즘을 비교하고 때에 맞춰 선택하면 된다.
페이지
프로세스의 논리 주소 공간을 일정 단위로 자른 파편이다. 메모리의 물리 주소 공간을 나눈 것은 프레임이라고 부른다.
Thrashing이란 페이지 부재율이 증가하여 CPU 이용률이 급격하게 낮아지는 현상을 이야기한다. 프로세스를 처리하는 시간보다 메모리에 올라오지 못한 페이지를 불러오느라 페이지 교체에 드는 시간이 증가하고, 이로 인해 CPU 이용률이 떨어진다.
운영체제는 CPU 이용률이 낮으면 MPD(Multi-Programming Degree)라는 다중 프로그래밍 정도의 강도를 높이게 된다. 이는 동시에 실행하는 프로세스가 많아질수록 프로세스에 할당된 메모리 페이지와 프레임들의 간격도 좁아지게 하는 결과를 초래한다. 너무 작은 페이지 프레임을 할당받은 프로세스들은 또 페이지를 불러오는 부재가 증가하게 되어 CPU의 이용률이 더 떨어지는 악순환이 생긴다.
지역성의 원리를 이용하여 지역성 집합이 메모리에 동시에 올라갈 수 있도록 보장하는 메모리 관리 기법이다.
워킹셋이라는 이름과 같이 프로세스의 작업에 자주 참조되는 페이지들을 묶는다고 생각하면 된다.
프로세스의 페이지 부재율을 주기적으로 조사하고, 이 값에 근거하여 각 프로세스에 할당할 메모리 양을 동적으로 에측하고 조절하는 알고리즘이다. 현재 페이지 부재와 직전 페이지 부재 사이의 시간을 관찰하여 상한값을 초과하거나 하한값 미만이 되면 운영체제가 메모리에 올라가 있는 프로세스 수를 조절한다.