为线程安全设备IO锁定单例类?

时间:2015-07-30 18:38:53

标签: c# multithreading singleton

假设我有可以读写Singleton的单例类SerialPort

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

在这种情况下,如果SetLEDStatusIsLEDOn被非常接近同一时间调用,我想确保SerialPort在读取之前不会写两次。我使用锁定会阻止吗?

此类操作会被称为“事务IO”吗?

如果这确实是正确的,还有其他更有效的方法来执行相同类型的操作吗?

修改

我理解为什么锁定Singleton.Instance可能会很糟糕,如果要锁定Singleton.Instance,然后调用Singleton.Instance中也试图锁定自身的方法,那么陷入僵局。

我原计划在单例中使用私有对象来锁定。但是由于下面概述的情况,我有点自言自语。其中,我不确定这是否正确。

(在Thread1和Thread2上运行上面两种方法(减少锁定)

  1. Thread1调用WriteSingleton.Instance锁定
  2. Thread2调用Write,但被锁
  3. 阻止
  4. Singleton.Instance完成Write并释放锁
  5. Thread2s调用Write执行,Singleton.Instance锁定
  6. Thread1调用Read,但被锁
  7. 阻止
  8. Singleton.Instance完成Write并释放锁
  9. Thread1s Read执行,Singleton.Instance锁定
  10. Thread2调用Read,但被锁
  11. 阻止
  12. Singleton.Instance完成Read并释放锁
  13. 执行了Thread2s ReadSingleton.Instance锁定
  14. Singleton.Instance完成Read并释放锁
  15. 在这种情况下,连续的串行端口有两个Writes是不合适的。我需要能够为某些类型的通信背靠背地进行Write Read

3 个答案:

答案 0 :(得分:2)

对于锁定对象,我会使用same reasoning on why not to lock(this) ever在类(即非静态)而不是单例实例本身上使用私有字段

我通常使用类似这样的声明,因为声明锁定对象作为自编代码更具可读性。

private readonly object _LEDLock = new object();

这样,当其他人去看时,他们说“哦,这是锁定对象,保护线程访问LED资源。”

恕我直言,我认为SetLEDStatusIsLEDOn方法(带锁定)中的行为会更好地封装在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;
        }
    }
} 

现在,任何调用线程都可以以线程安全的方式调用这些方法。

答案 1 :(得分:0)

您永远不应该锁定公共对象(在代码的使用者可以访问的对象上,或者在实例化它们的类型之外)。您始终应该只依赖于私有对象的锁定,这样您就可以确保没有人会发生意外锁定并陷入死锁状态。否则您的实现没问题,您必须只允许一个线程访问您的端口。

由于您的单例中已经有一个私有对象,而且是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。

答案 2 :(得分:0)

  

我使用锁定会阻止吗?

是的,因为一次只能有一个线程在这两个方法中执行。这是安全的,我认为没有问题。考虑锁定使用new object()创建的私有对象。这有点安全,因为它可以防止某些错误。

  

此类操作会被称为“事务IO”吗?

这个词并不为人所知。无论这意味着什么,这不是“交易IO”。