C#中拆分MemoryMappedFile为两个数据块时的写入异常问题
内存映射文件双Accessor读写冲突问题的原因与解决方法
问题现象
创建两个MemoryMappedViewAccessor分别访问共享内存的不同区域,控制台输出的指针虚拟地址不同,但读写操作时两个区块的数据互相覆盖,实际指向同一内存区域。
问题根源
Windows系统中,内存映射文件的视图是以内存页为单位分配的,默认页大小为4KB(可通过Environment.SystemPageSize获取):
- 调用
CreateViewAccessor时,若指定的起始偏移量未对齐到页边界,系统会自动将其调整至最近的页起始位置; - 若视图大小不足一页,系统会分配一整页的内存空间。
你的两个区块总大小仅30字节,远小于单页内存容量,因此两个MemoryMappedViewAccessor实际映射的是同一个物理内存页。虽然AcquirePointer返回的虚拟地址看似不同,但这些虚拟地址最终都指向该物理页的起始位置,导致读写操作互相覆盖。
解决方案
方案1:手动对齐偏移量到页边界
计算每个区块的起始偏移时,确保其为页大小的整数倍,同时预留足够的共享内存空间:
using System.IO.MemoryMappedFiles; int pageSize = Environment.SystemPageSize; const int block1Size = 20; const int block2Size = 10; // 对齐偏移量:block1从页起始位置开始,block2从下一个页起始位置开始 long block1Offset = 0; long block2Offset = pageSize; // 创建足够容纳两个对齐后区块的共享内存 MemoryMappedFile sharedFile = MemoryMappedFile.CreateNew("dummy_host_name", block2Offset + block2Size); MemoryMappedViewAccessor block1Accessor = sharedFile.CreateViewAccessor(block1Offset, block1Size); MemoryMappedViewAccessor block2Accessor = sharedFile.CreateViewAccessor(block2Offset, block2Size); // 后续测试逻辑保持不变
方案2:使用Accessor内置读写方法(推荐)
MemoryMappedViewAccessor提供了WriteArray/ReadArray等内置方法,这些方法会自动处理偏移量,无需手动管理指针,从根源上避免页对齐问题:
using System.IO.MemoryMappedFiles; using System.Linq; const int block1Size = 20; const int block2Size = 10; MemoryMappedFile sharedFile = MemoryMappedFile.CreateNew("dummy_host_name", block1Size + block2Size); MemoryMappedViewAccessor block1Accessor = sharedFile.CreateViewAccessor(0, block1Size); MemoryMappedViewAccessor block2Accessor = sharedFile.CreateViewAccessor(block1Size, block2Size); // 清除初始数据 byte[] zeros = new byte[block1Size]; block1Accessor.WriteArray(0, zeros, 0, block1Size); zeros = new byte[block2Size]; block2Accessor.WriteArray(0, zeros, 0, block2Size); PrintData(block1Accessor, block2Accessor); // 写入第一个区块数据 byte[] firstDataInput = Enumerable.Range(1, block1Size).Select(i => (byte)i).ToArray(); block1Accessor.WriteArray(0, firstDataInput, 0, block1Size); PrintData(block1Accessor, block2Accessor); // 写入第二个区块数据 byte[] secondDataInput = Enumerable.Range(block1Size + 1, block2Size).Select(i => (byte)i).ToArray(); block2Accessor.WriteArray(0, secondDataInput, 0, block2Size); PrintData(block1Accessor, block2Accessor); static void PrintData(MemoryMappedViewAccessor block1, MemoryMappedViewAccessor block2) { byte[] block1Data = new byte[block1Size]; block1.ReadArray(0, block1Data, 0, block1Size); byte[] block2Data = new byte[block2Size]; block2.ReadArray(0, block2Data, 0, block2Size); Console.WriteLine("first block: " + string.Join(", ", block1Data)); Console.WriteLine("second block: " + string.Join(", ", block2Data)); }
内容的提问来源于stack exchange,提问作者dcidral
相关产品推荐
相关产品推荐

