结构和结构集合的易失性

Volatile for structs and collections of structs

我想用网络智慧来澄清一些关于 .net 中多线程的时刻。互联网上有很多关于它的东西,但是我无法找到我的问题的好答案。

假设我们想要在我们的 class 中维护某种状态,同时保证并发线程的安全。简单的情况是状态为 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' 在这种情况下应该足够了。无论线程需要获取当前状态,它都可以使用永远不会被缓存的 'State' 属性 。无论线程想要更​​新状态,它都可以使用 'UpdateState'.

安全地完成

但是state是结构体怎么办?完整的 '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);
    }
}

最后的主要问题是:此代码是否足以在多线程环境中管理状态对象集合?或者可能存在一些隐藏的问题。

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);
    }
}

感谢您的回答。

However, what to do if state is a structure? Is a complete 'lock' the only way?

volatile关键字仅适用于可以自动更新的类型,例如引用类型变量、指针和原始类型。

但是,请注意 volatile 除了访问标记的变量之外还提供了一些保证。特别是,在写入标记为 volatile 的内存位置之前发生的所有内存写入都将被从内存中读取的任何代码看到 after 从同一 volatile-标记内存位置。

理论上,这意味着您可以使用 volatile 字段为其他内存位置提供类似易失性的行为。

虽然在实践中,仍然存在一个问题:对相同内存位置的新写入可能是可见的,也可能是不可见的,当然并不是所有的写入都可以原子地完成。所以 volatile 的这种使用实际上只对 可以 标记为易失性的其他内存位置有用,即使那样也不能确保你不会得到 volatile 标记的内存位置新的 值,否则会指示。

这是一个很长的说法,你应该只使用 lock 并完成它。 :)

Side question: can a variable still be cached inside the lock?

我不太确定你问这个问题是什么意思。但总的来说:只要这些优化不影响在单个线程中观察到的代码行为,就允许编译器进行优化。如果您正在考虑的缓存类型不会违反此规则,则可能会被允许。否则就不会了。

will this code be sufficient for managing a collection of state objects in multi-threading environment?

发布的代码看起来不错,至少就确保字典始终提供一致的状态值而言。

volatile 确保您从任何线程获取变量的最新值。这仅适用于基本类型,如 int、short 等,并且仅适用于 value-types.

volatile 引用类型只会确保您获得声明的最新引用,而不是它指向的值。 您应该只对 不可变数据类型使用 volatile.

处理集合的一些线程安全方法是:使用 lockMutex, and one of the latest goodies of .NET 4.5 ConcurrentCollections(不幸的是,事实证明这比通用的 bag-lock 组合要慢)。

  • 如果性能和自由是您想要的,请选择 lock
  • 如果您正在管理一个非常广泛的应用程序,请使用 Mutex
  • 根据您可以使用 ConcurrentCollections
  • 操作在集合中发生的时间长短(以及多少)

Here 很好地比较了锁和线程安全集合。