我正在开发一个多线程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;
}
}
以下是关于上述内容的问题:
DeviceMemory
类有任何问题会阻止我从设备线程写入UpdateBytes
方法并同时从不同线程上的多个客户端读取?感谢您阅读!
答案 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.一旦解决方案或其他解决方案表现更好,特别是如果服务器要扩展?
没有更多细节,不可能说。