Efficient Android Threading #1 자바의 멀티스레딩,안드로이드 스레드
이 글은 Efficient Android Threading 의 일부 내용만 발췌한 내용입니다.
자세한 내용은 책을 구입해서 보세용.
-
동시 실행 설계
자원의 생성과 해체의 빈도를 감소시키기 위해 항상 새로운 스레드를 만드는 것보다 재사용을 권장한다.
필요 이상으로 스레드를 사용하지 않는다. 사용하는 스레드가 많을수록 더 많은 메모리와 프로세서 시간이 소비된다.
-
App 관점에서 스레드는 UI, 바인더, 백그라운드 thread 로 3가지 유형이 있다.
-
바인더 스레드는 IPC 에 사용된다.
각 프로세스는 Thread pool 을 유지한다.
Thread pool 은 종료되거나 재생성되지 않지만, 프로세스 안에서 다른 스레드의 요청에 따라 테스크를 실행 할 수 있다.
이런 스레드는 시스템 서비스, 인텐트, 콘텐츠 프로바이더, 서비스 등 다른 프로세스에서 들어오는 요청을 처리한다.
필요한 경우 들어오는 요청을 처리하기 위해 새로운 바인더 스레드가 생성된다.
대부분의 경우 응용프로그램은 바인더 스레드에 대해 신경 쓸 필요가 없다.
일반적으로 플랫폼은 UI 스레드를 먼저 사용하도록 요청을 변형하기 때문이다.
App 이 AIDL 인터페이스를 통해 다른 프로세스로 바인딩 할 수 있는 서비스를 제공하는 경우는 예외다.
-
백그라운드 스레드는 UI 스레드에서 파생되었기 때문에 UI 스레드의 속성(우선순위) 등을 상속받는다.
App 이 만든 백그라운드 스레드는 "ps -t” 명령을 통해 확인할 수 있다.
ex) ps 는 process status 의 약자, -t 는 thread 정보
-
리눅스 프로세스는 다음의 성질을 갖는다.
UID
PID
PPID : 부모 프로세스 ID 로 안드로이드에서 모든 앱의 부모는 Zygote 이다.
Stack
Heap : 프로세스 전용으로 유지되며, 다른 프로세스에 의해 접근될 수 없다.
-
앱이 생성하고 시작한 모든 스레드는 포직스(POSIX) 표준에 의해 정의된 리눅스 네이티브 스레드, 즉 pthread 이다.
스레드는 자신을 생성한 프로세스에 소속된다.
-
안드로이드에서 응용 프로그램 스레드는 달빅 가상 머신이 아닌 리눅스 커널의 표준 스케줄러에 의해 스케줄링된다.
실제로 이것은 우리 앱의 스레드들이 실행 시간을 위해 앱 내의 스레드끼리 직접 경쟁할 뿐만 아니라 다른 모든 앱의 모든 스레드와도 경쟁함을 의미한다.
-
리눅스 커널 스케줄러는 완전히 공정한 스케줄러(Completely Fair Scheduler, CFS) 로 알려져 있다.
여기서 “공정한” 이란 스레드의 우선 순위뿐만 아니라 스레드에 부여된 실행 시간의 양을 추적하여 테스크의 실행이 균형을 유지하려 한다는 의미를 나타낸다.
예를 들어 어떤 스레드가 이전에 프로세서에 적게 할당되었다면, 더 높은 우선순위의 스레드 이전에 실행되도록 허용될 것이다.
또한 어떤 스레드가 실행에 할당된 시간을 사용하지 않았다면, 미래에 실행 시간을 덜 할당받도록 우선순위가 낮아질 것이다.
-
플랫폼은 스레드 스케줄링에 영향을 미치는 방법을 크게 두 가지 가지고 있다.
1. 우선순위
2. 컨트롤 그룹 : 안드로이드 특정 컨트롤 그룹을 변경한다.
-
리눅스에서 스레드 우선순위는 나이스니스값(niceness value) 또는 나이스값(nice value)라고 불린다.
이 값은 기본적으로 하나의 스레드가 다른 스레드에 대해 얼마나 좋게(nice하게)대하는지 알려준다.
따라서 낮은 나이스니스는 높은 우선순위에 해당한다.
안드로이드에서 리눅스 스레드는 -20(가장 높은 우선순위)과 19(가장 낮은 우선순위) 사이의 나이스니스값을 가지며,
기본 나이스니스는 0이다.
-
스레드의 우선순위는 스레드를 시작시킨 부모 스레드의 우선순위를 상속받고, 앱에 의해 명시적으로 변경되지 않는 한 그 값을 유지한다.
앱 내에서는 다음을 통해 우선순위가 조정될 수 있다.
thread.setPrioirty(int priority);
priority 는 1 ~ 10 사이의 값을 갖으며 높을수록 우선순위가 높다.
이 녀석은 플랫폼에 독립적이며, 하부 플랫폼 특유 스레드 우선순위의 추상화를 나타낸다.
젤리빈 기준 1 값은 리눅스 19, 10 값은 -8 에 매칭된다.
Process.setThreadPriority(int priority);
Process.setThreadPriority(int threadId, int priority);
여기의 priority 는 리눅스의 나이스니스 값 ( -20 ~ 19 ) 이다.
-
안드로이드는 스레드 스케줄링을 위해 일반적인 리눅스 CFS 에 의존할 뿐 아니라 모든 스레드를 스레드 컨트롤 그룹( 리눅스 cgroup )으로 묶는다.
스레드 컨트롤 그룹은 하나의 컨테이너 안에 들어 있는 모든 스레드에 대해 프로세서 시간의 할당을 관리하는 데 사용하는 리눅스 컨테이너다.
앱에서 생성된 모든 스레드는 스레드 컨트롤 그룹 중 하나에 속한다.
안드로이드는 여러 컨트롤 그룹을 정의하지만, 앱을 위한 가장 중요한 그룹은 포그라운드 그룹(foreground group)과 백그라운드 그룹(background group)이다.
다른 컨트롤 그룹의 스레드끼리는 프로세서의 실행 시간이 서로 다른 값으로 할당될 수 있도록 안드로이드 플랫폼은 실행의 제약 조건을 정의한다.
포그라운드 그룹의 스레드는 백그라운드 그룹의 스레드보다 더 많은 실행 시간을 할당받는다.
-
컨트롤 그룹 사이의 스레드 이동은 앱이 눈에 보이거나 보이지 않게 되면 즉시 이루어진다.
컨트롤 그룹의 사용은 화면에 보이는 앱의 성능을 증가시키고, 사용자에게 실제로 보이는 앱을 저해할 수 있는 백그라운드 앱의 위험을 감소시켜 사용자 경험을 향상시킨다.
-
기본적으로 앱에 의해 생성된 스레드는 UI 스레드와 같은 우선순위를 가지고, UI 스레드와 같은 컨트롤 그룹의 구성원이 되어 그들과 동등한 조건에서 프로세서 할당을 경쟁한다.
따라서 많은 백그라운드 스레드를 생성한 앱은, 의도는 그 반대였을지 몰라도 UI 스레드의 성능을 감소시킬 수 있다.
이 문제를 해결하기 위해 기본적으로 앱 스레드가 실행하는 컨트롤 그룹에서 백그라운드 스레드를 분리하는 것은 실현 가능한 일이다.
이렇게 분리하려면, 백그라운드 스레드의 우선순위를 충분히 낮게 설정하여 앱이 화면에 보이는 중이더라도 백그라운드 스레드는 항상 백그라운드 그룹에 속하게 해야 한다.
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND) 로 어떤 스레드의 우선순위를 낮추면, 우선순위가 낮아질 뿐만 아니라 그 스레드는 앱 프로세스 수준에서 분리되어 항상 백그라운드 그룹에 속하게 된다.
'프로그래밍 놀이터 > 안드로이드, Java' 카테고리의 다른 글
Efficient Android Threading #3 프로세스 간 통신 (0) | 2018.03.19 |
---|---|
Efficient Android Threading #2 스레드 통신 (0) | 2018.03.18 |
[android] PDF file 읽는 방법 (0) | 2018.03.16 |
[android] Dependency conflict 해결하기 (0) | 2018.03.15 |
[android] 잘 쓰지 않지만 유용한 android library 들 ( Spell Checker,Text Recognizer, TimeLogger, MediaProjection, PDF Creation ) (0) | 2018.03.14 |
댓글