Ответ
Для реализации потокобезопасного (thread-safe) связанного списка в Go необходимо защитить все операции чтения и записи с помощью примитивов синхронизации, таких как sync.Mutex
или sync.RWMutex
.
Самый простой и надежный подход — использовать блокировку на уровне всей структуры (coarse-grained locking). Это означает, что любая операция блокирует весь список.
Пример с sync.RWMutex
:
RWMutex
предпочтительнее, если предполагается, что операций чтения будет значительно больше, чем операций записи. Он позволяет множеству горутин одновременно читать список.
import "sync"
// Node - узел списка
type Node struct {
Value int
Next *Node
}
// SafeLinkedList - потокобезопасный связанный список
type SafeLinkedList struct {
head *Node
mu sync.RWMutex // Используем RWMutex для оптимизации чтений
}
// Insert добавляет элемент в начало списка (операция записи)
func (s *SafeLinkedList) Insert(value int) {
s.mu.Lock() // Эксклюзивная блокировка для записи
defer s.mu.Unlock()
newNode := &Node{Value: value, Next: s.head}
s.head = newNode
}
// Search ищет элемент в списке (операция чтения)
func (s *SafeLinkedList) Search(value int) bool {
s.mu.RLock() // Блокировка на чтение
defer s.mu.RUnlock()
current := s.head
for current != nil {
if current.Value == value {
return true
}
current = current.Next
}
return false
}
Ключевые моменты:
- Тип блокировки: Используйте
s.mu.Lock()
/s.mu.Unlock()
для операций, изменяющих структуру списка (добавление, удаление), иs.mu.RLock()
/s.mu.RUnlock()
для операций только для чтения (поиск, подсчет длины). - Длительность блокировки: Блокировка должна удерживаться на протяжении всей операции. Например, при поиске мьютекс блокируется перед началом обхода и разблокируется после его завершения. Попытка блокировать и разблокировать каждый узел отдельно (fine-grained locking) значительно сложнее в реализации и чревата ошибками (race conditions, deadlocks).