Read Read Memory Monitoring and Write Order Using Double Lock Check

I have the following function, which is designed to "memoize" functions without arguments. It only makes sense to call the function once, and then return the same result all other times.

private static Func<T> Memoize<T>(Func<T> func) { var lockObject = new object(); var value = default(T); var inited = false; return () => { if (inited) return value; lock (lockObject) { if (!inited) { value = func(); inited = true; } } return value; }; } 

Can I be sure that if the thread reads "inited == true" outside the lock, then it will read the "value" that was written before "inited" was set to true?

Note: Double locking check in .NET covers the fact that it should work, and this question is mainly for checking the correctness of my implementation and, possibly, getting better alternatives.

+6
source share
3 answers

No, because inited not volatile . volatile gives you free memory and acquires the fences you need to establish the right relationship between them.

If before inited is set to true, if inited not set, then the value may not be completely written when another thread reads inited and sees it as true, which may lead to a semi-constructed object being returned. Similarly, if there is a fence in the first test, but there is no corresponding fence, before reading inited , it is possible that the object is completely constructed, but that the CPU core, which saw inited as true, has not yet seen the memory effects of writing value (cache consistency is not necessary means that the effects of sequential recordings are visible in order on other cores). This again could potentially result in a constructed object.

This, incidentally, is an instance of the already very well-documented double-check lock pattern.

Instead of using a lambda that captures local variables (which will cause the compiler to generate an implicit class to store private variables in non-volatile fields), I suggest explicitly creating your own class with volatile , filed for value .

 private class Memoized<T> { public T value; public volatile bool inited; } private static Func<T> Memoize<T>(Func<T> func) { var memoized = new Memoized<T>(); return () => { if (memoized.inited) return memoized.value; lock (memoized) { if (!memoized.inited) { memoized.value = func(); memoized.inited = true; } } return memoized.value; }; } 

Of course, as others have said, Lazy<T> exists for this very purpose. Use it instead of riding yourself, but it’s always useful to know the theory of how something works.

+5
source

I think you better use the standard Lazy<T> class to implement the functions you need, for example:

 private static Func<T> Memoize<T>(Func<T> func) { var lazyValue = new Lazy<T>(func, isThreadSafe: true); return () => lazyValue.Value; } 
+2
source

No, this code is unsafe. The compiler can change the writing order to value and inited ; as is the memory system. This means that another thread can see that inited set to true , and value is still the default.

This pattern is called double-check locking and is discussed by Albahari under Lazy Initialization . The recommended solution is to use the built-in Lazy<T> class. The equivalent implementation will be as follows:

 private static Func<T> Memoize<T>(Func<T> func) { var lazy = new Lazy<T>(func); return () => lazy.Value; } 
+1
source

Source: https://habr.com/ru/post/986079/


All Articles