# 模組23 控制執行緒與優先安排(join()、yeild()、setPriority()) ## 先寫在前面 執行緒的切換由系統和 CPU 控制,很捉摸不定。 即使我們在這章節中,將學到如何幫執行緒安排優先權,也未必會按照我們所想的順序執行。 比較能確切掌控的,是讓正在執行的執行緒進入等待或睡眠,從 Running 狀態切換到 Runnable 狀態,然而在排程中等待 CPU 喚醒時,什麼時候能執行到也不一定。 ## 控制執行緒的常用方法 ### join() 讓我們可以控制 2 個執行緒之間的「先後」關係。 * `join()`:正在執行的執行緒,所呼叫的執行緒終止後,自己才會繼續執行。 * `join(long milliseconds)`:毫秒數一到,就不會再等待對方了,開始參與競爭執行的行列。 :::warning 在 a 執行緒中呼叫 `b.join()` ,是 a 會等待 b 結束後再繼續執行,可能會混淆,要多注意。 ::: ### setPriority() 為多個執行緒作流程安排(Scheduling),決定順序,盡可能確保重要或急迫的執行緒可以被立即或經常執行。 <font color=red>**原則上**</font>優先權較高者先執行,有時會有例外,系統為了避免優先權較低者餓死(Starvation)的情況,會讓其先執行;而**優先權相等時,是<font color=red>任選其一執行**</font>。 設定優先權,只是為了讓我們的排程更有效率,不能完全仰賴優先權之間的關係控制程式進行的順序。 * `setPriority(int newPriority)`:其中可以傳入的參數如下: * 1~10 內的整數 * 最小值 = `1` * 預設值 = `5` * 最大值 = `10` * 常數:代表數值,程式碼更有閱讀性。 * `Thread.MIN_PRIORITY`:相當於最小值 `1`。 * `Thread.NORM_PRIORITY`:相當於預設值 `5`。 * `Thread.MAX_PRIORITY`:相當於最大值 `10`。 ### yield() 改善 [Selifish thread](#自私的執行緒(Selfish-thread)) 造成的狀況,正在執行的執行緒自願將自己移出 Running 狀態,回到 Ready 狀態等待。 但僅對**優先權相等**的數個執行緒有效。 ## 多執行緒程式特性 總結就是一句話:==難以預測其行為==,並且[自私](#自私的執行緒(Selfish-thread))。 ### 執行緒面 1. 無法完全保證順序。 2. 工作切換(Task Switches)可能在任何時刻、任何位置發生。 3. 對於小改變有高度敏感性。 4. 並不總是立刻啟動執行(需被排程進 Ready 狀態)。 ### 系統面 * **在具有 [Time-slicing](#時間切割(Time-slicing)) 特性的 OS 中**: 1. 優先權相同的執行緒,會以**幾乎相同機會**的循環方式取得時間槽並執行。 2. 優先權較低的執行緒也有機會取得。 3. 比例大約正比於優先權值,所以在長時間執行中不會有執行緒完全都沒有被顧及到的狀況 * **非 Time-slicing 的 OS 中**: 易發生完全獨佔。 不論是執行緒上或系統上,為了解決獨佔的問題,對於有大量運算的執行緒,可以適度呼叫 `yeild()`,讓其他執行緒有執行的機會(尤其可增加使用者介面[UI]的良好互動)。 ## 其他概念補充 ### 時間切割(Time-slicing) 例如:windows 系統將CPU分成一段段的時間槽(time slot),分給優先權最高且相等的數個執行緒,直到它們執行完畢,或執行權被更高優先權的執行緒搶走。 但具有 Time-slicing 特性的 OS,也無法保證 time slot 能平均分配或執行緒執行的優先順序。 ### 自私的執行緒(Selfish thread) 實踐了 socially-impaired 的特性: * **以執行緒而言:** 擁有密實迴圈(tight loop,指迴圈的程式碼不多,很快就進入下一次執行),將一直獨佔 CPU 執行權。 * **以系統而言:** 若該系統不支援 Time-slicing,易完全獨佔,直到該迴圈執行完畢,或被更高優先權的其他執行緒搶走執行權。