atomic实现一种基于自旋的读写锁
Go语言中的atomic包提供了原子操作的相关函数,可以在不使用锁的情况下,实现多个goroutine之间的数据同步,以避免出现竞争条件和锁竞争的问题。
除了一些基本的原子操作函数(如AddInt32、CompareAndSwapInt32等),atomic包中还提供了一些高级的原子操作函数,如LoadPointer、StorePointer等,可用于实现一些高级的功能,例如自旋锁。
自旋锁是一种基于忙等待的锁,即在竞争激烈的情况下,每个线程都会反复检查锁是否被释放。在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