線程開的越多就越好嗎|趣談線程池

前言

線程優(yōu)化一直是啟動優(yōu)化中的一個必不可少的項目隔躲。作為一個 Android 程序員摩梧,你肯定希望應(yīng)用啟動的時候,火力全開蹭越,線程池拉滿障本,每一個 CPU 核心滿載而行。

可你把線程池拉滿的時候,啟動時長就一定會降低嗎驾霜?

結(jié)果顯然是否定的案训,之前我在進行啟動優(yōu)化的時候,就遇到了類似的問題粪糙。我引入了有向無環(huán)圖類似的啟動庫后强霎,又將線程池的數(shù)量設(shè)置為:

CPU核心數(shù) * 2 + 1

看似沒什么問題,后續(xù)啟動時長居然還增長了一點點蓉冈。

擺爛

為什么會出現(xiàn)這樣的問題城舞?我們今天就好好聊聊。

一寞酿、做個實驗

先做個實驗家夺,在應(yīng)用啟動過程中,主要做了兩步:

  • 主線程循環(huán) 10w 次伐弹,做一些簡單的計算
  • 線程池做一些異步任務(wù)拉馋,讀取文件,然后將讀取到的數(shù)據(jù)寫入數(shù)據(jù)庫惨好,這個異步任務(wù)提交了 1000 次

核心線程數(shù) = 2 * CPU 核心數(shù) + 1煌茴,變量最大線程數(shù):

  1. 實驗一:最大線程數(shù) = 2 * CPU 核心數(shù) + 1
  2. 實驗二:最大線程數(shù) = Int.MAX_VALUE

在模擬器上,實驗二平均啟動時長 6505 ms日川,實驗一平均啟動市場 5521 ms蔓腐,從這點看,線程開太多對主線程是有影響的龄句。

二回论、基礎(chǔ)知識

在啟動流程中基礎(chǔ)知識必不可少,從上往下講就是線程撒璧、線程池透葛、內(nèi)核 和 CPU笨使,這些知識都是老生常談了卿樱。

1. 線程

線程是操作系統(tǒng)進行運算調(diào)度的最小單位,可以理解為它就是系統(tǒng)執(zhí)行的任務(wù)硫椰。

作為任務(wù)繁调,它會有各種狀態(tài):

  1. NEW(新建):新創(chuàng)建的線程,還沒有啟動
  2. RUNNABLE(可運行):可以運行的線程
  3. BLOCKED(阻塞):阻塞狀態(tài)的線程
  4. WAITTING(等待):等待狀態(tài)
  5. TIME_WAITTING(計時等待)
  6. TERMINATED(終止)

各種狀態(tài)可以進行如下轉(zhuǎn)換:

MainGroup使用流程

處于可運行狀態(tài)的線程不一定處于運行中靶草,如果 CPU 核心數(shù) < 線程數(shù)量蹄胰,在某個時間點,處于運行中的線程數(shù)量最多也只能等于 CPU 核心數(shù)奕翔。

除此以外裕寨,只有處于可運行狀態(tài)的線程才有機會獲取 CPU 的青睞,從而分到時間片,得以執(zhí)行宾袜。

2. 線程池

線程池的知識都很熟悉了捻艳,簡單了解一下。

2.1 核心線程

簡單來說庆猫,我們想了解的部分就是線程池的核心線程和非核心線程:

  • 核心線程:核心線程會一直存在
  • 非核心線程:當(dāng)非核心線程閑置超過指定的時間认轨,就會被銷毀

通過配置合適的核心線程數(shù)和非核心線程數(shù)可以幫助我們管理好線程,可以帶來以下好處:

  1. 降低資源消耗:重復(fù)利用線程月培,降低資源消耗
  2. 提供響應(yīng)速度:任務(wù)一來就執(zhí)行
  3. 管理好線程資源:避免無節(jié)制的使用線程嘁字,引發(fā)性能問題

除此以外,在配置核心線程數(shù)和非核心線程數(shù)的時候杉畜,還需要根據(jù)業(yè)務(wù)場景纪蜒,將 CPU 密集型和 I/O 密集型任務(wù)考慮進去。

2.2 任務(wù)劃分

我們經(jīng)常將任務(wù)分為 I/O密集型 和 CPU密集型 任務(wù)此叠,那么這兩種有什么區(qū)別呢霍掺?

I/O 密集型任務(wù)指的是該任務(wù)的大部分時間用來提交 I/O 請求或者等待 I/O 請求。這類任務(wù)常常運行很短暫的一會兒拌蜘,然后進入阻塞狀態(tài)杆烁,等待更多的 I/O 請求。常見的如數(shù)據(jù)庫操作简卧、網(wǎng)絡(luò)操作兔魂、鍵盤事件、屏幕操作等举娩。

CPU 密集型任務(wù)指的是任務(wù)的大部分代碼用來執(zhí)行代碼析校。該類任務(wù)常常會一直運行并占用著 CPU,直到時間片用完铜涉。常見的如數(shù)據(jù)計算智玻、無限循環(huán)等。

