Search code examples
goconcurrencylinked-listmutexgoroutine

Data race with list.List concurrent access with mutexes


I'm getting a data race and I can't quite figure out why. Running my tests with the -race command I've narrowed it down to trying to access a list.List while reading from it, but my Mutexes don't seem to do anything.

I have a number of *list.Lists inside of an array like so:

type MyList struct {
    mutex sync.Mutex
    *list.List
}

type SomeObj struct {
    data string
}

var myListOfLists [10]MyList

I'm reading and writing from the list like so:

list := myListOfLists[someIndex]
list.mutex.Lock()
for e := list.Front(); e != nil; e = e.Next() {
        if (...) {
            list.MoveToFront(e)
        }
}
list.mutex.Unlock()

and in another goroutine also trying to read and build a full list to return

var fullCopy []*SomeObj
list := myListOfLists[someIndex]

list.mutex.Lock()
for e := list.Front(); e != nil; e = e.Next() {
        fullCopy = append(fullCopy, e.Value.(SomeObj))
}
list.mutex.Unlock()

Solution

  • The statement list := myListOfLists[someIndex] copies the array element to variable list. This copies the mutex, thus preventing the mutex from working. The go vet command reports this problem.

    You can avoid the copy by using a pointer to the array element:

    list := &myListOfLists[someIndex]
    

    Another approach is to use an array of pointers to MyList. While you are at it, you might as well use a list value instead a list pointer in MyList:

    type MyList struct {
        mutex sync.Mutex
        list.List
    }
    
    var myListOfLists [10]*MyList
    for i := range myListOfLists {
       myListOfLists[i] = &MyList{}
    }