2008-10-05 8 views
1

В многопоточном коде, когда экземпляр может быть прочитан или написан несколькими потоками, их необходимо заблокировать для безопасного выполнения этих операций.Сделать блокировку проще в MTA

Чтобы избежать повторения создания объекта для блокировки и записи множества операторов блокировки через код, я создал общий класс для обработки блокировки.

Я пропустил что-нибудь, концептуально? Это должно работать, не так ли?

public class Locked<T> where T : new() 
{ 
    private readonly object locker = new object(); 

    private T value; 

    public Locked() 
     : this(default(T)) 
    { } 

    public Locked(T value) 
    { 
     this.value = value; 
    } 

    public T Get() 
    { 
     lock (this.locker) 
     { 
      return this.value; 
     } 
    } 

    public void Set(T value) 
    { 
     lock (this.locker) 
     { 
      this.value = value; 
     } 
    }  
} 

И пример этого используется в классе:

private Locked<bool> stopWorkerThread = new Locked<bool>(); 

public void WorkerThreadEntryPoint() 
{ 
    while (true) 
    { 
     if (this.stopWorkerThread.Get()) 
     { 
      break; 

} 

Кроме того, как бы я проверить что-то вроде этого, в автоматическом режиме (например, создать модульный тест)?

И наконец, что я могу сделать, чтобы реализовать ++ и - оператор, чтобы избежать этого:

 this.runningThreads.Set(this.runningThreads.Get() + 1); 

ответ

3

Это только замки для продолжительности получения/установки; конечно, во многих распространенных случаях это будет атомарным, просто из-за размера данных.

Однако на самом деле большинство замков должны охватывать больше, чем это, так же, как сбор данных, блокирующих только добавление и т. Д., Не очень помогают - вызывающему абоненту обычно требуется один замок, чтобы охватить «есть ли он? если это обновление, добавьте «последовательность».

Для чего-то простого, как bool, «volatile» может решить проблему намного проще - особенно если это просто выход цикла.

Возможно, вы также захотите рассмотреть [MethodImpl (MethodImplOptions.Synchronized)] - хотя лично я предпочитаю закрытый объект блокировки (например, вы использовали), чтобы предотвратить проблемы с блокировкой внешних людей на объекте (в приведенном выше примере используется это " как замок).

Для тестирования устройства вам понадобится что-то, чтобы доказать, что оно сломано первым - что было бы трудно, так как операции были настолько малы (и уже были атомарными для большинства типов данных). Одной из других вещей, которые он избегает (что volatile также исправляет), является кэширование в регистре, но опять же это оптимизация и трудно заставить ее сломаться.

Если вас интересует блокировка-обертка, вы можете рассмотреть существующий код, например this.

1

В приведенном выше коде есть много потенциальных и реальных проблем с многопоточными функциями, и я бы не использовал что-то подобное в реальной ситуации. Например:

this.runningThreads.Set(this.runningThreads.Get() + 1); 

Здесь есть довольно очевидное состояние гонки. Когда возвращается вызов Get(), объект больше не блокируется. Чтобы выполнить реальный пост или предварительный прирост, счетчик должен быть заблокирован до Get после Set.

Также вам не всегда нужно делать полную блокировку, если все, что вы делаете, является синхронным чтением.

Более удобный интерфейс блокировки (я думаю) требует, чтобы вы явно блокировали экземпляр, где вам нужно это делать.Мой опыт в основном C++, так что я не могу рекомендовать полную реализацию, но мой предпочтительный синтаксис может выглядеть следующим образом:

using (Locked<T> lock = Locked<T>(instance)) 
{ 
    // write value 
    instance++; 
} 

// read value 
print instance; 
+0

Пример кода не будет работать, если заблокированной (например) был объявлен в другом месте и повторно используется для всех абонентов - в противном случае у каждого вызывающего абонента есть другая блокировка, которая побеждает цель. И тогда было бы проще просто объявить объект и заблокировать (obj) – 2008-10-05 08:29:38

Смежные вопросы