那線程數(shù)如何設(shè)置芙代?我們下面再去講吊奢。

3. 內(nèi)核

哪個線程先運行?什么時間運行纹烹?運行多久页滚?這些都是調(diào)度程序說了算!

3.1 調(diào)度程序

調(diào)度程序是一個內(nèi)核子系統(tǒng)铺呵,它是多任務(wù)操作系統(tǒng)的基礎(chǔ)裹驰。多任務(wù)操作系統(tǒng)就是能夠同時并發(fā)地交互執(zhí)行多個進程的操作系統(tǒng)。

即使是單核處理器片挂,它也可以并發(fā)的處理多個任務(wù)幻林,只不過在一個時間點贞盯,只有一個正在執(zhí)行的任務(wù)。

就好比安卓開發(fā)小王沪饺,身背幾個需求邻悬,被產(chǎn)品要求同一天上線,雖然也能夠完成随闽,但他在某個時間點父丰,只能寫一個需求,如果想一個時間點同時進行兩個需求掘宪,那得加人蛾扇,也就是我們通常說的雙核處理器,這就具備了并行的能力魏滚。

并行和并發(fā)

3.2 搶占式和非搶占式

多任務(wù)操作系統(tǒng)可以分為兩種類型:非搶占式多任務(wù)和搶占式多任務(wù)镀首。

Android 使用的是搶占式多任務(wù),在這種模式下鼠次,每個任務(wù)都會被分配到一定的時間用來執(zhí)行更哄,一旦時間片用完,就會自動切換到下一個任務(wù)腥寇,分配的時間我們稱之為時間片成翩。

還拿小王來舉例,小王身背三個需求赦役,每天的計劃中麻敌,上午需求 A,下午需求 B掂摔,晚上需求C术羔。到了下午,即使需求 A 沒做完乙漓,也要去做需求 B级历,這樣可以保證了每個需求每天都會有進度。

從啟動的角度來說叭披,我們肯定不希望主線程和子線程分得同樣的時間片寥殖,這可能會讓我們的應(yīng)用看著很慢。

為了給主線程分得更長的時間片趋观,每個進程都有一個 nice 值扛禽,它會影響時間片的分配锋边,但我們改不了這個皱坛,我們能夠處理的就是給線程設(shè)置優(yōu)先級,Android 中線程的優(yōu)先級從 -19 到 19豆巨,值越低代表優(yōu)先級越高剩辟,分得的時間片也就越長。

3.3 線程多了會怎么分配

上面的這些東西看似和我們應(yīng)用層開發(fā)沒關(guān)系,實則不然贩猎。

比如線程數(shù)量多了以后熊户,我們先拿小王舉例:

原先小王手里有 5 個需求,每個 2 天工時吭服,做完一個再做下一個嚷堡,10天能搞定。

現(xiàn)經(jīng)理要求他同時開發(fā) 5 個需求艇棕,保證 5 個需求每天都有進度蝌戒,那可就麻煩了,先不算 10 天開發(fā)時間沼琉,還得加上如下時間:

  • 每天切其他四個項目時間成本
  • 思考時間:每次切到下一個項目北苟,都會想上次開發(fā)到哪,上次的思路是什么

加上這些亂七八槽的打瘪,原來 10 天能搞定的東西友鼻,現(xiàn)在得變成 12 天。

線程多了闺骚,也會有這樣的問題彩扔,每次切換時間片都是成本。另外僻爽,線程的閑置率會上升借杰,像這樣運行 14ms 要等 185 ms:

idle率

還拿小王來看,原先五個需求进泼,桉順序做蔗衡,每個需求的生命周期就 2 天,但是并行開發(fā)后乳绕,每個需求的生命周期都拉長了绞惦,到了 12 天左右。對于啟動的主線程來講可不是好事洋措!

理想的情況應(yīng)該是量力而行济蝉,當(dāng)小王開發(fā)一個需求遇到問題需要等產(chǎn)品回復(fù)而停滯,在等待的這段時間內(nèi)菠发,開發(fā)另外一個需求王滤,知道產(chǎn)品回復(fù)完,再找一個合適的時間切回來滓鸠,這樣雁乡,反而會提升效率,將工作時間縮短到 9 天糜俗。

4. CPU

在2022年發(fā)布的 Android 低端機上踱稍,也都標(biāo)配了 8 核心的 CPU曲饱,核心數(shù)越多,就意味著并行能力越強珠月。

注意扩淀,這里用的是并行,而不是并發(fā)啤挎。

專業(yè)團隊

一個核心驻谆,就代表著團隊只有一個開發(fā),8 核代表著團隊有八個開發(fā)庆聘,意味著一個時間點最高可以有8個需求同時進行旺韭。

二、線程數(shù)如何設(shè)置

上面說了那么多掏觉,大家最想知道的就是線程數(shù)如何設(shè)置区端。

一般而言,核心線程數(shù)和最大線程數(shù)都設(shè)置為 CPU核心數(shù) * 2 +1 澳腹,阻塞隊列使用 LinkedBlockingDeque织盼。

