“不可变”字节数组或对象锁?

时间:2015-08-05 01:34:13

标签: c# arrays multithreading immutability

我正在开发一个多线程modbus服务器,我需要管理一个字节块供客户端读取。每个modbus设备都有一个线程来更新它们各自的字节数组部分,所以我需要实现某种形式的锁定。

初始化应用程序时,设备数量和分配给每个设备的内存字节数将保持不变。我做了一些研究,似乎可以安全地锁定一个多维数组,其中包含一个ReaderWriterLockSlim对象数组,用作锁:

    private static ReaderWriterLockSlim[] memLock;
    private static byte[][] memoryBlock;
    ...
    //Modify or read memoryBlock corresponding to deviceIndex as needed using:
    memLock[deviceIndex].EnterWriteLock();
        try
        {
            // Write to array of bytes from memoryBlock
            memoryBlock[2][3] = 0x05;
        }
        finally
        {
            memLock[deviceIndex].ExitWriteLock();
        }

        memLock[deviceIndex].EnterReadLock();
        try
        {
            // Read array of bytes from memoryBlock
        }
        finally
        {
            memLock[deviceIndex].ExitReadLock();
        }

我没有写过很多多线程应用程序,而且我最近才“发现”了不变性的概念。以下是将上述内容转换为不可变类的假设,假设设备列表和内存大小在内存Initialize之后永不改变。类为static的原因是因为在我的应用程序中只有该类的一个实例:

    private static class DeviceMemory
    {
        private static bool initialized_ = false;
        private static int memSizeInBytes_;
        private static List<byte[]> registers_;
        public static void Initialize(int deviceCount, int memSizeInBytes)
        {
            if (initialized_) throw new Exception("DeviceMemory already initialized");
            if (memSizeInBytes <= 0) throw new Exception("Invalid memory size in bytes");
            memSizeInBytes_ = memSizeInBytes;
            registers_ = new List<byte[]>();
            for(int i=0; i<deviceCount;++i)
            {
                byte[] scannerRegs = new byte[memSizeInBytes];
                registers_.Add(scannerRegs);
            }
            initialized_ = true;
        }
        public static byte[] GetBytes(int deviceIndex)
        {
            if (initialized_) return registers_[deviceIndex];
            else return null;
        }
        public static void UpdateBytes(int deviceIndex, byte[] memRegisters)
        {
            if (!initialized_) throw new Exception("Memory has not been initialized");
            if (memRegisters.Length != memSizeInBytes_)
                throw new Exception("Memory register size does not match the defined memory size in bytes: " + memSizeInBytes_);
            registers_[deviceIndex] = memRegisters;
        }
    }

以下是关于上述内容的问题:

  1. 我是否纠正我可以锁定一行二维数组,如上所示?
  2. 我是否正确实施了不可变类?即,您是否发现DeviceMemory类有任何问题会阻止我从设备线程写入UpdateBytes方法并同时从不同线程上的多个客户端读取?
  3. 这个不可变类是否比传统的多维字节数组/锁更明智?具体来说,我担心内存使用/垃圾收集,因为对字节数组的更新实际上是“新”字节数组,它取代了对旧数组的引用。但是,应该在客户端读取之后立即释放对旧数组的引用。每秒钟会有大约35个设备更新,每个设备有4个客户端以大约1秒的间隔读取。
  4. 一旦解决方案或其他解决方案表现更好,特别是如果服务器要扩展?
  5. 感谢您阅读!

1 个答案:

答案 0 :(得分:1)

首先,您在第二个代码示例中显示的不是“不可变类”,而是“静态类”。两个非常不同的东西;您需要检查自己的术语,以确保有效沟通,并在研究与之相关的技术时不要混淆。

关于你的问题:

  

1.我是否纠正我可以锁定一行二维数组,如上所示?

您应该使用ReaderWriterLockSlim代替,并且如果发生超时异常,则没有显示任何捕获超时异常的机制。但是,否则,是的......您可以对byte[]对象的每个byte[][]元素使用单独的锁。

更一般地说,您可以使用锁来表示您想要的任何数据单元或其他资源。锁对象无关紧。

  

2.我是否正确实现了一个不可变类?即,您是否看到DeviceMemory类有任何问题会阻止我从设备线程写入UpdateBytes方法并同时从不同线程上的多个客户端读取?

如果你的意思是“一成不变”,那就没有。如果你真的是指“静态”,那么是的,但我不清楚知道这是有用的。这个类不是线程安全的,这似乎是你更关心的问题,所以在这方面你还没有做好。

UpdateBytes()方法本身而言,它本身没有任何问题。实际上,由于将引用从一个变量复制到另一个变量是.NET中的原子操作,UpdateBytes()方法可以“安全地”更新数组元素,同时其他一些线程正在尝试检索它。 “安全地”,读者不会得到损坏的数据。

但是,班级中没有任何内容可确保Initialize()仅被调用一次。此外,没有同步(锁定或标记变量volatile),您无法保证在另一个线程中将会观察到在一个线程中写入的值。这包括所有字段以及单个byte[]数组元素。

  

3.对于更传统的多维字节数组/锁定,这个不可变类是一个明智的选择吗?具体来说,我担心内存使用/垃圾收集,因为对字节数组的更新实际上是“新”字节数组,它取代了对旧数组的引用。但是,应该在客户端读取之后立即释放对旧数组的引用。每秒会有大约35个设备更新,每个设备有4个客户端以大约1秒的间隔读取。

在您的问题中没有足够的上下文进行比较,因为我们不知道您将如何访问memoryBlock数组。如果你只是将新数组复制到数组中,那么两者应该是相似的。即使只是在第二个例子中你正在创建新的数组,然后假设数组不大,我希望每秒生成约100个新对象,以便完全在垃圾收集器的带宽内。

至于你的第二个代码示例中的方法是否是一种理想的方法,我将充分尊重建议您应该坚持使用传统的同步代码(即使用ReaderWriterLockSlim,或者甚至只是简单的lock声明)。并发很难通过常规锁定,从不介意尝试编写无锁代码。鉴于您所描述的更新率,我希望普通lock语句可以正常工作。

如果遇到一些带宽问题,那么至少你有一个已知良好的实现来比较新的实现,并且会更好地了解你真正需要的实现有多复杂。

4.一旦解决方案或其他解决方案表现更好,特别是如果服务器要扩展?

没有更多细节,不可能说。