盡管幾十年來(lái)小型化的進(jìn)步已經(jīng)為單處理器帶來(lái)了巨大的性能提升,但這個(gè)時(shí)代似乎即將結(jié)束。使用單芯片實(shí)現(xiàn)顯著額外性能的最佳選擇是通過(guò)多個(gè)內(nèi)核,但前提是軟件可以編程以利用它們。
不幸的是,并發(fā)編程很困難。即使是只熟悉單線(xiàn)程編程的專(zhuān)家級(jí)程序員也常常無(wú)法理解并發(fā)程序容易受到諸如競(jìng)爭(zhēng)條件、死鎖和饑餓等全新類(lèi)別的缺陷的影響。人類(lèi)很難推理并發(fā)程序,并且編程語(yǔ)言本身的某些方面不適合并發(fā)。因此,專(zhuān)家們經(jīng)常偶然發(fā)現(xiàn)這些危害。以下討論描述了常見(jiàn)的并發(fā)缺陷,并解釋了靜態(tài)分析工具如何在不執(zhí)行程序的情況下發(fā)現(xiàn)此類(lèi)缺陷。
競(jìng)爭(zhēng)條件的后果
當(dāng)多個(gè)執(zhí)行線(xiàn)程訪(fǎng)問(wèn)一個(gè)共享的數(shù)據(jù)并且其中至少一個(gè)線(xiàn)程在沒(méi)有顯式同步操作來(lái)分離訪(fǎng)問(wèn)的情況下更改該數(shù)據(jù)的值時(shí),就會(huì)出現(xiàn)競(jìng)爭(zhēng)條件。根據(jù)兩個(gè)線(xiàn)程的交錯(cuò),系統(tǒng)可能會(huì)處于不一致的狀態(tài)。
種族條件特別陰險(xiǎn),因?yàn)樗鼈兛梢詿o(wú)限期地潛伏而未被發(fā)現(xiàn),并且只在極少數(shù)情況下出現(xiàn),表現(xiàn)出難以診斷和重現(xiàn)的神秘癥狀。特別是,它們很可能通過(guò)對(duì)已部署軟件的測(cè)試而存活下來(lái)。充其量,這意味著增加開(kāi)發(fā)時(shí)間;在最壞的情況下,后果可能是毀滅性的。
2003 年?yáng)|北大停電如此普遍的一個(gè)原因是計(jì)算機(jī)化能源管理系統(tǒng)中的競(jìng)爭(zhēng)條件導(dǎo)致向運(yùn)營(yíng)商傳達(dá)誤導(dǎo)性信息。正如 Kevin Poulsen 在 2004 年的一篇文章 ( www.securityfocus.com/news/8412 ) 中指出的那樣,“該漏洞有一個(gè)以毫秒為單位的機(jī)會(huì)窗口。” 在測(cè)試過(guò)程中出現(xiàn)此類(lèi)問(wèn)題的可能性微乎其微。在另一種情況下,iOS 4.0 到 4.1(現(xiàn)已修復(fù))中的競(jìng)爭(zhēng)條件意味著任何可以物理訪(fǎng)問(wèn) iPhone 3G 或更高版本的人都可以在某些條件下繞過(guò)其密碼鎖定。
圖 1 顯示了一個(gè)簡(jiǎn)單競(jìng)爭(zhēng)條件的示例。帶有入口和出口傳感器的制造裝配線(xiàn)維護(hù)當(dāng)前生產(chǎn)線(xiàn)上的項(xiàng)目的運(yùn)行計(jì)數(shù)。每次項(xiàng)目進(jìn)入行時(shí),此計(jì)數(shù)都會(huì)增加,每次項(xiàng)目到達(dá)行尾并退出時(shí),此計(jì)數(shù)就會(huì)減少。如果一個(gè)項(xiàng)目在另一個(gè)項(xiàng)目退出的同時(shí)進(jìn)入該行,則計(jì)數(shù)應(yīng)該遞增然后遞減(或反之亦然),以使凈變化為零。但是,正常的遞增和遞減不是原子操作;它們由一系列單獨(dú)的指令組成,這些指令首先從內(nèi)存中加載值,然后在本地對(duì)其進(jìn)行修改,最后將其存儲(chǔ)回內(nèi)存中。如果更新事務(wù)是在沒(méi)有足夠保護(hù)措施的多線(xiàn)程系統(tǒng)中處理的,由于傳感器讀取和寫(xiě)入共享數(shù)據(jù):計(jì)數(shù),因此可能會(huì)出現(xiàn)競(jìng)爭(zhēng)條件。圖 1 中的交錯(cuò)導(dǎo)致錯(cuò)誤計(jì)數(shù)為 69。也有可能導(dǎo)致錯(cuò)誤計(jì)數(shù)為 71 的交錯(cuò),以及一些正確導(dǎo)致計(jì)數(shù)為 70 的交錯(cuò)。
圖 1:競(jìng)爭(zhēng)條件導(dǎo)致裝配線(xiàn)上的項(xiàng)目計(jì)數(shù)不正確。
對(duì)于這個(gè)例子和一般的競(jìng)爭(zhēng)條件錯(cuò)誤,標(biāo)準(zhǔn)調(diào)試技術(shù)可能由于幾個(gè)原因而無(wú)效。
很少發(fā)生意味著發(fā)現(xiàn)問(wèn)題的機(jī)會(huì)減少。如果問(wèn)題不經(jīng)常出現(xiàn),它可能永遠(yuǎn)不會(huì)在測(cè)試期間出現(xiàn)。這個(gè)問(wèn)題是雙重的。首先,兩個(gè)線(xiàn)程中可能的指令交錯(cuò)數(shù)量可能很大,并且隨著指令數(shù)量的增加而急劇增加。這種現(xiàn)象被稱(chēng)為組合爆炸。如果線(xiàn)程 A 執(zhí)行M條指令,線(xiàn)程 B 執(zhí)行N條指令,則兩個(gè)線(xiàn)程的可能交錯(cuò)為:
等式 1
例如,給定兩個(gè)普通線(xiàn)程,每個(gè)線(xiàn)程有 10 條指令,則指令有 184,756 種可能的交錯(cuò)。現(xiàn)實(shí)世界的軟件龐大而復(fù)雜;測(cè)試每一個(gè)交織是根本不可能的。其次,即使測(cè)試人員可以識(shí)別出一些值得檢查的交錯(cuò),也很難設(shè)置測(cè)試用例來(lái)確保它們確實(shí)發(fā)生,因?yàn)榫€(xiàn)程調(diào)度可能是高度不確定的。
如果詳盡的測(cè)試難以解決,那么開(kāi)發(fā)人員可以做什么?一種非常有用的方法是靜態(tài)分析。CodeSonar 等高級(jí)靜態(tài)分析工具使用高度復(fù)雜的符號(hào)執(zhí)行技術(shù)同時(shí)考慮許多可能的執(zhí)行路徑和交錯(cuò)。這些技術(shù)可以在不需要執(zhí)行程序的情況下找到競(jìng)爭(zhēng)條件和其他并發(fā)錯(cuò)誤。
有幾個(gè)因素使比賽狀況診斷變得困難。首先,癥狀可能令人困惑。在圖 1 示例中,運(yùn)行計(jì)數(shù)通常是正確的,但有時(shí)太高,有時(shí)太低。其次,不習(xí)慣考慮多線(xiàn)程編程的特定缺陷的程序員可能會(huì)在可能出現(xiàn)競(jìng)爭(zhēng)條件之前花費(fèi)大量時(shí)間對(duì)代碼感到困惑。高級(jí)靜態(tài)分析工具在這方面特別有用。他們通過(guò)檢查共享內(nèi)存位置的訪(fǎng)問(wèn)模式來(lái)識(shí)別競(jìng)爭(zhēng)條件;也就是說(shuō),他們關(guān)注的是種族本身,而不是它的癥狀。當(dāng)識(shí)別出競(jìng)爭(zhēng)條件時(shí),高級(jí)靜態(tài)分析工具將報(bào)告它以及支持信息,以幫助用戶(hù)進(jìn)行評(píng)估和調(diào)試。程序員的負(fù)擔(dān)大大減輕。
更復(fù)雜,更多錯(cuò)誤
競(jìng)爭(zhēng)條件通常通過(guò)使用鎖來(lái)保護(hù)共享資源來(lái)避免。但是,鎖可能會(huì)引入性能瓶頸,可能會(huì)阻止程序充分利用多核的潛力,因此程序員在使用它們時(shí)必須小心謹(jǐn)慎。編寫(xiě)有效使用鎖的代碼可能很棘手,這種復(fù)雜性可能導(dǎo)致一組不同的問(wèn)題,即死鎖和饑餓。
在死鎖中,兩個(gè)或多個(gè)線(xiàn)程相互阻止,因?yàn)槊總€(gè)線(xiàn)程都持有另一個(gè)線(xiàn)程需要的鎖。圖 2 顯示了如何使用用于保護(hù)兩個(gè)共享變量的兩個(gè)鎖出現(xiàn)死鎖。在此示例中,多條裝配線(xiàn)共享當(dāng)前正在裝配的項(xiàng)目總數(shù),第二個(gè) bad_items 值記錄有多少成品未通過(guò)質(zhì)量控制。一個(gè)線(xiàn)程在 count 上獲得鎖,另一個(gè)在 bad_items 上獲得鎖。兩個(gè)線(xiàn)程都無(wú)法獲得它需要的第二個(gè)鎖;因此既不能執(zhí)行它的操作,也不能到達(dá)釋放鎖的地步。由于兩個(gè)更新都無(wú)法完成,因此兩個(gè)線(xiàn)程都完全卡住了。
圖 2:在兩個(gè)線(xiàn)程之間的死鎖中,兩個(gè)線(xiàn)程都無(wú)法前進(jìn)。
靜態(tài)分析工具可以通過(guò)標(biāo)記不同線(xiàn)程可以以不同順序獲取相同鎖的情況來(lái)識(shí)別存在死鎖風(fēng)險(xiǎn)的軟件,例如圖 2 中所示的線(xiàn)程。消除所有此類(lèi)情況足以確保系統(tǒng)不會(huì)陷入死鎖。
饑餓是使用鎖的多線(xiàn)程程序中發(fā)生的另一個(gè)問(wèn)題。如果一個(gè)線(xiàn)程正在等待另一個(gè)線(xiàn)程當(dāng)前持有的資源需要很長(zhǎng)時(shí)間,它可能會(huì)餓死。例如,假設(shè)上述制造自動(dòng)化系統(tǒng)包括一個(gè)定期審核線(xiàn)程,該線(xiàn)程檢查所有進(jìn)入和退出記錄,以確保運(yùn)行計(jì)數(shù)與進(jìn)入的總項(xiàng)目數(shù)相匹配,而不是退出的總項(xiàng)目數(shù)。審計(jì)線(xiàn)程需要鎖定計(jì)數(shù)和所有傳感器,因此所有更新都必須等待審計(jì)完成。如果審核運(yùn)行很長(zhǎng)時(shí)間,更新可能會(huì)顯著延遲。如果運(yùn)行時(shí)間過(guò)長(zhǎng),下一次審計(jì)可能會(huì)設(shè)法獲取所有鎖并在未完成的線(xiàn)程取得任何進(jìn)展之前開(kāi)始運(yùn)行。在最壞的情況下,部分或全部更新可能永遠(yuǎn)沒(méi)有機(jī)會(huì)運(yùn)行。
靜態(tài)分析可以通過(guò)提出諸如“在持有鎖時(shí)是否調(diào)用長(zhǎng)時(shí)間運(yùn)行的庫(kù)函數(shù)?”之類(lèi)的問(wèn)題來(lái)提供重要的價(jià)值。CodeSonar 等工具還為用戶(hù)提供了添加自己檢查的機(jī)制。如果已知內(nèi)部函數(shù) f() 具有較長(zhǎng)的運(yùn)行時(shí)間,工程師可以添加自定義檢查,每當(dāng)持有一個(gè)或多個(gè)鎖的線(xiàn)程調(diào)用 f() 時(shí)觸發(fā)警告。
多線(xiàn)程為嵌入式開(kāi)發(fā)人員必須考慮的潛在錯(cuò)誤添加了全新的類(lèi)別,使得查找各種錯(cuò)誤變得更加困難。最新一代的靜態(tài)分析工具可以幫助解決這兩個(gè)問(wèn)題。
審核編輯:郭婷
-
傳感器
+關(guān)注
關(guān)注
2552文章
51383瀏覽量
756461 -
嵌入式
+關(guān)注
關(guān)注
5092文章
19176瀏覽量
307593 -
計(jì)算機(jī)
+關(guān)注
關(guān)注
19文章
7534瀏覽量
88621
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論