1. 任務(wù)因素

但這個數(shù)字肯定不是絕對的,我們需要考慮到 CPU 密集型任務(wù) 和 IO 密集型任務(wù)的區(qū)別酱塔。

如果我們使用子線程都是處理網(wǎng)絡(luò)沥邻、數(shù)據(jù)庫、讀文件等操作羊娃,這個數(shù)字就可以設(shè)置大一點唐全;如果子線程僅執(zhí)行一些耗時的計算代碼,這個數(shù)字就可以設(shè)置小一點蕊玷。

2. 任務(wù)閑置

即使我們自己設(shè)置的線程池沒什么問題邮利,但程序一啟動,任務(wù)執(zhí)行時候的線程閑置率一看就知道還有問題垃帅,比如這張圖:

idle率

為什么會出現(xiàn)這種閑置率太高的情況延届,原因可能如下:

  1. 過多使用 New Thread 或者不節(jié)制的使用線程池
  2. 很多第三方 SDK 都使用自身的線程池或者線程

查看閑置率有兩種,分別是使用Android Studio中的Profiler和Shell命令贸诚。

推薦大家使用 Profiler方庭,好處可太多了:

  • 可以查看線程總數(shù)
  • 可以查看CPU的負(fù)載情況
  • 可以查看每個任務(wù)的閑置率
  • ...

直接使用 Profiler 中的 System TraceView 只能查看系統(tǒng)級別的方法,如果是我們想查看的方法酱固,需要這么處理:

public void test{
    Trace.beginSection("名稱");
    //... 代碼省略
    Trace.endSection();
}

對每個方法做上述過程確實太麻煩械念,所以都是配合函數(shù)插樁使用。

另外一個就是使用 Shell 命令运悲,我們可以在 Android Studio 中 Logcat 窗口看到應(yīng)用的進程 Id龄减,進入 adb shell 后,就可以通過輸入命令 cat /proc/{進程ID}/schedstat 查看:

emulator64_x86_64_arm64:/ $ cat /proc/7775/schedstat
5511910111 2055599424 6712
// 參數(shù)一 CPU運行時間
// 參數(shù)二 該進程等待時間
// 參數(shù)三 主動切換和被動切換的次數(shù)

這些數(shù)據(jù)只能夠我們查看大概的情況扇苞。

總結(jié)

關(guān)于線程我們能做的并不多欺殿,盡量去收斂線程:

  1. 禁止使用 New Thread 方式去創(chuàng)建線程
  2. 統(tǒng)一應(yīng)用內(nèi)線程池寄纵,并制定合適的核心線程和最大線程數(shù)量
  3. 編寫公司庫的時候鳖敷,如需使用線程池脖苏,提供設(shè)置線程池的接口
  4. 可以設(shè)置自身線程池的第三方庫,優(yōu)先設(shè)置應(yīng)用內(nèi)線程池定踱,比如 OkHttp
  5. Hook 第三方庫使用 New Thread棍潘,改為應(yīng)用內(nèi)線程池
  6. 能懶加載盡量懶加載第三方庫,避免過早的競爭系統(tǒng)資源

主要就這些崖媚,如有不對的地方亦歉,評論區(qū)見~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市畅哑,隨后出現(xiàn)的幾起案子肴楷,更是在濱河造成了極大的恐慌,老刑警劉巖荠呐,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赛蔫,死亡現(xiàn)場離奇詭異,居然都是意外死亡泥张,警方通過查閱死者的電腦和手機呵恢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來媚创,“玉大人渗钉,你說我怎么就攤上這事〕疲” “怎么了鳄橘?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長芒炼。 經(jīng)常有香客問我挥唠,道長,這世上最難降的妖魔是什么焕议? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任宝磨,我火速辦了婚禮,結(jié)果婚禮上盅安,老公的妹妹穿的比我還像新娘唤锉。我一直安慰自己,他們只是感情好别瞭,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布窿祥。 她就那樣靜靜地躺著,像睡著了一般蝙寨。 火紅的嫁衣襯著肌膚如雪晒衩。 梳的紋絲不亂的頭發(fā)上嗤瞎,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機與錄音听系,去河邊找鬼贝奇。 笑死,一個胖子當(dāng)著我的面吹牛靠胜,可吹牛的內(nèi)容都是我干的掉瞳。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼浪漠,長吁一口氣:“原來是場噩夢啊……” “哼陕习!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起址愿,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤该镣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后响谓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體损合,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年歌粥,在試婚紗的時候發(fā)現(xiàn)自己被綠了塌忽。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡失驶,死狀恐怖土居,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嬉探,我是刑警寧澤擦耀,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站涩堤,受9級特大地震影響眷蜓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜胎围,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一吁系、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧白魂,春花似錦汽纤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春背传,著一層夾襖步出監(jiān)牢的瞬間呆瞻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工径玖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留痴脾,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓挺狰,卻偏偏與公主長得像明郭,于是被迫代替她去往敵國和親买窟。 傳聞我的和親對象是個殘疾皇子丰泊,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容