J'ai implémenté des E/S asynchrones avec un rappel où je suis préoccupé par la concurrence. Je conteste à vous que puisque je travaille toujours avec le même fichier et le fichier OS physique E/S est fondamentalement une opération synchrone alors je n'aurai pas besoin d'un mécanisme de verrouillage dans ma méthode de rappel - mais je peux très bien être mauvais ici - entrez SO: o) J'ai un gestionnaire de tampons qui met les données lues dans son cache tampon quand l'opération de lecture est terminée et un moteur d'état pour chaque opération superposée basée sur les états d'énumération EOverlappedStates; "E/S non démarré", "Réussite" et "Erreur". Pensez-vous que j'ai besoin d'un verrouillage dans la méthode de rappel pour assurer la simultanéité dans un programme multithread comme le nôtre?Concurrence de la méthode de rappel des E/S asynchrones
Ouvrir le fichier:
OS_FILE_HANDLE CUniformDiskInterface::OpenFile(const wchar_t *fileName, bool *fileExists, bool readData, bool writeData, bool overlap,
bool disableDiskCache, bool disableOsCache, bool randomAccess, bool sequentalScan) {
// Set access method
DWORD desiredAccess = readData ? GENERIC_READ : 0;
desiredAccess |= writeData ? GENERIC_WRITE : 0;
// Set file flags
DWORD fileFlags = disableDiskCache ? FILE_FLAG_WRITE_THROUGH : 0;
fileFlags |= disableOsCache ? FILE_FLAG_NO_BUFFERING : 0;
fileFlags |= randomAccess ? FILE_FLAG_RANDOM_ACCESS : 0;
fileFlags |= sequentalScan ? FILE_FLAG_SEQUENTIAL_SCAN : 0;
fileFlags |= !fileFlags ? FILE_ATTRIBUTE_NORMAL : 0;
fileFlags |= overlap ? FILE_FLAG_OVERLAPPED : 0;
HANDLE hOutputFile = CreateFile(
fileName,
desiredAccess,
0,
NULL,
OPEN_EXISTING,
fileFlags,
NULL);
fichier Lire:
_UINT64 CUniformDiskInterface::ReadFromFile(OS_FILE_HANDLE hFile, void *outData, _UINT64 bytesToRead, OVERLAPPED *overlapped, LPOVERLAPPED_COMPLETION_ROUTINE completionRoutine) {
DWORD wBytesRead = 0;
BOOL result = completionRoutine ?
ReadFileEx(hFile, outData, (DWORD)(bytesToRead), overlapped, completionRoutine) :
ReadFile(hFile, outData, (DWORD)(bytesToRead), &wBytesRead, overlapped);
if (!result)
{
int errorCode = GetLastError();
if (errorCode != ERROR_IO_PENDING)
{
wstringstream err(wstringstream::in | wstringstream::out);
err << L"Can't read sectors from file. [ReadFile] error #" << errorCode << L".";
throw new FileIOException(L"CUniformDiskInterface", L"ReadFromFile", err.str().c_str(), GETDATE, GETFILE, GETLINE);
}
}
return (_UINT64)wBytesRead; }
Structure chevauchée étendue:
/*!
\enum EOverlappedStates
\brief The different overlapped states
\details Used as inter-thread communication while waiting for the I/O operation to complete
*/
enum EOverlappedStates
{
/** The I/O operation has not started or in in-progress */
EOverlappedNotStarted,
/** The I/O operation is done and was successful */
EOverlappedSuccess,
/** The I/O operation is done but there was an error */
EOverlappedError
};
/*!
\struct OverlappedEx
\brief Extended overlapped structure
*/
struct OverlappedEx : OVERLAPPED
{
/** The buffer manager that is designated to cache the record when it's loaded */
CBufferManager *bufferManger;
/** Transaction ID related to this disk I/O operation */
_UINT64 transactionId;
/** Start disk sector of the record */
_UINT64 startDiskSector;
/** Buffer */
void *buffer;
/** Number of bytes in \c buffer */
_UINT64 bufferSize;
/** Current overlapped I/O state. Used for inter-thread communication while waiting for the I/O to complete */
EOverlappedStates state;
/** Error code, or \c 0 if no error */
_UINT32 errorCode;
};
méthode de rappel:
/*! \brief Callback routine after a overlapped read has completed
\details Fills the buffer managers buffer cache with the read data
\todo This callback method may be a bottleneck, so look into how to handle this better
*/
VOID WINAPI CompletedReadRoutine(DWORD dwErr, DWORD cbBytesRead, LPOVERLAPPED lpOverLap)
{
OverlappedEx *overlapped = (OverlappedEx*)lpOverLap;
overlapped->errorCode = (_UINT32)dwErr;
if (!dwErr && cbBytesRead)
{
overlapped->state = EOverlappedSuccess;
overlapped->bufferManger->AddBuffer(overlapped->startDiskSector, overlapped->buffer, overlapped->bufferSize);
}
else
{
// An error occurred
overlapped->state = EOverlappedError;
}
}
Utilisation:
_UINT64 startDiskSector = location/sectorByteSize;
void *buffer = bufferManager->GetBuffer(startDiskSector);
if (!buffer)
{
/*
The disk sector was not cached, so get the data from the disk and cache in internal memory with
the buffer manager
*/
buffer = new char[recordByteSize];
// Create a overlapped structure to enable disk async I/O operations
OverlappedEx *overlapped = new OverlappedEx;
memset(overlapped, 0, sizeof(OverlappedEx));
overlapped->Offset = (DWORD)(startDiskSector & 0xffffffffULL);
overlapped->OffsetHigh = (DWORD)(startDiskSector >> 31ULL);
overlapped->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
overlapped->bufferManger = bufferManager;
overlapped->startDiskSector = startDiskSector;
overlapped->buffer = buffer;
overlapped->bufferSize = recordByteSize;
overlapped->state = EOverlappedNotStarted;
// Read from disk
diskApi.ReadFromFile(fileHandle, buffer, sectorByteSize, overlapped, CompletedReadRoutine);
return overlapped;
}
Je suis conscient que les E/S asynchrones ne sont pas sur le même thread - mon code fonctionne bien et je ne cherche que des commentaires sur la question de la concurrence. Peut-être que je n'étais pas clair à ce sujet, mais c'est "peut-être pas"/"n'est pas" le même thread que les contrôles ultérieurs chevauchement-> state == EOverlappedNotStarted qui signifie encore une fois l'opération d'E/S est terminée. –
Si plusieurs threads accèdent aux mêmes données, vous avez besoin de verrous ou de types/opérations atomiques sur les données pour garantir leur intégrité. –
Eh bien, en général, c'est correct, Bart. Mais comme je l'ai écrit je soutiens que puisque ** physique ** I/O est fondamentalement synchrone de l'OS en accédant au même fichier que je n'ai pas besoin et "supplémentaire" de verrouillage puisque l'OS a déjà "pris soin de cela" par son syncronious la nature. –