Go 并发安全的有序单链表实现

本文探讨了在Go中实现并发安全有序单链表的方法,通过使用原子操作和锁来防止数据竞争。文章指出,对于读多写少的情况,可以使用读写锁;而在并发写操作中,通过细化锁的粒度到节点级别,以充分利用多核CPU的能力。文中以并发插入操作为例,详细解释了如何确保链表操作的正确性和性能,并提供了相关代码实现。
摘要由CSDN通过智能技术生成

多个goroutine访问同一个变量,需要考虑Data Race的问题。可以使用下面的命令来进行检测:

go test -race  或者 go run -race

解决方案:使用atomic库或者sync.Mutex,对访问顺序进行限制。

  1. atomic,一般在单个变量一写多读的场景使用, Compare And Swap虽然可以用于多写多读,但是应用乐观锁,效率不高
  2. sync.Mutex,一般在多个变量的多写多读场景使用

针对当前问题,实现并发安全的有序单链表,最简单的思路是,对给单链表加锁。如果是读操作远多于写操作,使用读写锁。
这种方式,可以解决Data Race的问题,但是只有并发读可以利用CPU多核能力,并发写操作时,只能使用一个CPU的能力。

如果想要使得并发写时,可以充分发挥多核CPU的能力,那么链表的写操作就需要更精细的处理。可以把锁的粒度,缩小到节点级别。

以并发插入为例:

在这里插入图片描述
如果想在A节点后边插入X节点,那么就需要新创建一个X节点,并把X的下一个节点指向B,再把A的下一个节点从指向B改为指向X。
在这个过程中,需要考虑的Data Race环节是:

  1. 如果一个节点在被修改,那么它不允许被其他goroutine读取或者写入。这个需要加锁保证
  2. 在A加锁之前,A.next指向的内容是可能会变更的,比如同时还有插入Y节点的操作,在A加锁之前,A.next实际就已经变为Y了,所以在A加锁之后,需要判定A.next是否依旧等于B,如果不再等于B了,那么需要重新查找A.next,以确保X.next的值无误
  3. 由于节点的写入操作加了锁,所以这是一个一写多读的场景,为了提升读性能,需要在这种场景下,使用原子读写

其他有序单链表的操作,可以参考插入操作的思路,最终代码实现如下:

// Package clist
package clist

import (
	"sync"
	"sync/atomic"
	"unsafe"
)

type IntList struct {
   
	head   *intNode
	length int64
}

type intNode struct {
   
	value  int
	next   *intNode
	mu     sync.Mutex
	marked int32
}

func newIntNode(value int) *intNode {
   
	return &intNode{
   value: value}
}

func NewInt() *IntList {
   
	return &IntList{
   head: newIntNode(-1)}
}

func (l *IntList) Insert(value int) bool {
   
	var a *intNode
	var b *intNode

	for {
   
		// Step 1, 寻找a b
		a = l.head
		b = a.atomicNext()

		
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值