make 多線程編譯會出錯么?
在編譯Qt的時候,參考帖都建議單線程編譯,避免出錯。
那麼make多線程編譯在什麼情況下會出錯?為什麼呢?
正確的 Makefile 不會,但寫錯 Makefile 就難說了。make 讀入Makefile後,會建立起依賴圖。然後根據依賴圖執行。
舉個簡單的例子吧。
.PHONY: all
all: prea preb
@echo done.
prea:
@echo a
@sleep 1
@echo aa
@sleep 1
@echo aaa
preb:
@echo b
@sleep 1
@echo bb
@sleep 1
@echo bbb
上面的例子,如果 make 單線程執行,那麼當然是串列來的。
a
aa
aaa
b
bb
bbb
done.
請 make -j2 看看結果如何?
a
b
aa
bb
aaa
bbb
done.
好,如果添加一行依賴呢?如下(prea 依賴 preb):
.PHONY: all
all: prea preb
@echo done.
prea: preb
prea:
@echo a
@sleep 1
@echo aa
@sleep 1
@echo aaa
preb:
@echo b
@sleep 1
@echo bb
@sleep 1
@echo bbb
然後你再 make -j2, 會發現結果仍然是串列的。
也就是說,make 並不是亂添多線程的。它只在依賴圖的節點那,看到並行的可能的話,它就會根據根據 -j 參數情況進行並行。每個單獨的目標(或者說最終的葉節點規則),其下的命令仍然是串列的,按上面的例子也就是說,絕對不會出現 echo aa 比 echo a 先執行的情況。
根據上面的例子,如果寫 Makefile 時允許 prea 和 preb 兩個目標(當然例子中它們也是 all 的依賴)並行的話,也就意味著 prea 和 preb 下的規則最好不要出現訪問共享資源的情況,也就是可能會引發競態的命令。不然多線程 make 確實是可能出錯的。比如說,在 prea 規則裡面去刪一個文件,preb 規則裡面去 touch 同一個文件,那麼執行結果確實會與多線程執行順序相關了。
當然了。一般來說,一般人很少會去動已經寫好的 Makefile(當然不是說寫驅動什麼的加個 obj-y 什麼的這種大家都會做的事情,我意思是真正可能會動到構建系統邏輯本身的), 正常來說人家的 Makefile 也都會是寫對的(也就是說多線程執行不會有什麼問題的)。類似 Qt 這種庫,我想他們的構建系統本身應該沒有問題,畢竟手寫 Makefile 構建系統的多是對工具鏈以及相關構建範疇相當熟悉的高手。
請貼出具體錯誤信息。
Qt官方CI都是多線程編譯的。很久以前有過一個神奇的Makefile,只要並行編譯就會少產出結果,所有的lib都編譯出來了,唯獨沒有鏈接出最後的程序,居然還給我返回成功了=_=
給一個幾千行的Makefile做debug太困難了,直接給代碼的負責人打了個電話,然後在自己的編譯依賴裡面寫了一個-j1,懶人就是這樣的。建議你單線程編譯的估計也是這個思路,如果是社區同好還是感謝一下人家的經驗,如果是owner……一定要留神他的其他項目
現在是21點,從17點開始,已經有了很多回答,但是nmake還沒有結束。。。
x220i i3-2310m 8G 256G SSD
看來要換本本了。。。
make -jN //準確的說應該是多任務並行化
出錯,只有一個原因,makefile 寫錯了。
一般表現出兩種情況。
一種是 依賴缺失 / 缺少中間結果
.PHONY: all
all: t1 t2
@echo $@ done.
t2:
cp t1 $@
t1:
sleep 1
touch $@
直接 make 會成功,順序執行 t1 -&> t2 -&> all。但是 make -j4 會失敗。這是因為在 makefile 中 t2 並不依賴於 t1,所以 t1 / t2 並行執行,但 t2 實際需要 t1 的結果。
把 makefile 修改為
.PHONY: all
all: t1 t2
@echo $@ done.
t2: t1
cp t1 $@
t1:
sleep 1
touch $@
依賴正確聲明的情況下,就對了。
另一種 不同目標交錯執行 / 臨界資源訪問問題
TEMP_FILE := tmp_file
.PHONY: all
all: t1 t2
cat t1 t2 &> $@
t2:
echo test from $@ &> $(TEMP_FILE)
sleep 2
cat $(TEMP_FILE) &> $@
t1:
sleep 1
echo test from $@ &> $(TEMP_FILE)
cat $(TEMP_FILE) &> $@
直接 make。
less all
test from t1
test from t2
make -j4
less all
test from t1
test from t1
就是對 tmp_file 的交叉訪問導致的,本來希望的執行順序是這樣的
sleep 1
echo test from t1 &> tmp_file
cat tmp_file &> t1
echo test from t2 &> tmp_file
sleep 2
cat tmp_file &> t2
cat t1 t2 &> all
結果並發執行,變成了
sleep 1
echo test from t2 &> tmp_file
sleep 2
echo test from t1 &> tmp_file
cat tmp_file &> t1
cat tmp_file &> t2
cat t1 t2 &> all
修改方法可以和之前的一樣,建立一個依賴就能避免 t1 / t2 的並發。也可以修改語句避免共用資源。
綜上,
對任務節點建立正確依賴有向圖,避免對臨時資源的共用,以此來保證 make -j 成功。
不過有時候 makefile 複雜了,在 without parallel 會成功,就懶得改的。所以有些庫建議 without -j 進行構建,既然能用就忍忍,大家都不容易(Sad:
------------ 吐槽分割線 --------------------
make -j 報錯後還要繼續狂刷日誌,最後還是得 make without -j 來定位錯誤,實在太不友好了。
順便說個不靠譜的奇淫技巧,先 make -j 構建,錯了再 make。可以省些中間結果的構建時間,但是如果遇到第二種情況,構建的中間結果錯了,那就再重來吧。(如果錯誤沒被發現就哭了
我個人的經驗是多線程編譯的時候如果一個線程出錯其餘的線程不會立即停下來,而是一直運行到需要的那個文件出錯的時候才停下來,所以有時線程多了最後的編譯報錯信息會不太明顯
首先make -jN不是多線程(Multithreaded)編譯,而是並行化執行任務(Parallel Job Execution)。
然後主要的問題就是大部分的gnu Make腳本都是串列寫的,並行化最多的會造成依賴(Dependency)的缺失。
比如touch a;touch b;touch c;cp c a這三個命令並行處理,很有可能c還沒創建,cp就先執行了。
當然還有其他的錯誤,不過根本原因還是腳本的思路都是串列的,缺少對並行下的設計。編譯使用多線程錯誤的話,我覺得錯誤大多數是模塊之間的依賴關係沒有滿足。
第三方的模塊多的話,我建議使用cmake,把Qt也當做你的project的一部分。
Cmake寫的好的話,是不會出現編譯問題的。我每次都是make -j32寫的好的makefile就不會並行編譯問題是出錯了後會等待其他正在同時進行的編譯完成,會把報錯信息淹沒~
正常情況下,一個健康的Makefile不應該存在依賴問題。事實上,安裝Gentoo的時候會編譯整個系統里的所有軟體,也沒有要求make -j1。make -jN就不應該出錯。
make -jN主要的問題是出現各種原因的錯誤後其他線程會繼續工作,從而埋沒出錯信息。這個問題可以通過各種保存日誌的方法解決。我在本地Linux下用 make -j4 打包qtbase isoft-linux/packages-framework qtdeclarative isoft-linux/packages-framework 在編譯伺服器上開到 make -j40 編譯Qt5的submodule沒有遭遇過多線程編譯出錯
-j 96, 出錯了就可以開始調makefile了, 多爽啊.
誒, 不對, 應該是qmake自動生成的? 那就得調qmake了.
會,畢竟編寫多線程版makefile是個體力活。
好像編譯android源碼時大家用的都是多線程吧,我編過幾個版本的,都沒出現過問題。
單線程編譯順序A-B-C-D,後面的lib依賴前面的編譯結果。多線程的話如果make文件沒有寫好,有可能A編譯完但B還沒有完成的情況下就去編譯C,由於C依賴的B未被編譯,所以編譯就出錯了。
推薦閱讀:
※CLion 鏈接庫?如 lpthread 怎麼設置?
※剛學c++多線程,需要從哪些方面入手,有推薦的教程或書籍嗎?
※多線程引用計數如何釋放?
※C++中如何將函數調用轉發至另一個線程?
※互斥鎖,同步鎖,臨界區,互斥量,信號量,自旋鎖之間聯繫是什麼?