2015-07-30 4 views
2

Скажем, у меня есть одноэлементный класс Singleton, который может читать и писать в SerialPort.Блокировка на одноэлементном классе для потокобезопасного устройства IO?

public sealed class Singleton 
{ 
    private static readonly Lazy<Singleton> lazy = 
     new Lazy<Singleton>(() => new Singleton()); 

    public static Singleton Instance { get { return lazy.Value; } } 

    SerialPort commPort = new SerialPort(); 

    private Singleton() 
    { 
     // Setup SerialPort 
    } 

    public String Read() 
    { 
     return commPort.ReadLine(); 
    } 

    public void Write(String cmd) 
    { 
     commPort.WriteLine(cmd); 
    } 
} 

Теперь давайте также сказать, что у меня есть несколько потоков, которые имеют доступ к устройству в конце SerialPort. Некоторые потоки могут записывать только SerialPort, а некоторые могут писать, а затем читать с SerialPort.

Я хочу убедиться, что хотя нить выполняет чтение, напишите, что он не прерывается другим потоком. Может ли это сделать lock на самом Singleton.Instance?

// Running on thread 1 
public Boolean SetLEDStatus(int onOff) 
{ 
    lock(Singleton.Instance) 
    { 
     Singleton.Instance.Write("SET LED " + onOff.ToString() + "\r\n"); 
     String status = Singleton.Instance.ReadLine(); 
     return (status.Contains("SUCCESS")) ? true : false; 
    } 
} 

// Running on thread 2 
public Boolean IsLEDOn() 
{ 
    lock(Singleton.Instance) 
    { 
     Singleton.Instance.Write("GET LED\r\n"); 
     return (Singleton.Instance.ReadLine().Contains("ON")) ? true : false; 
    } 
} 

В этом случае, если SetLEDStatus и IsLEDOn назывались очень близко к тому же времени, я хочу, чтобы убедиться, что SerialPort не написано слишком дважды, прежде чем он будет читать. Мое использование блокировки предотвращает это?

Будет ли этот тип действий называться «транзакционным IO»?

Если это действительно так, есть ли другие более эффективные способы выполнения такого же действия?

EDIT:

Я понимаю, почему замок на Singleton.Instance может быть плохим, если что-то зафиксировать на Singleton.Instance, а затем вызвать метод в Singleton.Instance, который также пытается заблокировать на себя, было бы тупиковым ,

Первоначально планировалось использовать закрытый объект в одиночном режиме для блокировки. Но я как бы говорил из этого из-за ситуации, описанной ниже. Который, я не уверен, что это правильно.

(с использованием двух методов (MINUES запирающий) выше, работающие на thread1 и thread2)

  1. Резьба1 называет Write, Singleton.Instance замки
  2. Резьба2 называет Write, но заблокирован замком
  3. Singleton.Instance завершает Write и освобождает замок
  4. Выполняется вызов Thread2s для Write, Singleton.Instance замки
  5. Резьба1 называет Read, но блокируется замком
  6. Singleton.Instance завершает Write и освобождает замок
  7. Thread1s Read исполняет, Singleton.Instance замки
  8. Резьба2 называет Read, но блокируется замком
  9. Singleton.Instance завершается Read и освобождает замок
  10. Thread2s Read выполнен, Singleton.Instance замки
  11. Singleton.Instance завершает Read и снимает блокировку

В этом случае есть два Writes к последовательному порту в ряде, который является неправомерным. Мне нужно иметь возможность делать WriteRead спиной к спине для некоторых видов связи.

ответ

2

Для объекта блокировки я использовал бы поле для класса (то есть не статического) вместо самого одиночного экземпляра, используя same reasoning on why not to lock(this) ever.

Обычно я использую декларацию, так как объявление объекта блокировки более читаемо как самодокументированный код.

private readonly object _LEDLock = new object(); 

Таким образом, когда кто-то идет смотреть, они говорят: «О, это объект замок, который охраняет доступ к нити LED ресурса.»

ИМХО, я считаю, что поведение в методах SetLEDStatus и IsLEDOn (с блокировкой) будет лучше инкапсулировать в вашем Singleton классе следующим образом:

public sealed class Singleton 
{ 
    private static readonly Lazy<Singleton> lazy = 
     new Lazy<Singleton>(() => new Singleton()); 

    public static Singleton Instance { get { return lazy.Value; } } 

    SerialPort commPort = new SerialPort(); 

