剖析Android中進(jìn)程與線程調(diào)度之nice
在計(jì)算機(jī)操作系統(tǒng)中,進(jìn)程是進(jìn)行資源分配和調(diào)度的基本單位,同時(shí)每個(gè)進(jìn)程之內(nèi)也可以存在多個(gè)線程。那么在Android系統(tǒng)(Linux Kernel)中,進(jìn)程是如何去搶占資源,線程又是如何根據(jù)優(yōu)先級(jí)切換呢,本文將嘗試剖析這個(gè)問題,研究nice在Linux以及Android系統(tǒng)中的 應(yīng)用。
一些概念
-
進(jìn)程 是計(jì)算機(jī)系統(tǒng)中,程序運(yùn)行的實(shí)體,也是線程的容器。
-
線程 是進(jìn)程中實(shí)際執(zhí)行單位,一個(gè)線程是程序執(zhí)行流的最小單元。在一個(gè)進(jìn)程中可以有多個(gè)線程存在。
nice與進(jìn)程調(diào)度
Linux中,使用nice value(以下成為nice值)來設(shè)定一個(gè)進(jìn)程的優(yōu)先級(jí),系統(tǒng)任務(wù)調(diào)度器根據(jù)nice值合理安排調(diào)度。
-
nice的取值范圍為-20到19。
-
通常情況下,nice的默認(rèn)值為0。視具體操作系統(tǒng)而定。
-
nice的值越大,進(jìn)程的優(yōu)先級(jí)就越低,獲得CPU調(diào)用的機(jī)會(huì)越少,nice值越小,進(jìn)程的優(yōu)先級(jí)則越高,獲得CPU調(diào)用的機(jī)會(huì)越多。
-
一個(gè)nice值為-20的進(jìn)程優(yōu)先級(jí)最高,nice值為19的進(jìn)程優(yōu)先級(jí)最低。
-
父進(jìn)程fork出來的子進(jìn)程nice值與父進(jìn)程相同。父進(jìn)程renice,子進(jìn)程nice值不會(huì)隨之改變。
詞源考究
nice這個(gè)命令的來源幾乎沒有資料提到,于是便嘗試自己來推斷一下。在諸如詞霸,滬江等詞典給出的意思均為好的;美好的;可愛的;好心的,友好 的。而有道詞典則稍微給出了一個(gè)其他詞典沒有的和藹的。個(gè)人認(rèn)為有道給出的這個(gè)比較合理。要想做到和藹,就需要做到謙讓,因此或多或少犧牲自己一點(diǎn),成全 他人。所以nice值越高,越和藹,但是自己的優(yōu)先級(jí)也會(huì)越低。
renice
對(duì)于一個(gè)新的進(jìn)程我們可以按照下面的代碼為一個(gè)進(jìn)程設(shè)定nice值。
nice -n 10 adb logcat
對(duì)于已經(jīng)創(chuàng)建的進(jìn)程,我們可以使用renice來修改nice值
sudo renice -n 0 -p 24161
該命令需要使用root權(quán)限,-p對(duì)應(yīng)的值為進(jìn)程id。
注意renice命令在Linux發(fā)行版中-n 的值應(yīng)該為進(jìn)程的目標(biāo)優(yōu)先級(jí)。而Mac下-n,則是代表對(duì)當(dāng)前權(quán)限的增加值。 比如在Mac下,講一個(gè)進(jìn)程的nice值由19改成10,可以這樣操作sudo renice -n -9 -p 24161,這一點(diǎn)需要注意,避免掉進(jìn)坑里。
Android中的nice
由于Android基于Linux Kernel,在Android中也存在nice值。但是一般情況下我們無法控制,原因如下:
-
Android系統(tǒng)并不像其他Linux發(fā)行版那樣便捷地使用nice命令操作。
-
renice需要root權(quán)限,一般應(yīng)用無法實(shí)現(xiàn)。
線程調(diào)度
雖然對(duì)于進(jìn)程的優(yōu)先級(jí),我們無法控制,但是我們可以控制進(jìn)程中的線程的優(yōu)先級(jí)。在Android中有兩種線程的優(yōu)先級(jí),一種為Android API版本,另一種是 Java 原生版本。
Android API
Android中的線程優(yōu)先級(jí)別目前規(guī)定了如下,了解了進(jìn)程優(yōu)先級(jí)與nice值的關(guān)系,那么線程優(yōu)先級(jí)與值之間的關(guān)系也就更加容易理解。
-
THREAD_PRIORITY_DEFAULT,默認(rèn)的線程優(yōu)先級(jí),值為0。
-
THREAD_PRIORITY_LOWEST,最低的線程級(jí)別,值為19。
-
THREAD_PRIORITY_BACKGROUND 后臺(tái)線程建議設(shè)置這個(gè)優(yōu)先級(jí),值為10。
-
THREAD_PRIORITY_FOREGROUND 用戶正在交互的UI線程,代碼中無法設(shè)置該優(yōu)先級(jí),系統(tǒng)會(huì)按照情況調(diào)整到該優(yōu)先級(jí),值為-2。
-
THREAD_PRIORITY_DISPLAY 也是與UI交互相關(guān)的優(yōu)先級(jí)界別,但是要比THREAD_PRIORITY_FOREGROUND優(yōu)先,代碼中無法設(shè)置,由系統(tǒng)按照情況調(diào)整,值為-4。
-
THREAD_PRIORITY_URGENT_DISPLAY 顯示線程的最高級(jí)別,用來處理繪制畫面和檢索輸入事件,代碼中無法設(shè)置成該優(yōu)先級(jí)。值為-8。
-
THREAD_PRIORITY_AUDIO 聲音線程的標(biāo)準(zhǔn)級(jí)別,代碼中無法設(shè)置為該優(yōu)先級(jí),值為 -16。
-
THREAD_PRIORITY_URGENT_AUDIO 聲音線程的最高級(jí)別,優(yōu)先程度較THREAD_PRIORITY_AUDIO要高。代碼中無法設(shè)置為該優(yōu)先級(jí)。值為-19。
-
THREAD_PRIORITY_MORE_FAVORABLE 相對(duì)THREAD_PRIORITY_DEFAULT稍微優(yōu)先,值為-1。
-
THREAD_PRIORITY_LESS_FAVORABLE 相對(duì)THREAD_PRIORITY_DEFAULT稍微落后一些,值為1。
使用Android API為線程設(shè)置優(yōu)先級(jí)也很簡(jiǎn)單,只需要在線程執(zhí)行時(shí)調(diào)用android.os.Process.setThreadPriority方法即可。這種在線程運(yùn)行時(shí)進(jìn)行修改優(yōu)先級(jí),效果類似renice。
new Thread () {
@Override
public void run() {
super.run();
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
}
}.start();
Java原生API
Java為Thread提供了三個(gè)級(jí)別的設(shè)置,
-
MAX_PRIORITY,相當(dāng)于android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY,值為10。
-
MIN_PRIORITY,相當(dāng)于android.os.Process.THREAD_PRIORITY_LOWEST,值為0。
-
NORM_PRIORITY,相當(dāng)于android.os.Process.THREAD_PRIORITY_DEFAULT,值為5。
使用setPriority我們可以為某個(gè)線程設(shè)置優(yōu)先級(jí),使用getPriority可以獲得某個(gè)線程的優(yōu)先級(jí)。
在Android系統(tǒng)中,不建議使用Java原生的API,因?yàn)锳ndroid提供的API劃分的級(jí)別更多,更適合在Android系統(tǒng)中進(jìn)行設(shè)定細(xì)致的優(yōu)先級(jí)。
注意
Android API的線程優(yōu)先級(jí)和Java原生API的優(yōu)先級(jí)是相對(duì)獨(dú)立的,比如使用 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND) 后,使用Java原生API,Thread.getPriority()得到的值不會(huì)改變。如下面代碼:
new Thread() {
@Override
public void run() {
super.run();
Log.i(LOGTAG, "Java Thread Priority Before=" + Thread.currentThread().getPriority());
Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
Log.i(LOGTAG, "Java Thread Priority=" + Thread.currentThread().getPriority());
}
}.start();
上述代碼的運(yùn)行日志為
I/MainActivity( 3679): Java Thread Priority Before=5 I/MainActivity( 3679): Java Thread Priority=5
由于上面的這一點(diǎn)缺陷,導(dǎo)致我們?cè)诜治鯝NR trace時(shí)需要注意,在下面的ANR日志信息中,prio=5中proi的值對(duì)應(yīng)的Java原生API的線程優(yōu)先級(jí)。而nice=-6中的nice表示的Android API版本的線程優(yōu)先級(jí)。
"main" prio=5 tid=1 NATIVE
| group="main" sCount=1 dsCount=0 obj=0x41690f18 self=0x4167e650
| sysTid=1765 nice=-6 sched=0/0 cgrp=apps handle=1074196888
| state=S schedstat=( 0 0 0 ) utm=5764 stm=3654 core=2
#00 pc 00022624 /system/lib/libc.so (__futex_syscall3+8)
#01 pc 0000f054 /system/lib/libc.so (__pthread_cond_timedwait_relative+48)
#02 pc 0000f0b4 /system/lib/libc.so (__pthread_cond_timedwait+64)
避免ANR
我在之前的文章說說Android中的ANR中提到使用WorkerThread處理耗時(shí)IO操作,同時(shí)將WorkerThread的優(yōu)先級(jí)降低, 對(duì)于耗時(shí)IO操作,比如讀取數(shù)據(jù)庫,文件等,我們可以設(shè)置該workerThread優(yōu)先級(jí)為THREAD_PRIORITY_BACKGROUND,以 此降低與主線程競(jìng)爭(zhēng)的能力。