2012-03-11 116 views
3

您能否幫助解釋多線程如何訪問靜態方法?多個線程能夠同時訪問靜態方法嗎?靜態方法vs實例方法,多線程,性能

對我來說,如果一個方法是靜態的,它會使它成爲所有線程共享的單個資源,這似乎是合乎邏輯的。因此,一次只能有一個線程使用它。我創建了一個控制檯應用程序來測試它。但從我的測試結果看來,我的假設是不正確的。

在我的測試中,構建了多個Worker對象。每個Worker都有一些密碼和密鑰。每個Worker都有一個實例方法,用它的密鑰對密碼進行哈希處理。還有一個靜態方法,它具有完全相同的實現,唯一的區別是它是靜態的。所有Worker對象已創建後,開始時間將寫入控制檯。然後引發DoInstanceWork事件,並且所有Worker對象都將其useInstanceMethod排隊到線程池。當所有方法或所有對象都完成所有完成所花費的時間時,將從開始時間開始計算並寫入控制檯。然後將開始時間設置爲當前時間,並提高DoStaticWork事件。這次所有的Worker對象都將它們的useStaticMethod排隊到線程池。當所有這些方法調用完成所花費的時間,直到他們全部完成後再次計算並寫入控制檯。

我在等待對象使用實例方法時所用的時間是使用靜態方法所花時間的1/8。 1/8,因爲我的機器有4個內核和8個虛擬線程。但事實並非如此。事實上,使用靜態方法所花費的時間實際上要快得多。

這是怎麼回事?引擎蓋下發生了什麼?每個線程都得到它自己的靜態方法的副本嗎?

這裏是控制檯APP-

using System; 
using System.Collections.Generic; 
using System.Security.Cryptography; 
using System.Threading; 

namespace bottleneckTest 
{ 
    public delegate void workDelegate(); 

    class Program 
    { 
     static int num = 1024; 
     public static DateTime start; 
     static int complete = 0; 
     public static event workDelegate DoInstanceWork; 
     public static event workDelegate DoStaticWork; 
     static bool flag = false; 

     static void Main(string[] args) 
     { 
      List<Worker> workers = new List<Worker>(); 
      for(int i = 0; i < num; i++){ 
       workers.Add(new Worker(i, num)); 
      } 
      start = DateTime.UtcNow; 
      Console.WriteLine(start.ToString()); 
      DoInstanceWork(); 
      Console.ReadLine(); 
     } 
     public static void Timer() 
     { 
      complete++; 
      if (complete == num) 
      { 
       TimeSpan duration = DateTime.UtcNow - Program.start; 
       Console.WriteLine("Duration: {0}", duration.ToString()); 
       complete = 0; 
       if (!flag) 
       { 
        flag = true; 
        Program.start = DateTime.UtcNow; 
        DoStaticWork(); 
       } 
      } 
     } 
    } 
    public class Worker 
    { 
     int _id; 
     int _num; 
     KeyedHashAlgorithm hashAlgorithm; 
     int keyLength; 
     Random random; 
     List<byte[]> _passwords; 
     List<byte[]> _keys; 
     List<byte[]> hashes; 

     public Worker(int id, int num) 
     { 
      this._id = id; 
      this._num = num; 
      hashAlgorithm = KeyedHashAlgorithm.Create("HMACSHA256"); 
      keyLength = hashAlgorithm.Key.Length; 
      random = new Random(); 
      _passwords = new List<byte[]>(); 
      _keys = new List<byte[]>(); 
      hashes = new List<byte[]>(); 
      for (int i = 0; i < num; i++) 
      { 
       byte[] key = new byte[keyLength]; 
       new RNGCryptoServiceProvider().GetBytes(key); 
       _keys.Add(key); 

       int passwordLength = random.Next(8, 20); 
       byte[] password = new byte[passwordLength * 2]; 
       random.NextBytes(password); 
       _passwords.Add(password); 
      } 
      Program.DoInstanceWork += new workDelegate(doInstanceWork); 
      Program.DoStaticWork += new workDelegate(doStaticWork); 
     } 
     public void doInstanceWork() 
     { 
      ThreadPool.QueueUserWorkItem(useInstanceMethod, new WorkerArgs() { num = _num, keys = _keys, passwords = _passwords }); 
     } 
     public void doStaticWork() 
     { 
      ThreadPool.QueueUserWorkItem(useStaticMethod, new WorkerArgs() { num = _num, keys = _keys, passwords = _passwords }); 
     } 
     public void useInstanceMethod(object args) 
     { 
      WorkerArgs workerArgs = (WorkerArgs)args; 
      for (int i = 0; i < workerArgs.num; i++) 
      { 
       KeyedHashAlgorithm hashAlgorithm = KeyedHashAlgorithm.Create("HMACSHA256"); 
       hashAlgorithm.Key = workerArgs.keys[i]; 
       byte[] hash = hashAlgorithm.ComputeHash(workerArgs.passwords[i]); 
      } 
      Program.Timer(); 
     } 
     public static void useStaticMethod(object args) 
     { 
      WorkerArgs workerArgs = (WorkerArgs)args; 
      for (int i = 0; i < workerArgs.num; i++) 
      { 
       KeyedHashAlgorithm hashAlgorithm = KeyedHashAlgorithm.Create("HMACSHA256"); 
       hashAlgorithm.Key = workerArgs.keys[i]; 
       byte[] hash = hashAlgorithm.ComputeHash(workerArgs.passwords[i]); 
      } 
      Program.Timer(); 
     } 
     public class WorkerArgs 
     { 
      public int num; 
      public List<byte[]> passwords; 
      public List<byte[]> keys; 
     } 
    } 
} 

