Static method vs instance method, multithreading, performance

Can you explain how multiple threads access static methods? Do multiple threads have access to a static method at the same time?

It seems logical to me that if the method is static, this will make it the only resource that will be used by all threads. Therefore, only one thread could use it at a time. I created a console application to test this. But from the results of my test, it turned out that my assumption was wrong.

Several objects are built in my test Worker

. Each Worker

has several passwords and keys. Everyone Worker

has an instance method that hashes their passwords using these keys. There is also a static method that has exactly the same implementation, with the only difference that it is static. After all objects are created Worker

, the startup time is recorded to the console. Then an event is raised DoInstanceWork

and all objects Worker

queue useInstanceMethod

up the threadpool. When all methods or all objects have Worker

completed, the time it takes to complete them is calculated from the start time and written to the console. Then the start time is set to the current time and the event is DoStaticWork

raised. This time all objects Worker

queueuseStaticMethod

in the threadpool. And when all these method calls have completed, the time it took until they all completed is computed again and written to the console.

I expected that the time that objects use their instance method is 1/8 of the time that they use a static method. 1/8 because my machine has 4 cores and 8 virtual threads. But this is not the case. In fact, the time spent using the static method was actually fractionally faster.

How it is? What's going on under the hood? Does each thread get its own copy of the static method?

Here is the console application -

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;
        }
    }
}

      

+3


source to share


5 answers


Methods are code - there is no problem with a thread accessing this code at the same time, since the code does not change when it runs; it is a read-only resource (jitter to the side). What needs to be carefully handled in multi-threaded situations is accessing data simultaneously (and more specifically when that data changes). Whether a method static

or an instance method has nothing to do with whether it needs to be serialized in some way to make it thread safe.



+8


source


In all cases, whether static or instance, any thread can access any method at any time, unless you do the explicit work of preventing it.

For example, you can create a lock to ensure that only one thread can access a given method, but C # won't do that for you.



Think about how to watch TV. The TV does nothing to prevent multiple people from watching it at the same time, and as long as everyone who watches it wants to see the same show, no problem. You certainly wouldn't want a TV to let one person watch it at once, simply because multiple people might want to watch different shows, would you? So if people want to watch different shows, they need some kind of mechanism external to the TV itself (perhaps with one remote control that the current viewer has at the time of the show) to make sure one guy doesn't change the channel. to show it while the other guy is watching.

+4


source


C # methods are "reentrant" (as with most languages, the last time I heard of truly non-returnable code was DOS routines). Each thread has its own call stack, and when a method is called, that thread's call stack is updated to have room for the return address, calling parameters, return value, local values, etc.

Suppose Thread1 and Thread2 call method M at the same time and M has a local variable int n. The Thread1 call table is separate from the Thread2 call stack, so n will have two different instances on two different stacks. Concurrency will only be a problem if n is not stored on the stack, but speaks in the same case (i.e. in a shared resource). The CLR (or is it Windows?) Carefully avoids the problem and cleans up, saves, and restores registers when switching threads. (What do you do in the presence of multiple processors, how do you allocate registers, how do you implement locking. These are really tricky problems that one respect compiler, OS writers do when they think about it)

Being reentrant does not prove that bad things happen when two threads call the same method at the same time: it only proves that nothing bad happens if the method does not have access and does not update other shared resources.

+3


source


When you access an instance method, you access it through an object reference.

When referring to a static method, you refer to it directly.

Thus, static methods are slightly faster.

+1


source


When you create a class, you are not creating a copy of the code. You have a pointer to a class definition and code runs through it. This way, instance methods are accessed in a sane way than static methods.

0


source







All Articles