    private readonly object _LEDLock = new object(); 

    private Singleton() 
    { 
     // Setup SerialPort 
    } 

    /// <summary> 
    /// This goes in the singleton class, because this is the class actually doing the work. 
    /// The behavior belongs in this class. Now it can be called with thread-safety from 
    /// any number of threads 
    /// </summary> 
    public Boolean SetLEDStatus(int onOff) 
    { 
     lock(_LEDLock) 
     { 
      var cmd = "SET LED " + onOff.ToString() + "\r\n"; 
      commPort.WriteLine(cmd); 
      string status = commPort.ReadLine(); 
      return (status.Contains("SUCCESS")) ? true : false; 
     } 
    } 

    public Boolean IsLEDOn() 
    { 
     lock(_LEDLock) 
     { 
      commPort.Write("GET LED\r\n"); 
      var result = commPort.ReadLine().Contains("ON")) ? true : false; 
      return result; 
     } 
    } 
} 

Теперь любой вызывающий поток может вызывать эти методы в потоке безопасный способ.

+0

См. Мое редактирование на вопрос о том, как использовать закрытый объект для блокировки. – KDecker

+0

Я думаю, что это ничего не меняет. Закрытое поле точно функционально эквивалентно самому экземпляру блокировки. Не используя сам экземпляр, вы не только избегаете себя создателем тупика, как вы упомянули, но и избегаете других. Что касается особенностей порядка чтения и записи, то это просто: все, что вам нужно выполнить последовательно, находится внутри одного оператора блокировки. Если ваш вариант использования таков, что это невозможно, что не похоже на него, то вы смотрите на более крупный рабочий процесс управления асинхронизацией. – ibgib

+0

Кроме того, я бы обязательно инициализировал ваш синглтон до того, как возникнет многопоточность, иначе вы могли бы создать два потока для создания самого ресурса. То есть не имеют первого вызова из (фонового) потока1 или потока2. – ibgib

0

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

Поскольку у вас уже есть частный объект в вашем одноплодной Wich является экземпляром SerialPort, перепроектировать свой класс, как мой пример ниже:

public sealed class Singleton 
{ 
    private static readonly Lazy<Singleton> lazy = 
     new Lazy<Singleton>(() => new Singleton()); 

    public static Singleton Instance { get { return lazy.Value; } } 

    private SerialPort commPort = new SerialPort(); 


    private Singleton() 
    { 
     // Setup SerialPort 
    } 

    public String Read() 
    { 
     lock (commPort) 
      return commPort.ReadLine(); 
    } 

    public void Write(String cmd) 
    { 
     lock (commPort) 
      commPort.WriteLine(cmd); 
    } 
} 

Из документации SerialPort, мы можем сделать вывод, что записи и чтения не являются поточно сейф: http://msdn.microsoft.com/en-us/library/system.io.ports.serialport.aspx. Итак, да, вы должны синхронизировать свои R/W с SerialPorts.

+0

Не было бы проблем, связанных с тем, что после того, как будет выпущен 'Write'' lock, появится еще один поток и 'lock' снова на' Write', прежде чем 'Read' будет заблокирован, в результате чего' SerialPort' будет «Написать» дважды? – KDecker

+0

Конечно, это может быть проблемой. В зависимости от его дизайна программного обеспечения, он приближается, используя aproach производителя/consummer и захватывает объект синхронизации, такой как ManualResetEvent или даже BlockingCollection. Что означает верхний код, что он обеспечит согласованность данных, которые будут записаны, чтобы ни один из двух потоков не повредил последовательный порт. Для него писать дважды, может быть, не проблема. –

0

Мое использование блокировки предотвращает это?

Да, поскольку только один поток за один раз может выполнять внутри этих двух методов. Это безопасно, и я не вижу проблемы с этим. Рассмотрим блокировку на частном объекте, созданном с помощью new object(). Это немного безопаснее, потому что он защищает от определенных ошибок.

Будет ли этот тип действий называться «транзакционным IO»?

Этот термин не известен. Что бы это ни значило, это не «транзакционный IO».

+0

См. Мое редактирование на вопрос о том, как использовать закрытый объект для блокировки. – KDecker

+0

@KDecker проблема, о которой вы говорите, не применяется, поскольку вы не блокируете чтение и запись. Вы блокируете уровень SetLEDStatus и IsLEDOn. Все операции корректно спариваются в любое время. – usr