About Volatile, lock, Interlocked and Synchronized in multi-threaded environment

  • Welcome to like: Collection ?Leave a message Please correct me if there are any mistakes, give people roses, and leave lingering fragrance in your hands!
  • Author of this article: Original by webmote
  • Author’s motto: In the new journey, we face not only technology but also people’s hearts. People’s hearts are immeasurable, and the sea water is immeasurable. Only technology is a shining lighthouse in the deep dark night!

Preface


Variable access under multi-threading is like the king of Neptune who has several boats under his feet. He arranges every girlfriend’s date under his meticulous time management. If he is not careful, he may overturn the boat of friendship and completely fall into the endless sea. deep…

In order to allow you, dear apes, to elegantly control the frequency when jumping between dating partners, the programming language introduces multiple keywords and object classes to complete related operations.

Let’s take a look one by one to see what kind of weird things these concepts can accomplish!

1. Volatile modifier keyword

The volatile keyword is usually used to indicate that the value of a field is likely to be modified by multiple threads, so it should not be optimized when the compiler (VS) is compiled, and it will not be cached in the compiler or hardware registers. inside.

The volatile keyword ensures that every time it is read and written, its value is taken directly from the memory, avoiding any optimization and caching.

The information identified by the volatile keyword is just like Neptune’s A’s girlfriend information. Every time Neptune wants to know A’s girlfriend’s information, A’s latest information will be displayed instead of inquiring from other people. Outdated information. With first-hand information, we can avoid accidental capsizing to the greatest extent.

Let’s take a simulation example. Due to compiler optimization, preparing this example is not easy.

//Let us test it under .net6...
Console.WriteLine("Start testing...");
var test = new Test();
new Thread(delegate () {<!-- --> Thread.Sleep(500); test.foo = 255; }).Start();
while (test.foo != 255) ;
Console.WriteLine("Oh no, girlfriend A is arriving at the battlefield!");
Console.ReadLine();

public class Test
{<!-- -->
    public int foo = 0;
}

If you are running under the Debug version, you can receive the arrival information of girlfriend A at this time. But once you publish it as a Release, the gears of fate begin to turn, and you suddenly no longer receive important arrival information. As time ticks by, the breath of danger hits your face.

You can also try it, switch to the Release version, press Ctrl + F5, the interface is as follows:


At this time, the importance of the volatile keyword is reflected. We modify the following information:

public class Test
{<!-- -->
    public volatile int foo = 0;
}


Look, a volatile can save your life.

Notes on using volatile:

  • The volatile keyword is often used in multi-threaded applications to handle shared fields that are accessed simultaneously by multiple threads.
  • volatile is not used for synchronization; it only ensures visibility and atomicity of individual read and write operations. If synchronization is required to enforce ordering or mutual exclusion, consider using other synchronization mechanisms such as lock, Monitor, Semaphore.
  • When working with shared data in multi-threaded scenarios, it is generally recommended to use the lock keyword or other atomic operation classes, since using the volatile keyword alone may not be sufficient for complex synchronization requirements .
  • The volatile` keyword is used for field modification. Generally used are integers, Boolean, pointers, and of course reference types (generally referring to addresses).

Generally, the Boolean value of closing the thread is the best usage scenario.

Singleton double-checked lock scenarios are also useful, for example:

public class Singleton {<!-- -->
private static volatile Singleton _instance = null;
private static Object _locker = new Object();
public static Singleton GetSingleValue()
{<!-- -->
   if (_instance == null)
   {<!-- -->
       lock(_locker)
       {<!-- -->
          if (_instance == null)
          {<!-- --> _ instance = new Singleton(); }
       }
   }
   return_instance;
}

Of course, there is a simpler way to write it, that is to use the Lazy class

public class Singleton
{<!-- -->
     private static readonly Lazy<Singleton> _instance
         = new Lazy<Singleton>(() => new Singleton());
    private Singleton()
    {<!-- -->
    }
     public static Singleton Instance
     {<!-- -->
         get
         {<!-- -->
             return _instance.Value;
         }
     }
}

2. Lock, lock the person you want to lock

Lock lock is one of the most useful protection mechanisms. Lock the resource and let other threads queue up behind it so they don’t hit each other.


In other words, Neptune’s schedule must be locked. If there is no lock, Neptune will be dead.

Here is a simple example:

private object mylock = new object();

public int A {<!-- -->

  get {<!-- -->
    int result;
    lock(mylock) {<!-- -->
    result = mA;
    }
    return result;
  }

  set {<!-- -->
     lock(mylock) {<!-- -->
        mA = value;
     }
  }
}

As a demonstration, this example is simple enough; as a deep learning example, this example is not good.

The properties of most classes do not require lock operations. It is enough to use public DateTime CreatedTime{get;set;}. Because basic types are all atomic operations, there is no need to lock unless you have more complex operations in get and set.

Therefore, it is not necessary to add a lock. If it is accessed by multiple threads, you might as well add volatile. Of course, attributes cannot be added directly, and more code needs to be written.

3.Interlocked non-locking atomic operation

Locks are unique, so for time management gurus, this is not good news.

So what other methods are there that can not only satisfy the masters to perform multiple operations at the same time, but also get notifications in a normal and timely manner? Then I have to mention Interlocked . It is economical and is indeed a must-have word for home travel.

public class NuclearPowerPlant
{<!-- -->
private long _meltdownIsHappening = 0;
public bool MeltdownIsHappeningRightNow
{<!-- -->
get
{<!-- -->
/* The lock operation only supports integer type, so we use it to replace Boolean.
*/
return Interlocked.Read(ref _meltdownIsHappening) == 1;
}
set
{<!-- -->
Interlocked.Exchange(ref _meltdownIsHappening, Convert.ToInt64(value));
}
}
}

This efficiency is incredibly high.

Note that Interlocked.Increment(ref this.counter); is equivalent to lock(this.locker) this. Counter + + ; in terms of implementation, but is it efficient? It doubled several times. Unfortunately, good things always have limits. Interlocked only supports integer types.

4. Synchronized synchronization operation

The Synchronized keyword always seems to be copied from somewhere, so this usage is not common.
However, its meaning is very clear, that is, only one thread is allowed to access it at the same time.

code show as below:

public class Test
{<!-- -->
    public volatile int foo = 0;

    [MethodImpl(MethodImplOptions.Synchronized)]
    public int Add(int a)
    {<!-- -->
        return foo + a;
    }
}

MethodImpl(MethodImplOptions.Synchronized)The implementation of this attribute is also very simple, which is crude lock(this).

Therefore, direct use is not recommended.

Summary

Oh oh oh, it seems that I still have more to say, but time is limited, so let’s stop here.

You’ve seen this, do you still care about giving it a like?

I’ve already liked it, do you still care about a collection?

I have collected them all, do you still care about a comment?