8

[编辑:看起来原始问题涉及双精度而不是整数。所以我认为如果我们将整数更改为双精度,这个问题就成立了。]

从有时返回零值的多个线程中使用的类中读取整数属性时,我遇到了罕见的问题。初始化后值不会更改。

这个问题解决了这个问题。共识是,即使我正在访问一个整数,我也需要同步属性。(一些原始答案已被删除)。我没有在那里选择答案,因为我还没有解决我的问题。

因此,我对此进行了一些研究,但不确定要使用 .Net 4 的哪种锁定机制,或者这些锁定是否应该在类本身之外。

这就是我想使用的:

  public class ConfigInfo
  {
    private readonly object TimerIntervalLocker = new object();
    private int _TimerInterval;
    public int TimerInterval
    {
      get
      {
        lock (TimerIntervalLocker) {
          return _TimerInterval;
        }
      }
    }

    private int _Factor1;
    public int Factor1
    {
      set
      {
        lock (TimerIntervalLocker) {
          _Factor1 = value;
          _TimerInterval = _Factor1 * _Factor2;
        }
      }
      get
      {
        lock (TimerIntervalLocker) {
          return _Factor1;
        }
      }
    }

    private int _Factor2;
    public int Factor2
    {
      set
      {
        lock (TimerIntervalLocker) {
          _Factor2 = value;
          _TimerInterval = _Factor1 * _Factor2;
        }
      }
      get
      {
        lock (TimerIntervalLocker) {
          return _Factor2;
        }
      }
    }
  }

但我读到这非常慢。

另一种选择是ConfigData在用户端锁定实例,但这似乎需要做很多工作。我见过的另一种选择是Monitor.EnterMonitor.Exit但我认为 Lock 是相同的东西,语法更少。

那么使类的属性线程安全的最佳实践是什么?

4

4 回答 4

1

一个。使用锁可能会很慢,因为它使用操作系统资源,如果属性的复杂性较低,那么自旋锁(或 interlocked.compareexchange)会更快。

湾。您必须确保线程不会进入锁,并通过从一个属性调用另一个属性被锁定。- 如果这可能发生(当前不是您的代码中的问题),您需要使锁线程或任务敏感。

编辑:

如果对象应该在初始化期间设置并且从不更改,则使其不可变(就像 .NET 字符串一样)。删除所有公共设置器并提供一个带有参数的构造函数,用于定义初始状态,可能还有用于创建具有修改状态的新实例的附加方法/操作符(例如 var newString = "Old string" + " was modified.";)。

于 2013-10-23T14:14:16.457 回答
1

如果值永远不会改变,那么只制作该实例的副本并将每个线程传递一个它自己的实例会更容易。完全不需要锁定。

于 2013-10-23T14:35:15.973 回答
1

我认为您应该重写您的ConfigInfo课程,使其看起来像这样;那么你就不会出现溢出或线程问题:

public sealed class ConfigInfo
{
    public ConfigInfo(int factor1, int factor2)
    {
        if (factor1 <= 0)
            throw new ArgumentOutOfRangeException("factor1");

        if (factor2 <= 0)
            throw new ArgumentOutOfRangeException("factor2");

        _factor1 = factor1;
        _factor2 = factor2;

        checked
        {
            _timerInterval = _factor1*_factor2;
        }
    }

    public int TimerInterval
    {
        get
        {
            return _timerInterval;
        }
    }

    public int Factor1
    {
        get
        {
            return _factor1;
        }
    }

    public int Factor2
    {
        get
        {
            return _factor2;
        }
    }

    private readonly int _factor1;
    private readonly int _factor2;
    private readonly int _timerInterval;
}

请注意,我checked用于检测溢出问题。

否则某些值会给出不正确的结果。

例如,57344 * 524288将在未经检查的整数算术中给出零(并且有很多其他的值对会给出零,甚至更多会给出负结果或“似乎”正确的正值)。

于 2013-10-23T14:53:33.917 回答
0

如评论中所述,最好将属性设置为readonly。我想到了以下可能:

public class ConfigInfo
{
    private class IntervalHolder
    {
        public static readonly IntervalHolder Empty = new IntervalHolder();

        private readonly int _factor1;
        private readonly int _factor2;
        private readonly int _interval;

        private IntervalHolder()
        {
        }

        private IntervalHolder(int factor1, int factor2)
        {
            _factor1 = factor1;
            _factor2 = factor2;
            _interval = _factor1*_factor2;
        }

        public IntervalHolder WithFactor1(int factor1)
        {
            return new IntervalHolder(factor1, _factor2);
        }

        public IntervalHolder WithFactor2(int factor2)
        {
            return new IntervalHolder(_factor1, factor2);
        }

        public int Factor1
        {
            get { return _factor1; }
        }

        public int Factor2
        {
            get { return _factor2; }
        }

        public int Interval
        {
            get { return _interval; }
        }

        public override bool Equals(object obj)
        {
            var otherHolder = obj as IntervalHolder;
            return 
                otherHolder != null &&
                otherHolder._factor1 == _factor1 &&
                otherHolder._factor2 == _factor2;
        }
    }

    private IntervalHolder _intervalHolder = IntervalHolder.Empty;

    public int TimerInterval
    {
        get { return _intervalHolder.Interval; }
    }

    private void UpdateHolder(Func<IntervalHolder, IntervalHolder> update)
    {
        IntervalHolder oldValue, newValue;
        do
        {
            oldValue = _intervalHolder;
            newValue = update(oldValue);

        } while (!oldValue.Equals(Interlocked.CompareExchange(ref _intervalHolder, newValue, oldValue)));
    }

    public int Factor1
    {
        set { UpdateHolder(holder => holder.WithFactor1(value)); }
        get { return _intervalHolder.Factor1; }
    }

    public int Factor2
    {
        set { UpdateHolder(holder => holder.WithFactor2(value)); }
        get { return _intervalHolder.Factor2; }
    }
}

这样,您的TimerInterval值始终与其因素同步。唯一的问题是,当某个线程读取其中一个属性而另一个线程从ConfigInfo外部写入它们时。第一个可能会得到错误的值,如果不引入单个锁根,我看不到任何解决此问题的方法。问题是读取操作是否至关重要。

于 2013-10-23T14:27:19.113 回答