In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-04-02 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Network Security >
Share
Shulou(Shulou.com)06/01 Report--
Topic
The content of this section can be regarded as a very old point of knowledge, which has already appeared in .NET 4.0, and some gardeners have made some analysis in the garden, so why should I talk about it again? The reasons are as follows:
(1) if I haven't used it, it can be regarded as a personal study of my own.
(2) compared with what the gardener said, I wonder if I can talk about it in more detail. Give me a challenge.
(3) can readers understand it more thoroughly? It doesn't matter whether you hit me in the face or not, what matters is the process and experience of learning.
The HashTable class appeared in .NET 1.0, which is not thread safe. Later, for thread safety, there was Hashtable.Synchronized. Before, I saw that my colleagues used Hashtable.Synchronized to map entity classes and tables in the database, and then I saw that some colleagues in other projects used ConcurrentDictionary classes for mapping. After checking the data, I found that Hashtable.Synchronized was not really thread safe, so I decided to find out. There have been many articles in the garden saying that the ConcurrentDictionary class is not thread-safe. Why is it thread-unsafe? At least first we need to know what thread safety is and see what its definition is. The definition is as follows:
Thread safety: if your code is in a process where multiple threads are running at the same time, those threads may run the code at the same time. If the result of each run is the same as that of a single thread, and the values of other variables are the same as expected, it is thread-safe.
A relatively unified definition of thread safety is given above. Most of the methods in the garden feel that it is not thread safe for the GetOrAdd or AddOrUpdate parameters in this class to contain delegates. We also give the definition of thread safety above. Now let's take a look at one of them.
Private static readonly ConcurrentDictionary _ dictionary = new ConcurrentDictionary (); public static void Main (string [] args) {var task1 = Task.Run () = > PrintValue ("JeffckWang"); var task2 = Task.Run (() = > PrintValue ("cnblogs")); Task.WaitAll (task1, task2); PrintValue ("JeffckyWang from cnblogs"); Console.ReadKey () } public static void PrintValue (string valueToPrint) {var valueFound = _ dictionary.GetOrAdd ("key", x = > {return valueToPrint;}); Console.WriteLine (valueFound);}
For the GetOrAdd method, how does it know whether the data should be added or fetched? The method is described as follows:
TValue GetOrAdd (TKey key,Func valueFactory)
When a specified key is given, it will traverse if it exists and directly return its value, if it does not exist, the second parameter will be called, that is, the delegate will run, add it to the dictionary, and finally return to the caller the corresponding value of this key.
Running the above program at this time, we will get the result of one of the following two:
We start two threads, the above running results are not the same, according to the above definition should be thread safety ah, well here on the definition of thread safety we should eliminate the following two points can be regarded as real thread safety.
(1) conditions of competition
(2) deadlock
So the question is, what are the competitive conditions? Well, I'm the legendary 100,000 something.
As my girlfriend said, there are so many reasons, what I said is right, don't ask why, but for such a serious thing, we have to seek truth from facts, right? Competition condition is a kind of behavior in software or system, its output will not be affected by other events, if the event is affected, if the event does not occur, the consequence is very serious, and then bugno is produced. The most common scenario occurs when two threads share a variable at the same time, one thread is reading the variable and the other is writing the variable at the same time. For example, define a variable initialized to 0, and now two threads share this variable, and one thread operation increases it by 1, while another thread operation increases it by 1. In fact, the result we expect should be 2, so in order to solve the competition, we use the locking mechanism to achieve thread safety in a multi-threaded environment.
So the question is, what is a deadlock?
As for deadlock, needless to say, deadlock occurs in multithreaded or concurrent environment, in order to wait for other operations to complete, but other operations have not been completed, resulting in deadlock. What conditions can lead to a deadlock? As follows:
(1) Mutual exclusion: only processes use resources at a given time.
(2) occupy and wait.
(3) not to be preemptive.
(4) cycle waiting.
At this point, through our understanding of thread safety, we understand that locks are generally added for thread safety, but the method in which parameters contain delegates in ConcurrentDictionary is not locked, but the result is still the same. as for other unpredictable situations, according to my personal understanding, it is not completely thread unsafe, but data inconsistencies may occur in a multi-threaded environment. Why are the data inconsistent? Let's move on. We modify the above method as follows:
Public static void PrintValue (string valueToPrint) {var valueFound = _ dictionary.GetOrAdd ("key", x = > {Interlocked.Increment (ref _ runCount); Thread.Sleep (100); return valueToPrint;}); Console.WriteLine (valueFound) }
Number of runs of the main program output:
Var task1 = Task.Run (() = > PrintValue ("JeffckyWang")); var task2 = Task.Run (() = > PrintValue ("cnblogs")); Task.WaitAll (task1, task2); PrintValue ("JeffckyWang from cnblogs"); Console.WriteLine (string.Format ("number of runs: {0}", _ runCount))
At this point, we see that we do get the same value, but run it twice, why do we run it twice, when the second thread is before running the call, and the value of the first thread has not been saved. The whole situation can be described as follows:
(1) when thread 1 calls the GetOrAdd method, the key does not exist and the valueFactory delegate is called.
(2) Thread 2 also calls the GetOrAdd method. If Thread 1 is not finished, the valueFactory delegate will also be called.
(3) Thread 1 completes the call and returns the JeffckyWang value to the dictionary. At this time, the check key does not have a value, then adds it to the new KeyValuePair and returns the JeffckyWang to the caller.
(4) Thread 2 completes the call and returns the cnblogs value to the dictionary, checking that the value of this key has been saved in thread 1, so the interrupt adds its value instead of the value in thread 1, and finally returns it to the caller.
(5) Thread 3 calls the GetOrAdd method to find out that the value of the key key already exists and returns its value to the caller. The delegate valueFactory is no longer called.
From here we know that the results are consistent, but after running twice, there are three threads on it, and if there are more threads, it will be run repeatedly, which may cause data inconsistency, so my understanding is that it is not completely thread-unsafe. Is it possible that two of these methods are thread unsafe? didn't the .NET team realize it for a long time? the above also shows that if it is designed in this way to prevent unexpected situations, you need to say a few more words here. The greatest advantage of open source is that it can draw on collective wisdom. For Microsoft.AspNetCore.Mvc.Core, which is open source, we can check the source code of the middleware pipeline as follows:
/ Builds a middleware pipeline after receiving the pipeline from a pipeline provider / public class MiddlewareFilterBuilder {/ / 'GetOrAdd' call on the dictionary is not thread safe and we might end up creating the pipeline more / / once. To prevent this Lazy is used. In the worst case multiple Lazy objects are created for multiple / / threads but only one of the objects succeeds in creating a pipeline. Private readonly ConcurrentDictionary _ pipelinesCache = new ConcurrentDictionary (); private readonly MiddlewareFilterConfigurationProvider _ configurationProvider; public IApplicationBuilder ApplicationBuilder {get; set;}}
Calling the above method through the ConcurrentDictionary class cannot guarantee the number of delegate calls. The mvc intermediate pipeline can only be initialized once, so the ASP.NET Core team uses Lazy to initialize it. In this case, we will also make the above corresponding modifications, as follows:
Private static readonly ConcurrentDictionary _ lazyDictionary = new ConcurrentDictionary (); var valueFound = _ lazyDictionary.GetOrAdd ("key", x = > new Lazy () = > {Interlocked.Increment (ref _ runCount); Thread.Sleep (100); return valueToPrint )); Console.WriteLine (valueFound.Value)
At this point you will get the following:
We change the second parameter to Lazy, and finally call valueFound.value to output the number of calls to the console. At this point, let's explain what happened in the whole process.
(1) when thread 1 calls the GetOrAdd method, the key does not exist and the valueFactory delegate is called.
(2) Thread 2 also calls the GetOrAdd method. If Thread 1 is not finished, the valueFactory delegate will also be called.
(3) Thread 1 completes the call and returns an uninitialized Lazy object. At this time, the delegate on the Lazy object has not been called, and the value of the key key is checked, so the Lazy is inserted into the dictionary and returned to the caller.
(4) Thread 2 also completes the call and returns an uninitialized Lazy object. Before that, it is checked that the value of the existing key key is saved to the dictionary through thread 1, so the creation will be interrupted, and its value will be replaced by the value in thread 1 and returned to the caller.
(5) Thread 1 calls Lazy.Value, and the call to the delegate runs in a thread-safe manner, so it only runs once if it is called by two threads at the same time.
(6) Thread 2 calls Lazy.Value, and the same Lazy has just been initialized by thread 1, and the second delegate call will not be made at this time. If the delegate initialization of thread 1 is not completed, thread 2 will be blocked until it is completed. Thread 2 will not make the call until it is finished.
(7) Thread 3 calls the GetOrAdd method, and if the key key already exists, the delegate is no longer called, and the result saved by the key key is returned directly to the caller.
The above uses Lazy to force us to run the delegate only once. If invoking the delegate is time-consuming and does not use Lazy to implement it, it will be called multiple times. The result is conceivable that now we only need to run it once, although the two results are the same. We call Lazy.Value to make the delegate run in a thread-safe manner, ensuring that only one thread is running at some point, that other calls to Lazy.Value will be blocked until the first call is finished, and the rest of the threads will use the same result.
So the question is, why is calling Lazy.Value thread-safe?
Let's take a look at the Lazy object next. It is convenient to demonstrate that we define a blog class
Public class Blog {public string BlogName {get; set;} public Blog () {Console.WriteLine ("blog constructor called"); BlogName = "JeffckyWang";}}
Next, make the call in the console:
Var blog = new Lazy (); Console.WriteLine ("blog object is defined"); if (! blog.IsValueCreated) Console.WriteLine ("blog object has not been initialized"); Console.WriteLine ("blog name: + (blog.Value as Blog) .BlogName); if (blog.IsValueCreated) Console.WriteLine (" blog object has now been initialized ")
Print as follows:
From the above printing, we know that when blog.Value is called, the blog object is created and the value of the attribute field in the object is returned. The above Boolean attribute, that is, IsValueCreated display indicates whether the Lazy object has been initialized. The above initialization process can be summarized as follows:
Var lazyBlog = new Lazy (() = > {var blogObj = new Blog () {BlogName = "JeffckyWang"}; return blogObj;})
The print result is consistent with the above. The above operations are all done in a non-thread-safe mode. If the object is created only once in a multithreaded environment, we need to use the following constructor:
Public Lazy (LazyThreadSafetyMode mode); public Lazy (Func valueFactory, LazyThreadSafetyMode mode)
Do this by specifying the enumerated value of LazyThreadSafetyMode.
(1) None = 0 [thread unsafe]
(2) PublicationOnly = 1 [for multithreading, when multiple threads run the initialization method, the value will be set to other threads when the first thread completes]
(3) ExecutionAndPublication = 2 [for single thread, locking mechanism, the value of each initialization method is executed, the corresponding output]
Let's demonstrate the situation:
Public class Blog {public int BlogId {get; set;} public Blog () {Console.WriteLine ("blog constructor called");}}
Static void Run (object obj) {var blogLazy = obj as Lazy; var blog = blogLazy.Value as Blog; blog.BlogId++; Thread.Sleep; Console.WriteLine ("blog Id is:" + blog.BlogId);}
Var lazyBlog = new Lazy (() = > {var blogObj = new Blog () {BlogId = 100}; return blogObj;}, LazyThreadSafetyMode.PublicationOnly); Console.WriteLine ("blog object is defined"); ThreadPool.QueueUserWorkItem (new WaitCallback (Run), lazyBlog) ThreadPool.QueueUserWorkItem (new WaitCallback (Run), lazyBlog)
The result is printed as follows:
It is strange that when the thread-safe mode is changed to LazyThreadSafetyMode.ExecutionAndPublication, the results should be 101,102,102,102 are returned, but the above blog.BogId++ and pause order are reversed as follows:
Thread.Sleep; blog.BlogId++
At this time, both modes return 101 and 102, for some reason! In order to ensure the thread safety of the two methods in the ConcurrentDictionary class, we use Lazy to implement the above. The default mode is LazyThreadSafetyMode.ExecutionAndPublication to ensure that the delegate is executed only once. In order not to break the GetOrAdd method of the native call to ConcurrentDictionary, but also to ensure thread safety, we encapsulate a method to facilitate the call.
Public class LazyConcurrentDictionary {private readonly ConcurrentDictionary concurrentDictionary; public LazyConcurrentDictionary () {this.concurrentDictionary = new ConcurrentDictionary ();} public TValue GetOrAdd (TKey key, Func valueFactory) {var lazyResult = this.concurrentDictionary.GetOrAdd (key, k = > new Lazy (() = > valueFactory (k), LazyThreadSafetyMode.ExecutionAndPublication)) Return lazyResult.Value;}}
Make the method call intact:
_ runCount = LazyConcurrentDictionary= LazyConcurrentDictionary Main (task1 = Task.Run (()) = > PrintValue (task2 = Task.Run (()) = > PrintValue (.Format (PrintValue (valueFound = _ lazyDictionary.GetOrAdd (= >)
Finally, the result of running only once is printed correctly, as follows:
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 223
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.