I’m reading the book Multithreading Applications in Win32
The book says return node->next will be compiled into separate machine instructions that would not be executed as an atomic operation, so Next() should also be protected by the critical section.
My question is, what instructions could it be translated into, to cause a race condition?
typedef struct _Node
{
struct Node *next;
int data;
} Node;
typedef struct _List
{
Node *head;
CRITICAL SECTION critical_sec;
} List;
List *CreateList()
{
List *pList = malloc(sizeof(List));
pList->head = NULL;
InitializeCriticalSection(&pList->critical_sec);
return pList;
}
void DeleteList(List *pList)
{
DeleteCriticalSection(&pList->critical_sec);
free(pList);
}
void AddHead(List *pList, Node *node)
{
EnterCriticalSection(&pList->critical_sec);
node->next = pList->head;
pList->head = node;
LeaveCriticalSection(&pList->critical_sec);
}
void Insert(List *pList, Node *afterNode, Node *newNode)
{
EnterCriticalSection(&pList->critical_sec);
if (afterNode == NULL)
{
AddHead(pList, newNode);
}
else
{
newNode->next = afterNode->next;
afterNode->next = newNode;
}
LeaveCriticalSection(&pList->critical_sec);
}
Node *Next(List *pList, Node *node)
{
Node* next;
EnterCriticalSection(&pList->critical_sec);
next = node->next;
LeaveCriticalSection(&pList->critical_sec);
return next;
}
Edit:
OK, although in this particular case it won’t corrupt the singly linked list w/o protecting the Next() operation, a shared structure should be protected as a whole or nothing, generally.
return node->nextperforms two operations; it first loads thestructpointed to bynodeinto memory, then looks at thenode+offsetof(next)to find the pointernext, load that into a register, and then return to the calling program. The contents ofnodemay be manipulated by another thread of execution in the meantime.