回答

8

方法是代碼 - 線程同時訪問該代碼沒有問題,因爲代碼沒有通過運行來修改;這是一個只讀資源(拋開抖動)。在多線程情況下需要謹慎處理的是同時訪問數據(更具體地說,當修改該數據是可能的時候)。一個方法是static還是一個實例方法與它是否需要以某種方式序列化以使其線程安全無關。

+1

+1代碼與數據。 – 2012-03-11 14:17:25

+0

感謝您對方法是隻讀資源的說明。我認爲我理解的巨大差距在於同一資源如何被多個線程同時讀取。我認爲這對於對計算機的基本理解是相當重要的。我想我在下意識地想着一個方法只能在每個時鐘週期讀取一次,當我猜測它實際上可以爲許多線程讀取很多次。但是,說這些讀取只是出現併發是真的,因爲它們都發生在一個時鐘週期內?另外如何讀取一個方法?一次或分段?謝謝 – 2012-03-11 14:56:02

4

在所有情況下,不論是靜態或實例,任何線程可以在任何時間訪問任何方法,除非你明確的工作,以防止它。

例如,您可以創建鎖以確保只有一個線程可以訪問給定的方法,但C#不會爲您執行此操作。

認爲它像看電視。電視沒有什麼能阻止多人同時觀看,只要每個觀看它的人都想看到相同的節目,就沒有問題。你肯定不希望電視只允許一個人立即觀看,因爲多人可能想觀看不同的節目,對吧?因此,如果人們想觀看不同的節目,他們需要某種電視機外部的機制(可能只有一個遙控器,當前觀衆在他的節目中持有)以確保一個人不會改變當另一個人正在觀看時,他的節目的頻道。

1

訪問實例方法時,通過對象引用訪問它。

當你訪問一個靜態方法時,你直接訪問它。

所以靜態方法速度要快一點。

0

當你實例化一個類時,你不會創建代碼的副本。你有一個指向類的定義的指針,並通過它來加入代碼。因此,實例方法的訪問方式比靜態方法更爲合理

3

C#方法是「可重入的」(就像在大多數語言中一樣;最後一次聽說真正的非重入代碼是DOS例程)每個線程都有自己的調用堆棧,並且當一個方法被調用時,該線程的調用堆棧被更新爲具有用於返回地址,調用參數,返回值,本地值等的空間。

假設線程1和線程2同時調用方法M並且M有一個本地int變量n。 Thread1的調用堆棧與Thread2的調用堆棧分離,因此n將在兩個不同的堆棧中有兩個不同的實例。只有當n不是存儲在堆棧中,而是在同一個寄存器中(即在一個共享資源中)CLR(或者它是Windows?)時,併發會是一個問題,注意不要導致問題並清理,存儲和恢復切換線程時的寄存器。 (你在多CPU的情況下做什麼,你如何分配寄存器,你是如何實現鎖定的。這些確實是一個很難解決的問題,使得編譯器,操作系統編程人員都會想到它)

可重入當兩個線程同時調用相同的方法時,並不證明沒有不好的事情發生:只有當方法不訪問和更新其他共享資源時,它才證明不會發生壞事。