atomic并发场景应用案例

atomic实现一种基于自旋的读写锁

Go语言中的atomic包提供了原子操作的相关函数,可以在不使用锁的情况下,实现多个goroutine之间的数据同步,以避免出现竞争条件和锁竞争的问题。

除了一些基本的原子操作函数(如AddInt32CompareAndSwapInt32等),atomic包中还提供了一些高级的原子操作函数,如LoadPointerStorePointer等,可用于实现一些高级的功能,例如自旋锁。

自旋锁是一种基于忙等待的锁,即在竞争激烈的情况下,每个线程都会反复检查锁是否被释放。在Go语言中,可以使用atomic包的CompareAndSwapInt32函数来实现一种基于自旋的读写锁。

具体实现如下:

type spinLock struct {
    lock int32
}

func (s *spinLock) Lock() {
    for !atomic.CompareAndSwapInt32(&s.lock, 0, 1) {
        // 自旋等待锁被释放
    }
}

func (s *spinLock) Unlock() {
    atomic.StoreInt32(&s.lock, 0)
}

在该实现中,spinLock结构体包含一个名为lock的int32类型字段,用于表示锁的状态。Lock方法中,使用CompareAndSwapInt32函数来比较当前锁的状态是否为0,如果是则将其设置为1,并返回true表示获取锁成功。如果当前锁的状态不为0,即表示锁已被占用,此时当前goroutine会一直循环等待,直到成功获取到锁为止。Unlock方法中,使用StoreInt32函数将锁的状态重新设置为0,表示释放锁。

需要注意的是,自旋锁适用于竞争激烈但持锁时间较短的情况下,例如对于一个临界区的读写操作,自旋锁可以大大减少上下文切换和锁竞争的开销,提高程序的性能。但如果锁被占用的时间过长,或者在锁被占用时进行一些繁重的计算操作,就会导致自旋锁的效率下降,因此需要根据实际情况进行选择

使用 atomic 实现单例模式

atomic 包中的 CompareAndSwapPointer 函数可以原子地比较并交换指针。可以利用这个函数来实现单例模式。

package singleton

import"sync/atomic"
var (
	instance   *Singleton
	initialized uint32
)

type Singleton struct {
}

func GetInstance() *Singleton {
	if atomic.LoadUint32(&initialized) == 1 {
		return instance
	}

	return getInstance()
}

func getInstance() *Singleton {
	if atomic.LoadUint32(&initialized) == 1 {
		return instance
	}

	value := &Singleton{}
	if !atomic.CompareAndSwapPointer(
		(*unsafe.Pointer)(unsafe.Pointer(&instance)),
		nil, unsafe.Pointer(value)) {
		return instance
	}
	atomic.StoreUint32(&initialized, 1)
	return instance
}

在上面的代码中,我们使用了 initialized 变量来标记 instance 是否已经被初始化过了。在第一次调用 GetInstance 函数时,会检查 initialized 是否为 1。如果是,则直接返回 instance。如果不是,则调用 getInstance 函数来创建实例。

getInstance 函数中,首先再次检查 initialized 是否为 1。如果是,则直接返回 instance。如果不是,则创建一个新的 Singleton 实例,并使用 CompareAndSwapPointer 函数来原子地将实例指针赋值给 instance 变量。在赋值成功后,将 initialized 标记为已初始化,并返回 instance。在多个 goroutine 并发调用 GetInstance 函数时,只有一个 goroutine 会创建实例,并且其他 goroutine 会返回已经创建好的实例。这样就实现了单例模式

atomic实现资源池

资源池是一种常用的优化技术,它维护了一组已经初始化好的资源,并在需要时分配这些资源,使用完毕后将其放回池中,而不是每次都重新创建资源。这种做法可以避免创建和销毁资源的开销,提高程序的性能。

下面我们来演示如何使用atomic包来实现一个简单的资源池。

首先,我们定义一个Pool结构体,其中包含一个缓冲区(即资源池),以及用于管理缓冲区状态的原子整数。在这个例子中,我们将缓冲区定义为字符串切片。

type Pool struct {
    buffer []string
    index  int64
}

接下来,我们定义一个NewPool函数,它可以创建一个新的资源池。在这个函数中,我们先初始化缓冲区,并将所有的元素设为默认值。然后,我们将index设置为-1,这表示缓冲区目前没有任何可用的资源。

func NewPool(size int) *Pool {
    p := &Pool{
        buffer: make([]string, size),
        index:  -1,
    }

    for i := 0; i < size; i++ {
        p.buffer[i] = "resource " + strconv.Itoa(i)
    }

    return p
}

接下来,我们定义一个Get方法,用于从资源池中获取一个资源。在这个方法中,我们首先使用atomic.AddInt64函数将index的值增加1,然后检查它是否超出了缓冲区的大小。如果超出了缓冲区的大小,我们将index的值减回1,并返回一个空字符串表示资源不可用。否则,我们返回对应位置的资源。

func (p *Pool) Get() string {
    i := atomic.AddInt64(&p.index, 1) - 1
    if i >= int64(len(p.buffer)) {
        atomic.AddInt64(&p.index, -1)
        return""
    }

    return p.buffer[i]
}

最后,我们定义一个Put方法,用于将一个资源放回资源池中。在这个方法中,我们将index的值减1,然后检查它是否小于0。如果小于0,说明没有资源被获取出去,我们将index的值增加1,并返回false表示资源不需要放回池中。否则,我们将资源放回缓冲区,并返回true表示资源已经成功放回池中。

func (p *Pool) Put(resource string) bool {
    i := atomic.AddInt64(&p.index, -1)

    if i < 0 {
        atomic.AddInt64(&p.index, 1)
        returnfalse
    }

    p.buffer[i] = resource 
return true

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值