任務排序是由宣告任務的輸入來指定。正確執行需要正確的輸入宣告。例如,以下兩個任務沒有指定排序
write := IO.write(file("/tmp/sample.txt"), "Some content.")
read := IO.read(file("/tmp/sample.txt"))
sbt 可以自由地先執行 write
然後執行 read
、先執行 read
然後執行 write
,或同時執行 read
和 write
。這些任務的執行是不確定的,因為它們共用一個檔案。正確的任務宣告應為
write := {
val f = file("/tmp/sample.txt")
IO.write(f, "Some content.")
f
}
read := IO.read(write.value)
這建立了排序:read
必須在 write
之後執行。我們也保證了 read
將從 write
建立的同一個檔案讀取。
注意:本節描述的功能為實驗性功能。此功能的預設組態尤其可能變更。
宣告任務的輸入與相依性可確保任務正確排序,且程式碼正確執行。實際上,任務共用有限的硬體和軟體資源,且可能需要控制這些資源的利用率。預設情況下,sbt 會平行執行任務(受限於已描述的排序限制),以盡可能利用所有可用的處理器。此外,預設情況下,每個測試類別都會對應到自己的任務,以便平行執行測試。
在 sbt 0.12 之前,使用者對此程序的控制僅限於
(雖然從未公開為設定,但在內部也可設定一次執行的最大任務數。)
上述第二種組態機制僅選擇將專案的所有測試在同一個任務中執行或在不同的任務中執行。每個專案仍然有一個單獨的任務來執行其測試,因此如果整體執行是平行的,則不同專案中的測試任務仍然可以平行執行。沒有任何方法可以限制執行,讓所有專案中只執行單一測試。
sbt 0.12.0 引入了一個通用基礎結構,用於限制任務並行性,超越了通常的排序宣告。這些限制有兩個部分。
因此,該系統依賴於任務的正確標記,然後依賴於一組良好的規則。
一般而言,標記與權重相關聯,權重表示任務對標記所代表資源的相對利用率。目前,此權重是整數,但未來可能是浮點數。Initialize[Task[T]]
定義了兩個用於標記建構任務的方法:tag
和 tagw
。第一個方法 tag
將權重固定為 1,用於作為參數提供給它的標記。第二個方法 tagw
接受標記和權重的配對。例如,以下範例將 CPU
和 Compile
標記與 compile
任務關聯(權重為 1)。
def myCompileTask = Def.task { ... } tag(Tags.CPU, Tags.Compile)
compile := myCompileTask.value
可以透過將標記/權重配對傳遞給 tagw
來指定不同的權重
def downloadImpl = Def.task { ... } tagw(Tags.Network -> 3)
download := downloadImpl.value
標記任務後,concurrentRestrictions
設定會根據這些任務的加權標記設定對可同時執行的任務的限制。這必然是一組全域規則,因此必須將其範圍設為 Global /
。例如,
Global / concurrentRestrictions := Seq(
Tags.limit(Tags.CPU, 2),
Tags.limit(Tags.Network, 10),
Tags.limit(Tags.Test, 1),
Tags.limitAll( 15 )
)
此範例限制
請注意,這些限制依賴於任務的正確標記。此外,作為限制提供的值必須至少為 1,以確保每個任務都能夠執行。如果未滿足此條件,sbt 將產生錯誤。
大多數任務不會被標記,因為它們是生命週期非常短的任務。這些任務會自動被指派標籤 Untagged
。您可能希望使用 limitSum
方法將這些任務包含在 CPU 規則中。例如
...
Tags.limitSum(2, Tags.CPU, Tags.Untagged)
...
請注意,限制是第一個參數,以便可以將標記作為 varargs 提供。
另一個有用的便利函式是 Tags.exclusive
。這指定具有給定標記的任務應隔離執行。它僅在沒有其他任務正在執行時開始執行(即使它們具有獨佔標記),並且在它完成之前,沒有其他任務可以開始執行。例如,可以使用自訂標記 Benchmark
來標記任務,並設定規則以確保此類任務自行執行
...
Tags.exclusive(Benchmark)
...
最後,為了獲得最大的彈性,您可以指定 Map[Tag,Int] => Boolean
類型的自訂函式。Map[Tag,Int]
代表一組任務的加權標記。如果函式傳回 true
,則表示允許該組任務同時執行。如果傳回值為 false
,則不允許該組任務同時執行。例如,Tags.exclusive(Benchmark)
等效於以下內容
...
Tags.customLimit { (tags: Map[Tag,Int]) =>
val exclusive = tags.getOrElse(Benchmark, 0)
// the total number of tasks in the group
val all = tags.getOrElse(Tags.All, 0)
// if there are no exclusive tasks in this group, this rule adds no restrictions
exclusive == 0 ||
// If there is only one task, allow it to execute.
all == 1
}
...
自訂函式必須遵循一些基本規則,但實際上要注意的主要規則是,如果只有一個任務,則必須允許執行。如果使用者定義的限制會阻止任務完全執行,則 sbt 將產生警告,然後無論如何都會執行該任務。
內建標記在 Tags
物件中定義。以下列出的所有標記都必須由該物件限定。例如,CPU
指的是 Tags.CPU
值。
內建語意標記為
Compile
- 描述編譯來源的任務。Test
- 描述執行測試的任務。Publish
Update
Untagged
- 當任務未明確定義任何標記時,會自動新增。All
- 自動新增至每個任務。內建資源標記為
Network
- 描述任務的網路利用率。Disk
- 描述任務的檔案系統利用率。CPU
- 描述任務的計算利用率。目前預設標記的任務為
compile
:Compile
,CPU
test
:Test
update
:Update
,Network
publish
,publishLocal
:Publish
,Network
其他值得注意的是,預設的 test
任務會將其標記傳播到為每個測試類別建立的每個子任務。
預設規則與先前版本的 sbt 提供相同的行為
Global / concurrentRestrictions := {
val max = Runtime.getRuntime.availableProcessors
Tags.limitAll(if(parallelExecution.value) max else 1) :: Nil
}
與之前一樣,Test / parallelExecution
控制是否將測試對應到不同的任務。若要限制所有專案中同時執行的測試數量,請使用
Global / concurrentRestrictions += Tags.limit(Tags.Test, 1)
若要定義新的標記,請將字串傳遞至 Tags.Tag
方法。例如
val Custom = Tags.Tag("custom")
然後,像使用其他標籤一樣使用此標籤。例如:
def aImpl = Def.task { ... } tag(Custom)
aCustomTask := aImpl.value
Global / concurrentRestrictions +=
Tags.limit(Custom, 1)
這是一項實驗性功能,在某些方面可能會有所變動或需要進一步開發。
目前,標籤僅適用於定義它的直接計算。例如,在以下程式碼中,第二個編譯定義沒有套用任何標籤。只有第一個計算被標記。
def myCompileTask = Def.task { ... } tag(Tags.CPU, Tags.Compile)
compile := myCompileTask.value
compile := {
val result = compile.value
... do some post processing ...
}
這是理想的嗎?符合預期的嗎?如果不是,更好的替代行為是什麼?
權重目前是 int
,但如果小數權重很有用,則可以更改為 double
。重要的是要保留權重為 1 的一致概念,以便內建和自訂任務共享此定義,並且可以編寫有用的規則。
使用者對於自訂規則如何適用於哪些工作負載的回饋,將有助於確定一組良好的預設標籤和規則。
應該更容易移除或重新定義規則,或許可以為它們命名。目前,規則必須附加或完全重新定義所有規則。此外,在使用 :=
語法時,標籤只能為原始定義位置的任務定義。
對於移除標籤,removeTag
的實作應以直接的方式遵循 tag
的實作。
選擇具有權重的標籤系統是因為它具有相當強大且靈活的特性,且不會過於複雜。此選擇並非根本性,如有必要,可以增強、簡化或取代。描述系統必須在其中運作的約束的基本介面是 sbt.ConcurrentRestrictions
。此介面用於在任務執行 (sbt.Execute
) 和底層基於執行緒的平行執行服務 (java.util.concurrent.CompletionService
) 之間提供一個中間排程佇列。此中間佇列會根據 sbt.ConcurrentRestrictions
實作,限制將新任務轉發至 j.u.c.CompletionService
。有關詳細資訊,請參閱 sbt.ConcurrentRestrictions API 文件。