静态方法与实例方法,多线程,性能

时间:2012-03-11 14:04:17

标签: c# multithreading

您能帮助解释多线程如何访问静态方法吗?多个线程是否能够同时访问静态方法?

对我来说,如果一个方法是静态的,那么它将成为一个由所有线程共享的单一资源似乎是合乎逻辑的。因此,一次只能有一个线程使用它。我创建了一个控制台应用程序来测试它。但是根据我的测试结果,似乎我的假设不正确。

在我的测试中,构建了许多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;
        }
    }
}

5 个答案:

答案 0 :(得分:9)

方法是代码 - 线程同时访问该代码没有问题,因为代码没有通过运行来修改;它是一个只读资源(抛开抖动)。在多线程情况下需要仔细处理的是同时访问数据(更具体地说,在修改数据时可能)。方法是static还是实例方法与是否需要以某种方式序列化以使其线程安全无关。

答案 1 :(得分:4)

在所有情况下,无论是静态还是实例,任何线程都可以随时访问任何方法,除非你做了明确的工作来阻止它。

例如,您可以创建一个锁以确保只有一个线程可以访问给定的方法,但C#不会为您执行此操作。

想想就像看电视一样。电视没有什么可以防止多人同时观看它,只要每个观看它的人都希望看到同一个节目,就没有问题。你当然不希望电视只允许一个人立刻观看它,因为很多人可能想观看不同的节目,对吧?因此,如果人们想要观看不同的节目,他们需要电视本身外部的某种机制(可能只有一个遥控器,当前观众在演出期间保留),以确保一个人不会改变另一个人正在观看他演出的频道。

答案 2 :(得分:3)

C#方法是“可重入的”(与大多数语言一样;我最后一次听说真正的非重入代码是DOS例程)每个线程都有自己的调用堆栈,当调用方法时,调用堆栈的那个更新线程以获得返回地址,调用参数,返回值,本地值等的空间。

假设Thread1和Thread2同时调用方法M并且M具有本地int变量n。 Thread1的调用堆栈与Thread2的调用堆栈分开,因此n将在两个不同的堆栈中具有两个不同的实例。只有当n不存储在堆栈中但是在同一个寄存器中(即在共享资源中)时,并发才会成为问题CLR(或者是Windows?)小心不要让它导致问题并清理,存储和恢复切换线程时的寄存器。 (你在多个CPU的存在下做了什么,你如何分配寄存器,你如何实现锁定。这些确实是一个难以解决的问题,使得一个尊重编译器,操作系统编写者在想到它时)。

当两个线程同时调用同一个方法时,重入并不能证明没有坏事发生:它只能证明如果方法不访问和更新其他共享资源就不会发生坏事。

答案 3 :(得分:1)

访问实例方法时,您将通过对象引用访问它。

访问静态方法时,您将直接访问它。

所以静态方法要快一点。

答案 4 :(得分:0)

当你实现一个类时,你不会创建代码的副本。你有一个指向类定义的指针,代码通过它加入。因此,实例方法的访问方式比静态方法