Извините, если это не относится непосредственно к вашей основной проблеме (неблокирующая атомарность), но я вижу, что вы манипулируете отображенными в память файлами, используя классы MemoryMappedFile
и MemoryMappedViewAccessor
.
Я действительно не знаю, обращались ли к этому текущие итерации .NET Framework, но в кодовой базе, которую мы написали около трех лет назад, мы обнаружили, что преобразование файлов в памяти с использованием этих классов предлагало действительно плохо Производительность (примерно в 7 раз медленнее, если я правильно помню) по сравнению с использованием Win32 API и манипулированием указателями в отображенной памяти даже внутри управляемого C ++ / CLI класса.
Я настоятельно рекомендую вам протестировать этот метод, вы можете быть удивлены приростом производительности (как мы, конечно, сделали), и, возможно, прирост производительности настолько значителен, что он позволяет вам позволить себе затраты на стандартную блокировку для достижения атомарность, которую вы желаете.
Если вы хотите изучить этот путь, вот фрагмент кода, который показывает основы техники.
Int32 StationHashStorage::Open() {
msclr::lock lock(_syncRoot);
if( _isOpen )
return 0;
String^ fileName = GetFullFileName();
_szInBytes = ComputeFileSizeInBytes(fileName);
String^ mapExtension = GetFileExtension();
String^ mapName = String::Format("{0}{1}_{2}", _stationId, _date.ToString("yyyyMMdd"), mapExtension);
marshal_context context;
LPCTSTR pMapName = context.marshal_as<const TCHAR*>(mapName);
{
msclr::lock lock( _openLock );
// Try to see if another storage instance has requested the same memory-mapped file and share it
_hMapping = OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, pMapName);
if( !_hMapping ) {
// This is the first instance acquiring the file
LPCTSTR pFileName = context.marshal_as<const TCHAR*>(fileName);
// Try to open the existing file, or create new one if not exists
_hFile = CreateFile(pFileName,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if( !_hFile )
throw gcnew IOException(String::Format(Strings::CreateFileFailed, GetLastError(), _stationId));
_hMapping = CreateFileMapping(_hFile,
NULL,
PAGE_READWRITE | SEC_COMMIT,
0,
_szInBytes,
pMapName);
if( !_hMapping )
throw gcnew IOException(String::Format(Strings::CreateMappingFailed, GetLastError(), _stationId));
_usingSharedFile = false;
} else {
_usingSharedFile = true;
}
}
// _pData gives you access to the entire requested memory range, you can directly
// dereference it, memcopy it, etc.
_pData = (UInt32*)::MapViewOfFile(_hMapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
if( !_pData )
throw gcnew IOException(String::Format(Strings::MapViewOfFileFailed, ::GetLastError(), _stationId));
// warm-up the view by touching every page
Int32 dummy = 0;
for( int i = 0; i < _szInBytes / sizeof(Int32); i+= 1024 ) {
dummy ^= _pData[i];
}
// return the dummy value to prevent the optimizer from removing the apparently useless loop
_isOpen = true;
return dummy;
}
void StationHashStorage::Cleanup() {
if( !_disposed ) {
// dispose unmanaged resources here
if( _pData ) {
if( !UnmapViewOfFile(_pData) )
LOG_ERROR(Strings::UnmapViewOfFileFailed, ::GetLastError(), _stationId);
_pData = NULL;
}
if( _hMapping ) {
if( !CloseHandle(_hMapping) )
LOG_ERROR(Strings::CloseMappingFailed, ::GetLastError(), _stationId);
_hMapping = NULL;
}
if( _hFile ) {
if( !CloseHandle(_hFile) )
LOG_ERROR(Strings::CloseFileFailed, ::GetLastError(), _stationId);
_hFile = NULL;
}
_disposed = true;
}
}
Теперь относительно вашего реального вопроса. Возможно ли, что вы внедрили сгенерированный идентификатор как часть потока данных?
Моя идея будет выглядеть примерно так:
Предварительно записать все содержимое вашей памяти с использованием фиктивного известного значения (возможно, 0xffffffff).
Используйте текущую атомную логику проверки емкости.
После написания полезной нагрузки сообщения, вы немедленно пишете вычисленный идентификатор сообщения (ваша проверка емкости должна учитывать эти дополнительные данные)
Вместо использования Interlocked.Add для получения следующего Id, вы должны ввести цикл, который проверяет память до текущего сообщения (предыдущего идентификатора сообщения), пока оно не станет отличным от вашего фиктивного известная ценность. После выхода из цикла текущим идентификатором сообщения будет считанное значение + 1.
Это потребует некоторых специальных манипуляций с первым вставленным сообщением (поскольку для этого нужно заполнить первый маркер Id в вашем потоке. Вам также нужно быть осторожным (если вы используете длинные идентификаторы и находитесь в 32-битном режиме) ) что ваш Id-поток читает и пишет атомарно.
Удачи, и я действительно призываю вас попробовать Win32 API, было бы очень интересно узнать, улучшились ли, надеюсь, вещи! Не стесняйтесь обращаться ко мне, если вам нужна помощь с кодом C ++ / CLI.