问题
I would like to use net wisdom to clarify some moments regarding multi-threading in .net. There are a lot of stuff in the internet about it however I was not able to find a good answer to my question.
Let say we want to maintain a state of something in our class with safety for concurrent threads. Easy case is when state is int:
class Class1
{
volatile int state = 0;
public int State
{
get
{
return state;
}
}
public Action<int> StateUpdated;
public void UpdateState(int newState)
{
state = newState;
if (StateUpdated != null)
StateUpdated(newState);
}
}
'volatile' should be enough in this case. Whatever thread needs to get current state it can use 'State' property which will never be cached. Whatever thread wants to update state it can do it safely using 'UpdateState'.
However, what to do if state is a structure? Is a complete 'lock' the only way? Side question: can a variable still be cached inside the lock?
struct StateData
{
//some fields
}
class Class1
{
StateData state;
public StateData State
{
get
{
return state;
}
}
public Action<StateData> StateUpdated;
public void UpdateState(StateData newState)
{
state = newState;
if (StateUpdated != null)
StateUpdated(newState);
}
}
And eventually the main question: will this code be sufficient for managing a collection of state objects in multi-threading environment? Or there might be some hidden problems.
public struct StateData
{
//some fields
}
public delegate void StateChangedHandler(StateData oldState, StateData newState);
class Class1
{
ConcurrentDictionary<string, StateData> stateCollection = new ConcurrentDictionary<string, StateData>();
public StateData? GetState(string key)
{
StateData o;
if (stateCollection.TryGetValue(key, out o))
return o;
else
return null;
}
public StateChangedHandler StateUpdated;
void UpdateState(string key, StateData o)
{
StateData? prev = null;
stateCollection.AddOrUpdate(key, o,
(id, old) =>
{
prev = old;
return o;
}
);
if (prev != null && StateUpdated != null)
StateUpdated(prev.Value, o);
}
}
Thanks for your answers.
回答1:
However, what to do if state is a structure? Is a complete 'lock' the only way?
The volatile
keyword is applicable only to types that can be updated atomically, such as reference type variables, pointers, and primitive types.
However, note that volatile
provides some guarantees besides just access to the marked variable. In particular, all writes to memory that occur before a write to a volatile
-marked memory location will be seen by any code that reads from memory after reading from that same volatile
-marked memory location.
In theory, this means you can use a volatile
field to provide volatile-like behavior for other memory locations.
In practice though, there is still a problem: new writes to the same memory locations may or may not be visible, and of course not all writes can be completed atomically. So this use of volatile
is really only good for other memory locations that could be marked volatile, and even then doesn't ensure you won't get newer values than the volatile
-marked memory location would otherwise indicate.
Which is a long way of saying, you should just use lock
and be done with it. :)
Side question: can a variable still be cached inside the lock?
I'm not entirely sure what you mean by this question. But in general: the compilers are permitted to make optimizations as long as those optimizations don't affect the observed behavior of the code in a single thread. If the type of caching you are thinking of would not violate this rule, it might be allowed. Otherwise it wouldn't be.
will this code be sufficient for managing a collection of state objects in multi-threading environment?
The code as posted seems fine, at least as far as ensuring that the dictionary is always providing coherent state values.
回答2:
volatile
ensures that you get the latest value of a variable from any thread. This works only with primitive types such as int, short etc. and only with value-types.
A volatile reference type will only ensure that you get the latest reference of the declaration and not the value of where it points to. You should only use volatile for immutable data types.
Some thread-safe ways of juggling with collections are: using lock
, Mutex, and one of the latest goodies of .NET 4.5 ConcurrentCollections (which unfortunately proved to be slower than the generic bag-lock combo).
- If performance and freedom is what you want go with lock
- If you are managing a very extensive application go with Mutex
- Depending on how long (&how many) operations take place in a collection you could go with ConcurrentCollections
Here's a nice comparison between locks and thread-safe collections.
来源:https://stackoverflow.com/questions/28925889/volatile-for-structs-and-collections-of-structs