2015-05-08 33 views
0

我正在創建一個應用程序,其中有50x50的地圖。在這張地圖上,我可以添加點,這是「點」類的新實例。每個點都有它自己的線程,每個與特定點連接的線程都運行在類的「探索」方法上,並且在此方法中還有另一種方法「check_place(x,y)」,它負責檢查某個地方在地圖上已經被發現了。如果不是,則類「num_discovered」的靜態變量應該增加。應用程序中啓動的每個線程都應該實時訪問該方法「check_place(x,y)」的單個實例。在方法的一個實例上運行的線程

構造:

public dot(Form1 F) 
{  
    /... 
    thread = new System.Threading.Thread(new System.Threading.ThreadStart(explore)); //wątek wykonujący metodę explore klasy robot 
    thread.Start(); 
} 

check_place(X,Y)的方法:

static void check_place(int x, int y) 
{ 
    lock (ob) 
    { 
     if (discovered[x, y] == false) 
     { 
      discovered[x, y] = true; 
      num_discovered += 1; 
     } 
    } 
} 

在探索方法我調用方法 「check_place(X,Y)」 是這樣的:

dot.check_place(x, y); 

是否有足夠的地方在一個時刻只能有一個點可以檢查的地方已經發現的實現情況如何?

+0

是你的問題'發現[x,y]'和'num_discovered'訪問是否是線程安全的?我們需要爲此看到「ob」的聲明。 – CodeCaster

+0

爲什麼每個點都有一個線程?我可以想象,如果點數少,每個點需要大量處理能力,這對您的應用程序實時運行至關重要。如果它是一個學習線程的練習項目,那也很好。否則,這聽起來像是對我來說可維護性。 – MariusUt

+0

當線程調用check_place時線程之間不會存在爭用嗎?爲什麼你需要爲每個點有一個線程? – auburg

回答

3

是否足以實現一次只有一個點可以檢查地點是否已經被發現的情況?

是。但是有什麼意義呢?

如果線程正在等待其他線程花費所有時間,那麼您從多線程中獲得了什麼?

有三個(有時是重疊的)原因,產生更多的線程:

  1. 要使用多個核心的同時:總吞吐量增加。
  2. 當另一個線程正在等待其他事情(通常是來自文件,數據庫或網絡的I/O)時完成工作:整體吞吐量增加。
  3. 在工作完成時對用戶交互作出響應:總體吞吐量下降,但用戶感覺速度更快,因爲他們分別正在作出反應。

這裏最後不適用。

如果您的「檢查」涉及I/O,那麼第二個可能適用,並且此策略可能有意義。

第一個可以很好地應用,但是因爲所有線程都花費大部分時間在其他線程上等待,所以吞吐量並沒有得到改善。

事實上,因爲在設置線程和切換它們之間存在開銷,所以這個代碼將比只有一個線程處理所有事情要慢:如果一次只能有一個線程工作,那麼只有一個線程!

所以你在這裏使用鎖是正確的,因爲它可以防止腐敗和錯誤,但毫無意義,因爲它使一切都變得太慢。

怎麼辦這個問題:

如果你的真實案例涉及I/O或其他原因線程其實花費大部分時間都是在彼此照顧對方的方式,那麼你有什麼是好的。

否則你有兩個選擇。

簡單:只需使用一個線程。硬:有更好的鎖定。有精細的鎖定

一種方式是做雙重檢查:

static void check_place(int x, int y) 
{ 
    if (!discovered[x, y]) 
    lock (ob) 
     if (!discovered[x, y]) 
     { 
     discovered[x, y] = true; 
     num_discovered += 1; 
     } 
} 

現在至少是一些線程會跳過某些情況下discovered[x, y]true無阻礙了其他線程。

當線程將在鎖定期結束時獲得結果時,這非常有用。儘管如此,它仍然不夠好,因爲它只是快速轉向一個案件,它再次爲鎖定而戰。

如果我們的discovered查找是本身線程安全的,線程安全是細粒度的,那麼我們就可以取得一些進展:

static void check_place(int x, int y) 
{ 
    if (discovered.SetIfFalse(x, y)) 
    Interlocked.Increment(ref num_discovered) 
} 

到目前爲止,雖然我們剛剛搬來搬去的問題;我們如何使SetIfFalse線程安全而不使用單個鎖並導致相同的問題?

有幾種方法。我們可以使用條帶鎖或低鎖定併發集合。

這似乎是你有一個50×50大小固定的結構,在這種情況下,這是不是太狠:

private class DotMap 
{ 
    //ints because we can't use interlocked with bools 
    private int[][] _map = new int[50][]; 
    public DotMap() 
    { 
    for(var i = 0; i != 50; ++i) 
     _map[i] = new int[50]; 
    } 
    public bool SetIfFalse(int x, int y) 
    { 
    return Interlocked.CompareExchange(ref _map[x][y], 1, 0) == 0; 
    } 
} 

現在,我們的優勢是:

  1. 我們所有的鎖定要低得多(但請注意,在競爭面前,Interlocked的操作仍然會減慢,儘管不如lock)。
  2. 我們的大部分鎖定都不受其他鎖定的限制。具體而言,在SetIfFalse中可以允許檢查單獨的區域,而不用彼此分開。

這既不是萬能的,但(這種方法仍然遭受爭的臉,也帶來了自己的成本),也易於推廣到其他情況下(改變SetIfFalse的東西,做任何事情超過檢查和更改單一的價值並不容易)。即使在擁有大量內核的機器上,它仍然很可能比單線程方法慢。

另一種可能性是沒有SetIfFalse線程安全可言,但要保證每個相互分隔的線程,讓他們永遠也不會打相同的價值觀,該結構是安全的這種多線程訪問(在線程只遇到不同索引時必須是線程安全的,而且必須是可變結構,其中Add和/或Remove不是可變結構)的情況。

總而言之,你有正確的想法,關於如何使用lock來防止線程導致錯誤,這就是98%的時間使用多線程的方法,因爲它涉及線程等待在別的東西上。你的例子雖然鎖定太多以致於無法從多核心獲益,並且創建代碼並不重要。

1

你在這方面的表現可能會非常糟糕 - 我建議在這裏使用Task.Run來提高效率,當你需要在多個線程上並行運行你的瀏覽方法。

至於鎖定和線程安全的,如果check_place鎖是你在發現變量設置布爾變量的唯一場所和設置num_discovered變量,現有的代碼將工作。如果你從代碼中的其他地方開始設置它們,那麼你也需要在那裏使用鎖。

另外,當從這些變量中讀取數據時,您應該使用同一個鎖對象將這些值讀入其他鎖中的局部變量,以保持線程安全。

我還有其他的建議,但這些是你需要的兩個最基本的東西。

+0

「Task.Run」如何提高效率? –

+0

是的,不是每次都創建一個新線程,Task.Run使用一個內部管理的線程池,只在需要時才按需創建線程。這樣更有效率。 https://msdn.microsoft.com/en-us/library/hh195051%28v=vs.110%29。aspx – Pittmyster

+0

但是線程都在同一個鎖上阻塞,所以線程池將不得不創建更多的線程,因此「按需」會導致相同數量的線程,這同樣是低效的。 –

相關問題