1
public class Service { private List<SomeType> _list = null; public static List<SomeType> GetSomeTypeList() { if (_list != null) return _list; List<SomeType> _temp = null; _temp = FunctionToGetSomeTypeValue(); _list = _temp; return _list; } } 

It's server code, GetSomeTypeList may be called by multiple requests (threads), the return result of FunctionToGetSomeTypeValue will be the same even in different thread, it's a static resource, but FunctionToGetSomeTypeValue may fail by return null. My intention is to get the resource when it's needed and try to get it every time it's called, unless it is a success. The question is when updating _list, _list = _temp, do I need to lock using lock(lockobj) {_list=_temp}?

if two threads are calling GetSomeTypeList(), and both goes to call FunctionToGetSomeTypeValue(), one is success, one is failed, the success one call _list=_temp first, failed one then assign _list=null. I'm fine with this case, because eventually _list will be in a good state, and all calls just return _list.

Thing I don't understand is if I don't use lock, is there any chance _list object be assigned a incorrect value (a unexpected value)? Not null, neither correct _temp with correct address.

so the actual question is, assume there's variable: static int Obj = 0;

  • one thread is setting Obj = 1;
  • another thread is setting Obj = 2;

if there's no lock over setting of Obj, value of Obj should be either 1 or 2, won't be anything else, is that correct? what if Obj is a struct?

Is assignment of reference object (address) a atomic operation ? What about simple type object(int, struct)?

2
  • Setting a reference is atomic. Commented Dec 23, 2015 at 23:57
  • Is there a problem with using a lock? Commented Dec 24, 2015 at 0:15

4 Answers 4

2

In addition to the other answers, there exists a Framework class, Lazy<T>, that allows for this lazy instantiation of objects in a thread-safe manner. There is no need to write your own:

public class Service { private static Lazy<List<SomeType>> _list = new Lazy<List<SomeType>>( ()=>FunctionToGetSomeTypeValue(), isThreadSafe: true ); public static List<SomeType> GetSomeTypeList() { return _list.Value; } } 
Sign up to request clarification or add additional context in comments.

1 Comment

Right, no need to reinvent the wheel. Rule 1 of thread synchronization: Don't do it. Use higher-level tools that do it for you :)
1

Assigning a reference is atomic, so _list will always be a valid reference. GetSomeTypeList() will always return a reference to List<SomeType> or a null.

However, it is possible that 2 threads call it at the same time, both get past the if (_list != null) condition and receive different instances of List<SomeType>.

I'd rather put a lock there

public static List<SomeType> GetSomeTypeList() { if (_list == null) { lock (_lock) { if (_list == null) { _list = FunctionToGetSomeTypeValue(); } } } return _list; } 

Or, if you don't like lock, Interlocked.CompareExchange to assign a new value to _list only if it's still null

public static List<SomeType> GetSomeTypeList() { if (_list == null) { var temp = FunctionToGetSomeTypeValue(); Interlocked.CompareExchange(ref _list, temp, null); } return _list; } 

Note that in both cases the first check if (_list == null) is done without any thread synchronization. Once a value is assigned to _list, it can be retrieved safely without synchronization.

Comments

0

As SLaks already pointed out, setting a reference is an atomic operation, i.e. in the case you mentioned there cannot be invalid values. It is only a question which assignment will "win", but it always will be a complete assignment.

The same is true e.g. for int, but in general NOT for structs.

C# language specification:

5.5 Atomicity of variable references

Reads and writes of the following data types are atomic: bool, char, byte, sbyte, short, ushort, uint, int, float, and reference types. In addition, reads and writes of enum types with an underlying type in the previous list are also atomic. Reads and writes of other types, including long, ulong, double, and decimal, as well as user-defined types, are not guaranteed to be atomic.

Comments

0

You don't need a lock, but at least a null check around the end of that method.

There could be two threads calling your FunctionToGetSomeValueType at the same time. If the first is successful, _list will be set to the result. When the second fails a little later, it may reset it to null.

private static object syncLock = new object (); public static List<SomeType> GetSomeTypeList() { if (_list != null) return _list; List<SomeType> _temp = FunctionToGetSomeTypeValue(); if (_temp != null) // check this! _list = _temp; return _list; } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.