2011-04-03 26 views
1

我記得在我上大學的課程中,我最喜歡的一個競賽條件示例是其中一個簡單的main()方法啓動了兩個線程,其中一個線程增加了一個共享(全局)一個變量,另一個遞減。僞代碼:雙CPU機器上的線程合作

static int i = 10; 

main() { 
    new Thread(thread_run1).start(); 
    new Thread(thread_run2).start(); 
    waitForThreads(); 
    print("The value of i: " + i); 
} 

thread_run1 { 
    i++; 
} 

thread_run2 { 
    i--; 
} 

教授接着問什麼i值一百萬十億數不勝數運行之後。 (如果它基本上是10以外的任何東西)。不熟悉多線程系統的學生回答說,100%的時間,print()聲明總是將i報告爲10.

這實際上是不正確的,因爲我們的教授表明,作爲3條語句的每個增量/減量語句實際上編譯(組裝):

1: move value of 'i' into register x 
2: add 1 to value in register x 
3: move value of register x into 'i' 

因此,i的值可以是9,10或11(I不會進入細節。)

我的問題:

這是(是?)我的理解是物理寄存器集是特定於處理器的。在使用雙CPU機器時(注意雙核和雙CPU之間的區別),每個CPU都有自己的一組物理寄存器嗎? 我以爲答案是肯定的。

在單CPU(多線程)機器上,上下文切換允許每個線程擁有自己的虛擬寄存器集。由於雙CPU機器上有兩套物理寄存器,因此不會因競爭條件而導致更大的潛在可能性,因爲您可以從字面上同時運行兩個線程,而不是單線程上的「虛擬」同時操作, CPU機器? (虛擬同時操作是指參考每個上下文開關保存/恢復寄存器狀態的事實)。

更具體地說 - 如果你是在一個8-CPU機器上運行它,每個帶有一個線程的CPU都是競態的條件消除了?如果將此示例展開爲使用8個線程,則在雙CPU機器上,每個具有4個內核的CPU可能會增加或減少競爭條件? 操作系統如何防止裝配指令的step 3在兩個不同的CPU上同時運行?

回答

1

是的,引入雙核CPU導致大量潛在線程化程序失敗。調度程序通過多任務單核CPU快速切換線程之間的線程上下文。它消除了一類與陳舊的CPU緩存相關的線程錯誤。

儘管如此,您給出的示例也可能會在單個內核上失敗。當線程調度程序中斷線程時,就像它將變量的值加載到寄存器中以增加線程一樣。它不會失敗,因爲調度程序中斷線程的可能性並不大。

有一個操作系統功能,可以讓這些程序無論如何都能夠在幾分鐘內癱瘓而不是崩潰。稱爲'處理器親和性',可作爲Windows上start.exe的AFFINITY命令行選項,winapi中的SetProcessAfinityMask()。查看Interlocked類的助手方法,該方法可以自動遞增和遞減變量。

+0

被選爲多CPU和多核之間差異的答案,以及關於在單核上失敗的註釋。甚至沒有考慮過,這是一個很好的觀點。 – 2011-04-03 23:35:50

1

你仍然有競爭條件 - 它根本沒有改變。設想兩個核心同時執行增量 - 它們都會加載相同的值,遞增到相同的值,然後存儲相同的值...因此,這兩個操作的總增量將是一個而不是兩個。

的潛在問題的其他原因,其中的內存模型而言 - 在步驟1中可能沒有真正檢索i最新值,並在某種程度上步驟3中可能不會立即寫i新值其他線程可以看到。

基本上,這一切都變得非常棘手 - 這就是爲什麼它通常是一個好主意,訪問共享數據使用已被寫入由專家誰真正知道無鎖更高層次的抽象當任使用同步他們在做什麼。

+0

當然 - 除了Java的Collections.synchronizedMap/Set/List/Collection方法之類的更高級別的結構之外,我熟悉同步技術(互斥鎖,信號量,同步塊)。 - 我只是想知道,如果一個寫得不好的程序在雙CPU機器和雙核機器上的功能是不同的。 - 我想我畢竟是漫不經心,我​​真的只是在尋找我最後一個問題的答案,我相信你回答了。 :) – 2011-04-03 17:45:45

1

首先,雙處理器與雙核處理器沒有實際影響。雙核處理器在芯片上仍然有兩個完全獨立的處理器。他們可能共享一些緩存,並共享一條公共總線到內存/外設,但處理器本身是完全獨立的。 (雙線程單代碼,例如超線程)是第三種變體 - 但它也有一套每個虛擬處理器的寄存器。兩個處理器共享一組執行資源,但它們保留完全獨立的寄存器組。

其次,實際上只有兩種情況真的很有趣:一個執行線程,以及其他一切。一旦你有多個線程(即使所有的線程都在單個處理器上運行),你也會遇到同樣的潛在問題,就好像你在一個擁有數千個處理器的大型機器上運行一樣。現在,當代碼在更多的處理器上運行時(儘可能多地創建線程),您很可能會發現問題很快就會顯現出來,但問題本身並沒有/沒有完全改變。

從實踐的角度來看,從測試的角度來看,擁有更多內核是非常有用的。考慮到典型操作系統上任務切換的粒度,編寫代碼將非常容易,該代碼將在不會在單個處理器上出現問題的情況下運行,在運行時會在幾小時甚至幾分鐘內崩潰並燒燬兩個或更多物理處理器。但問題並沒有真正改變 - 當你擁有更多的處理器時,這個問題更有可能更快地顯示出來。

最終,競爭條件(或死鎖,活鎖等)是關於代碼的設計的,關於其運行的硬件的而不是。硬件可以在執行相關條件時採取哪些步驟,但有關差異與簡單數量的處理器無關。相反,它們是關於諸如當您不僅僅擁有多個處理器的單個機器而是具有完全獨立地址空間的多臺機器時所做出的讓步之類的事情,因此您可能需要採取額外步驟來確保在向存儲器寫入值時對於無法直接看到該內存的其他機器上的CPU,它將變得可見。