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的来看

  • 相关阅读:
    SQL Server 百万级数据提高查询速度的方法(转)
    sql优化的几种方法
    MyBatis中调用存储过程和函数
    android ipc通信机制之二序列化接口和Binder
    APK的目录结构
    android Handler错误,不同的包Handler
    BaiduMap开发,获取公交站点信息。
    GitHub托管项目步骤
    Mysql,JDBC封装
    简单工厂模式练习
  • 原文地址:https://www.cnblogs.com/liaofan/p/4010436.html
Copyright © 2011-2022 走看看