zoukankan      html  css  js  c++  java
  • 用CAS操作实现Go标准库中的Once

    Go标准库中提供了Sync.Once来实现“只执行一次”的功能。学习了一下源代码,里面用的是经典的双重检查的模式:

    // Once is an object that will perform exactly one action.
    type Once struct {
    	m    Mutex
    	done uint32
    }
    
    func (o *Once) Do(f func()) {
    	if atomic.LoadUint32(&o.done) == 1 {
    		return
    	}
    	// Slow-path.
    	o.m.Lock()
    	defer o.m.Unlock()
    	if o.done == 0 {
    		f()
    		atomic.StoreUint32(&o.done, 1)
    	}
    }
    

    我觉得这里也可以用Go的原子操作来实现“只执行一次”,代码更简单,而且比起用Mutex的开销要更小一些:

    type Once struct {
    	done int32
    }
    
    func (o *Once) Do(f func()) {
    	if atomic.LoadInt32(&o.done) == 1 {
    		return
    	}
    	// Slow-path.
    	if atomic.CompareAndSwapInt32(&o.done, 0, 1) {
    		f()
    	}
    }
    

    熟悉Java内存模型的程序员可能会留意到上面的CAS操作中传递的是变量地址。如果在Java中,这样的变量是需要volatile来保证线程之间的可见性的,而Golang并没有volatile,Golang的内存模型的happen-after规则也没有提到atomic操作。但从Golang标准库的相关源码来看,Golang的atomic操作应该是可以满足可见性要求的。

    从下面的测试上看,这样实现没有什么并发的bug。

    	runtime.GOMAXPROCS(1000)
    	n := 100000
    	wg := new(sync.WaitGroup)
    	wg.Add(n)
    	
    	a := int32(0)
    	for i := 0; i < n; i++{
    		go func(){
    			if atomic.CompareAndSwapInt32(&a, 0, 1) {
    				fmt.Println("Change a to 1")
    			}
    			wg.Done()
    		}()
    	}
    	wg.Wait()
    
    	fmt.Println("Test is done")
    

    Go的内存模型文档中对atomic包并未提起,如果参考Java的来看

  • 相关阅读:
    [Writeup]奇怪的单点音
    [Writeup]百度一下,你就知道
    C语言学习一个月后感想
    小黄衫获得感想及经验总结
    Ubuntu初始化配置
    在Ubuntu 18.04中安装 docker compose
    Docker安装 配置
    Laravel Heroku评价
    Laravel Deploy to Heroku
    Laravel Nuxt auth refresh
  • 原文地址:https://www.cnblogs.com/liaofan/p/4010436.html
Copyright © 2011-2022 走看看