静态方法与实例方法,多线程,性能
本文关键字:多线程 性能 实例方法 静态方法 | 更新日期: 2023-09-27 18:27:28
您能帮助解释多个线程如何访问静态方法吗?多个线程是否能够同时访问静态方法?
对我来说,如果一个方法是静态的,那么它将成为所有线程共享的单个资源,这似乎是合乎逻辑的。因此,一次只有一个线程能够使用它。我已经创建了一个控制台应用程序来测试这一点。但从我的测试结果来看,我的假设似乎是不正确的。
在我的测试中,构造了许多Worker
对象。每个Worker
都有许多密码和密钥。每个Worker
都有一个实例方法,该方法将其密码与密钥散列。还有一种静态方法具有完全相同的实现,唯一的区别是它是静态的。创建完所有Worker
对象后,开始时间将写入控制台。然后引发DoInstanceWork
事件,所有Worker
对象将其useInstanceMethod
排入线程池。当所有方法或所有Worker
对象都完成时,从开始时间开始计算它们全部完成所需的时间,并将其写入控制台。然后将开始时间设置为当前时间,并引发DoStaticWork
事件。这一次,所有Worker
对象都将其useStaticMethod
排入线程池。当所有这些方法调用都完成时,将再次计算直到它们全部完成所需的时间并将其写入控制台。
我期望对象使用其实例方法所花费的时间是使用静态方法所花费时间的1/8。1/8,因为我的机器有4个内核和8个虚拟线程。但事实并非如此。事实上,使用静态方法所花费的时间实际上要快一点点。
这是怎么回事?引擎盖下面发生了什么?每个线程都有自己的静态方法副本吗?
这是控制台应用程序-
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;
}
}
}
方法是代码-线程并发访问该代码没有问题,因为运行该代码不会修改该代码;它是只读资源(抖动除外)。在多线程情况下,需要小心处理的是同时访问数据(更具体地说,当有可能修改该数据时)。方法是static
还是实例方法与是否需要以某种方式序列化以确保线程安全无关。
在任何情况下,无论是静态还是实例,任何线程都可以在任何时候访问任何方法,除非你做了明确的工作来阻止它。
例如,您可以创建一个锁,以确保只有一个线程可以访问给定的方法,但C#不会为您这样做。
把它想象成看电视。一台电视不会阻止多个人同时观看,只要每个观看它的人都想看同一个节目,就没有问题。你当然不会希望一台电视一次只允许一个人看,因为多个人可能想看不同的节目,对吧?因此,如果人们想看不同的节目,他们需要电视本身之外的某种机制(也许有一个遥控器,当前观众在节目期间可以握住它),以确保一个人在另一个人观看时不会将频道切换到他的节目。
C#方法是"可重入的"(就像在大多数语言中一样;我上一次听说真正不可重入代码是DOS例程)每个线程都有自己的调用堆栈,当调用一个方法时,该线程的调用堆栈会更新,以便为返回地址、调用参数、返回值、本地值等留出空间。
假设Thread1和Thread2同时调用方法M,并且M有一个局部int变量n。Thread1的调用堆栈与Thread2的调用堆栈是分开的,因此n将在两个不同的堆栈中有两个不同实例。只有当n不是存储在堆栈中,而是存储在同一个寄存器(即共享资源中)时,并发才是一个问题。CLR(或者是Windows?)注意不要让它引起问题,并在切换线程时清理、存储和恢复寄存器。(在多个CPU的情况下,你会做什么,你如何分配寄存器,你如何实现锁定。这些确实是困难的问题,让人想起编译器和操作系统编写器。)
当两个线程同时调用同一个方法时,可重入并不能证明没有坏事发生:只有当该方法不访问和更新其他共享资源时,它才能证明没有坏事出现。
当您访问实例方法时,您是通过对象引用来访问它的。
当您访问一个静态方法时,就是直接访问它。
所以静态方法稍微快一点。
当你安装一个类时,你不会创建代码的副本。你有一个指向类定义的指针,代码通过它加入。因此,实例方法的访问方式与静态方法