2012-03-11 81 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的情况下做什么,你如何分配寄存器,你是如何实现锁定的。这些确实是一个很难解决的问题,使得编译器,操作系统编程人员都会想到它)

可重入当两个线程同时调用相同的方法时,并不证明没有不好的事情发生:只有当方法不访问和更新其他共享资源时,它才证明不会发生坏事。