简体   繁体   中英

Why should Next() operation of a singly linked list be protected with a critical section?

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->next performs two operations; it first loads the struct pointed to by node into memory, then looks at the node+offsetof(next) to find the pointer next , load that into a register, and then return to the calling program. The contents of node may be manipulated by another thread of execution in the meantime.

  1. Yes, you absolutely need to protect your "next" with a lock in a multithreaded application.

... HOWEVER ...

  1. "Writers" (like add or remove node) MUST be mutually exclusive. Critical section is a good choice.

  2. "Readers" (like "next") can run concurrently with each other.

SUGGESTION:

If you can target Windows Vista or higher, consider using an SRW lock instead:

While I think the answer by sarnold is correct, I just wanted to point out that the sizeof() call in your malloc() call in CreateList appears to have a bug. I believe it should be:

List *pList = malloc(sizeof(List));

The way you had it, you will create enough memory to hold a pointer to a List and not the List structure. (You may also want to cast the return type to (List*) and compare it to NULL before using it..)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM