Maison > Article > développement back-end > Implémentation d'une file d'attente illimitée sans verrouillage à l'aide du nouveau type atomique.Pointer
L'éditeur de PHP, Strawberry, vous présentera aujourd'hui une nouvelle technologie - utilisant le nouveau type atomique.Pointer pour implémenter une file d'attente sans verrouillage et illimitée. En programmation simultanée, les files d'attente constituent une structure de données courante, mais les implémentations de files d'attente traditionnelles nécessitent généralement l'utilisation de verrous pour garantir la sécurité des threads, ce qui entraînera des pertes de performances. Le nouveau type atomic.Pointer fournit une solution sans verrouillage qui peut réaliser des opérations de file d'attente simultanées efficaces. Ci-dessous, nous présenterons en détail cette nouvelle implémentation, ainsi que ses avantages et comment l'utiliser.
J'essaie d'implémenter cette file d'attente non bloquante de Michael et Scott.
J'essaie d'utiliser le nouveau type atomic.pointer introduit dans go 1.19, mais je reçois des courses de données dans mon application.
Voici ma mise en œuvre :
package queue import ( "errors" "sync/atomic" ) // LockfreeQueue represents a FIFO structure with operations to enqueue // and dequeue generic values. // Reference: https://www.cs.rochester.edu/research/synchronization/pseudocode/queues.html type LockFreeQueue[T any] struct { head atomic.Pointer[node[T]] tail atomic.Pointer[node[T]] } // node represents a node in the queue type node[T any] struct { value T next atomic.Pointer[node[T]] } // newNode creates and initializes a node func newNode[T any](v T) *node[T] { return &node[T]{value: v} } // NewQueue creates and initializes a LockFreeQueue func NewLockFreeQueue[T any]() *LockFreeQueue[T] { var head atomic.Pointer[node[T]] var tail atomic.Pointer[node[T]] n := &node[T]{} head.Store(n) tail.Store(n) return &LockFreeQueue[T]{ head: head, tail: tail, } } // Enqueue adds a series of Request to the queue func (q *LockFreeQueue[T]) Enqueue(v T) { n := newNode(v) for { tail := q.tail.Load() next := tail.next.Load() if tail == q.tail.Load() { if next == nil { if tail.next.CompareAndSwap(next, n) { q.tail.CompareAndSwap(tail, n) return } } else { q.tail.CompareAndSwap(tail, next) } } } } // Dequeue removes a Request from the queue func (q *LockFreeQueue[T]) Dequeue() (T, error) { for { head := q.head.Load() tail := q.tail.Load() next := head.next.Load() if head == q.head.Load() { if head == tail { if next == nil { return head.value, errors.New("queue is empty") } q.tail.CompareAndSwap(tail, next) } else { v := next.value if q.head.CompareAndSwap(head, next) { return v, nil } } } } } // Check if the queue is empty. func (q *LockFreeQueue[T]) IsEmpty() bool { return q.head.Load() == q.tail.Load() }
J'ai trouvé ici une implémentation différente qui fonctionne dans mon application sans courses de données, mais je n'arrive pas à comprendre quelle est exactement la différence entre les deux.
Merci pour toute aide ou commentaire !
Il s'avère que changer quelques éléments peut résoudre le problème.
Premier changement :
var n = node[t]{} head.store(&n) tail.store(&n)
Le deuxième changement consiste à changer la signature de retour dequeue()
.
Le fichier final ressemble à ceci :
package queue import ( "sync/atomic" ) // LockfreeQueue represents a FIFO structure with operations to enqueue // and dequeue generic values. // Reference: https://www.cs.rochester.edu/research/synchronization/pseudocode/queues.html type LockFreeQueue[T any] struct { head atomic.Pointer[node[T]] tail atomic.Pointer[node[T]] } // node represents a node in the queue type node[T any] struct { value T next atomic.Pointer[node[T]] } // newNode creates and initializes a node func newNode[T any](v T) *node[T] { return &node[T]{value: v} } // NewQueue creates and initializes a LockFreeQueue func NewLockFreeQueue[T any]() *LockFreeQueue[T] { var head atomic.Pointer[node[T]] var tail atomic.Pointer[node[T]] var n = node[T]{} head.Store(&n) tail.Store(&n) return &LockFreeQueue[T]{ head: head, tail: tail, } } // Enqueue adds a series of Request to the queue func (q *LockFreeQueue[T]) Enqueue(v T) { n := newNode(v) for { tail := q.tail.Load() next := tail.next.Load() if tail == q.tail.Load() { if next == nil { if tail.next.CompareAndSwap(next, n) { q.tail.CompareAndSwap(tail, n) return } } else { q.tail.CompareAndSwap(tail, next) } } } } // Dequeue removes a Request from the queue func (q *LockFreeQueue[T]) Dequeue() T { var t T for { head := q.head.Load() tail := q.tail.Load() next := head.next.Load() if head == q.head.Load() { if head == tail { if next == nil { return t } q.tail.CompareAndSwap(tail, next) } else { v := next.value if q.head.CompareAndSwap(head, next) { return v } } } } } // Check if the queue is empty. func (q *LockFreeQueue[T]) IsEmpty() bool { return q.head.Load() == q.tail.Load() }
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!