스레드
스레드 기본
- 태스크 : 프로그램에서 작업의 단위. 프로그래밍 언어 관점에서는 어떤 작업을 수행하기 위해 순차적으로 진행되는 코드들의 묶음으로 볼 수 있다.
- 스레드 : 하나의 프로세서(CPU)에서 태스크의 실행
- 하나의 프로세서는 한번에 하나의 명령어(코드 한줄)씩 처리할 수 있다.
- 실행되는 스레드의 개수가 프로세서보다 많으면? 스케쥴러를 이용해 스레드를 바꿔가며 처리한다.
- 스케쥴러는 스레드마다 가지고있는 우선순위를 이용해 처리순서를 조절한다.
싱글스레드 vs 멀티스레드 프로그램
싱글스레드
- 장점
- 스레드 처리로직이 없으므로
코드 복잡도가 낮다.
- 코드 & 실행순서가 매우 간결함
- 단점
- 특정 동작이 오래걸릴 경우, 프로그램 전체가 지연되어
성능이 떨어진다.
멀티스레드
- 장점
- 오래걸리는 동작을 다른 스레드로 분리하여, 전체 프로그램은 지연되지 않으므로
성능이 떨어지지 않는다.
- 단점
- 스레드 처리로직이 추가되어야 하므로
코드 복잡도가 높다.
- 실행순서를 예측하기 어려움
- 메모리 등,
시스템 리소스의 사용량이 증가
한다.
스레드 안전(Thread-Safety)
- 여러 스레드에서 객체에 접근할 때, 객체가 항상 정확한 상태를 유지하는 상태
암시적 잠금
- 자바의
synchronized
키워드를 사용
- 필요 이상의 잠금영역을 만들면, 성능이 저하될 수 있다.
synchronized void changeState() {
sharedResource++;
}
void changeState() {
synchronized (this) {
sharedResource++;
}
}
private final Object mLock = new Object();
void changeState() {
synchronized (mLock) {
sharedResource++;
}
}
synchronized static void changeState() {
staticSharedResource++;
}
static void changeState() {
synchronized (Myclass.class) {
staticSharedResource++;
}
}
명시적 잠금
- 잠금을 세부적으로 컨트롤할 필요가 있을 때 주로 사용.
ReentrantLock
, ReentrantReadWriteLock
클래스를 사용
ReentrantLock
- 일반적인 Lock - synchronized 키워드와 동일한 효과
int sharedResource;
private ReentrantLock mLock = new ReentrantLock();
public void changeState() {
mLock.lock();
try {
sharedResource++;
} finally {
mLock.unlock();
}
}
ReentrantReadWriteLock
- 공유메모리에 데이터를 Write 할때에만 잠금.
- 읽기 스레드만 있을 때 : 잠금 동작 안함
- 쓰기 스레드가 하나라도 있을 때 : 잠금 동작
- 현재 스레드의 허용 / 차단여부를 확인해야 하므로, 단순한 잠금보다는 성능저하가 있을 수 있다.
int sharedResource;
private ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
public void changeState() {
mLock.writeLock().lock();
try {
sharedResource++;
} finally {
mLock.writeLock().unlock();
}
}
public int readState() {
mLock.readLock().lock();
try {
return sharedResource;
} finally {
mLock.readLock().unlock();
}
}
스레드 설계 전략
- 태스크를 적당한 크기로 구성한다. 너무 큰 크기의 태스크라면, 쪼개는 것을 고려한다.
- 태스크간의 실행순서를 파악한다.
- 순차적인 실행이 필요한 태스크 : 하나의 스레드에서 순차적으로 수행
- 순서에 무관한(동시 실행이 가능한) 태스크 : 여러 스레드에서 동시에 수행
- 공유자원을 어떻게 관리할지 고려한다.
안드로이드에서의 스레드
- 안드로이드는 리눅스를 기반으로 하므로, OS관점에서 모든 응용프로그램의 스레드는 리눅스의
pthreads
를 기반으로 되어있다.
응용프로그램 스레드
- 응용프로그램 관점에서의 스레드는
UI
, 바인더
, 백그라운드
의 3가지 유형으로 볼 수 있다.
UI 스레드
- 응용프로그램의 메인스레드 - 응용프로그램의 프로세스와 같은 수명을 가짐
- UI를 변경할 수 있는 유일한 스레드
- UI요소는 이 스레드에서만 접근할 수 있으므로, 동시성문제의 영향을 받지 않는다.
- UI스레드에서 처리하는 태스크가 많으면, UI 갱신(렌더링)이 느려지기 때문에 사용자의 입장에서는 반응성이 좋지 않다고 느끼게 된다.
바인더 스레드
- 서로 다른 프로세스에서, 스레드사이의 통신에 사용
- 다른 프로세스로부터 들어오는 요청을 처리하기 위해 만들어진다.
- 일반적으로 플랫폼은 UI스레드를 먼저 사용하도록 다른 프로세스로부터 들어오는 요청을 변경하므로, 대부분의 경우에는 바인더 스레드에 대해 신경 쓸 필요가 없다.
백그라운드 스레드
- 응용프로그램에서 명시적으로 생성하는 모든 스레드
- UI 스레드로부터 파생된 스레드이므로, 특별한 설정변경이 없으면 UI 스레드의 속성을 상속받는다.
- OS관점에서는 UI 스레드와 백그라운드 스레드는 같은 평범한 pthread 이므로 동등하게 처리된다.
리눅스 스레드 특징
- 안드로이드는 리눅스 기반이므로, 안드로이드의 스레드는 리눅스의 스레드와 같다.
스케쥴링
- 스레드의 개수가 프로세서보다 많은 경우, 프로세서는 스레드를 효율적으로 처리하기 위해 스레드를 스케쥴링한다.
- 스케쥴링은 모든 응용프로그램의 스레드를 대상으로 이루어진다.
- 스케쥴링에 영향을 미치는 요소로는
우선순위
와 컨트롤그룹
이 있다.
우선순위
- 리눅스에서 사용하는 우선순위값을 그대로 이용한다.(나이스니스(niceness) 라고 불림)
- 응용프로그램 관점에서는 자바에서 표현하는 우선순위를 변경하거나, 직접 나이스니스값을 변경하여 우선순위를 조정할 수 있다.
자바의 우선순위 이용
- 자바에서 플랫폼 독립성을 구현하기 위해 제공하는 스레드 우선순위. 안드로이드 내부적으로는 이 우선순위의 값이 나이스니스값에 대응되어 사용된다.
0 ~ 10
사이의 값.(10 : 최고 우선순위, 0 : 최저 우선순위)
- 기본값은
5
- java.lang.Thread 클래스의
setPriority(int priority)
메서드를 이용
- ex)젤리빈에서의 자바 우선순위 - 나이스니스 대응 값
setPriority(priority) | 나이스니스 |
1 (Thread.MIN_PRIORITY) | 19 |
2 | 16 |
3 | 13 |
4 | 10 |
5 (Thread.NORM_PRIORITY) | 0 |
6 | -2 |
7 | -4 |
8 | -5 |
9 | -6 |
10 (Thread.MAX_PRIORITY) | -8 |
나이스니스 값 이용
-20 ~ 19
사이의 값.(-20 : 최고 우선순위, 19 : 최저 우선순위)
- 기본값은
0
- android.os.Process 클래스의
setThreadPriority()
정적 메서드를 이용
- setThreadPriority(int priority) : 이 메서드를 호출한 스레드의 나이스니스 변경
- setThreadPriority(int threadId, int priority) : 해당 스레드의 나이스니스 변경
컨트롤 그룹
- 그룹안에 속한 모든 스레드에 대해, 프로세서 시간할당 수준을 동일하게 관리하기 위해 사용하는 리눅스 컨테이너
- 응용프로그램 관점에서는 foreground / background 그룹 2가지가 가장 중요하다.
- foreground그룹의 스레드들은 background 그룹의 스레드들보다 프로세서의 실행시간을 길게 할당받는다. (background 그룹의 모든 스레드들의 실행시간을 합쳐도 5~10% 이내로 시스템이 제한)
- 기본적으로, 사용자의 눈에 보이는 프로세스에 속한 스레드는 foreground 그룹에 속하도록 시스템이 조절한다.
- 이를 통해 눈에 보이는 응용프로그램에 대한 성능을 보장함
- 현재 눈에 보이는 응용프로그램에서 스레드를 생성하면, 그 스레드는 UI스레드와 같은 우선순위 / 같은 컨트롤 그룹을 가지게 되어, UI스레드의 성능을 감소시킬 여지가 있다.
Process.setThreadPriority()
메서드를 이용하여 Process.THREAD_PRIORITY_BACKGROUND
우선순위로 낮추면, 스레드의 우선순위가 낮아짐과 동시에, 항상 백그라운드 그룹에 속하게 된다. 이를 이용하여, UI스레드의 실행시간을 보장하는 것을 권장한다.
댓글 없음:
댓글 쓰기