Имеет ли смысл здесь блокировка ()? - PullRequest
1 голос
/ 14 марта 2011

У меня есть простой класс, такой как:

  public class XXX
  {
    double val1;
    double val2;
    double delta;
    public void SetValues(double v1, double v2)
    {
      val1 = v1;
      val2 = v2;
      delta = val1 - val2;
    }

    public double Val1 { get { return val1; } }
    public double Val2 { get { return val2; } }
    public double Delta { get { return delta; } }
  }

, один поток для установки значений и несколько потоков, которые читают значения.Таким образом, можно использовать lock(), чтобы все чтение и запись были непрерывными.Но Я знаю, что синхронизация никогда не достигается, всегда есть вероятность, что Val1 - Val2 может быть не равным Delta , который меня не волнует .Больше всего меня беспокоит получение стабильных значений через геттеры.Однако lock() стоит дорого для этой ситуации, потому что в основном читатели будут работать.

Следующее лучшее, что приходит мне в голову, это использование Interlocked.Exchange()

    public void SetValues(double v1, double v2)
    {
      Interlocked.Exchange(ref val1, v1);
      Interlocked.Exchange(ref val2, v2);
      Interlocked.Exchange(ref delta, v1 - v2);
    }

    public double Val1 { 
      get 
      {
        double val = 0;
        Interlocked.Exchange(ref val, val1);
        return val; 
      } 
    }

Но код кажется мне довольно глупым.Я не знаю.

Так имеет ли смысл lock()?Должен ли я использовать Interlocked.Exchange() для увеличения производительности?Или что еще я могу сделать?

Ответы [ 3 ]

3 голосов
/ 14 марта 2011

Вам необходимо заблокировать весь метод SetValues:

private object lockObject = new object();

public void SetValues(double v1, double v2)
{
  lock(lockObject)
  {
    val1 = v1;
    val2 = v2;
    delta = val1 - val2;
  }
}

public double Val1 { get { lock(lockObject) { return val1; } } }
public double Val2 { get { lock(lockObject) { return val2; } } }
public double Delta { get { lock(lockObject) { return delta; } } }

Считыватели по-прежнему могут получать Val1, Val2 и Delta, которые не принадлежат друг другу, потому что они читают его в несколько шагов.

Вы можете поместить Val1, Val2 и Delta в объект значения, который может быть получен за один раз и который не изменяется:

public Values Val1
{ 
  get 
  { 
    lock(lockObject) 
    { 
      // create a consistent object which holds a copy of the values
      return new Values(val1, val2, delta); 
    } 
  }
}

struct Values
{
  // ...
  public double Val1 { get /* ... */ }
  public double Val2 { get /* ... */ }
  public double Delta  { get /* ... */ }
}
2 голосов
/ 14 марта 2011

Если вам нужно, чтобы геттеры возвращали стабильный результат, и вам нужно поддерживать только внутреннюю синхронизацию, то я бы порекомендовал вам создать класс с именем «Снимок» (содержащий Val1, Val2 и Delta) или что-то подобное.В вашем установщике создайте новую копию этого класса и замените ее в переменную экземпляра.В своем геттере просто верните текущую копию снимка.До тех пор, пока вызывающие абоненты нуждаются в согласованном взаимодействии, они будут использовать этот единственный экземпляр моментального снимка, возвращенный из одного вызова геттера.

Так что вам придется отказаться от использования нескольких получателей - нет способа (без внешней синхронизации) гарантироватьчто Val1, Val2 и Delta будут согласованы в противном случае.


public class XXX
  {
    public class Snapshot {
      double val1;
      double val2;
      double delta;
      public Snapshot (double val1,double val2)
      {
         this.val1 = val1;
         this.val2 = val2;
         this.delta = val1 - val2;
      }
      public double Val1 { get { return val1; } }
      public double Val2 { get { return val2; } }
      public double Delta { get { return delta; } }
    }
    Snapshot _current;
    public void SetValues(double v1, double v2)
    {
      Snapshot s = new Snapshot(v1,v2);
      /* If there were subsequent steps needed to get the snapshot "ready", you could do them here.
         Otherwise, I think you can do this as a single assignment into _current above */
      _current = s;
    }

    public Snapshot Current { get { return _current; } }

  }
2 голосов
/ 14 марта 2011

Для сценариев с несколькими читателями / писателями я бы использовал ReaderWriterLockSlim .

Represents a lock that is used to manage access to a resource, 
allowing multiple threads for reading or exclusive access for writing.

Use ReaderWriterLockSlim to protect a resource that is read by multiple 
threads and written to by one thread at a time. ReaderWriterLockSlim 
allows multiple threads to be in read mode, allows one thread to be in 
write mode with exclusive ownership of the lock, and allows one thread 
that has read access to be in upgradeable read mode, from which the 
thread can upgrade to write mode without having to relinquish its 
read access to the resource.
...