golang并发编程-05-同步-03-原子操作(atomic包)
文章目录
- 1 载入
-
- 1.1 Load 的6个函数
- 1.2 使用示例
- 1.3 *unsafe.Pointer
- 2. 存储
-
- 2.1 Store的6个函数
- 2.2 使用示例
- 3. 增/减
-
- 3.1 Add的5个函数
- 3.2 使用示例(五丈原七星灯续命——原子加/减)
- 4. 比较并交换
-
- 4.1 Swap的5个函数
- 4.2 示例 (孙策之死——原子交换值)
- 5. 比较并交换
-
- 5.1 CompareAndSwap的6个函数
- 5.2 示例(妖人于吉——原子比较,伺机交换)
- 6. 一个并发的练习
Go语言提供的原子操作都是非侵入式的。它们由标准库代码包sync/atomic中的众多函数代表。
1 载入
1.1 Load 的6个函数
func LoadInt32(addr *int32) (val int32)func LoadInt64(addr *int64) (val int64)func LoadUint32(addr *uint32) (val uint32)func LoadUint64(addr *uint64) (val uint64)func LoadUintptr(addr *uintptr) (val uintptr)func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
1.2 使用示例
import ("fmt""sync/atomic")func main() {var a int32a = 5b := atomic.LoadInt32(&a)fmt.Println(b)}
1.3 *unsafe.Pointer
优势:unsafe.Pointer 是一个万能指针,可以在任何指针类型之间做转化
弊端:绕过了 Go 的类型安全机制,所以是不安全的操作。
func main() {i := 10var p *int = &ivar fp *float32 = (*float32)(unsafe.Pointer(p))*fp = *fp * 10fmt.Println(i) // 100}
2. 存储
2.1 Store的6个函数
func StoreInt32(addr *int32, val int32)func StoreInt64(addr *int64, val int64)func StoreUint32(addr *uint32, val uint32)func StoreUint64(addr *uint64, val uint64)func StoreUintptr(addr *uintptr, val uintptr)func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)
2.2 使用示例
为了方便说明,我们将a分为值
aInt
和指针aPi
两个变量。(当然你像上例中表示指针也没有问题)
func main() {var aInt int64fmt.Println("a 的初始值: ",aInt)aPi := &aIntatomic.StoreInt64(aPi,66)fmt.Println("存入新值后,a的值:",aInt)}
结果输出
a 的初始值: 0存入新值后,a的值: 66
3. 增/减
3.1 Add的5个函数
func AddInt32(addr *int32, delta int32) (new int32)func AddInt64(addr *int64, delta int64) (new int64)func AddUint32(addr *uint32, delta uint32) (new uint32)func AddUint64(addr *uint64, delta uint64) (new uint64)func AddUintptr(addr *uintptr, delta uintptr) (new uintptr)
3.2 使用示例(五丈原七星灯续命——原子加/减)
说明:五丈原,诸葛亮阳寿将尽。七星灯续命12年,魏延踢到七星灯。
func main() {fmt.Println("======== 进入五丈原 ========")var remainingAge int32remainingAge = 0fmt.Printf("【武侯】生命余额:%d 年\n",remainingAge)atomic.AddInt32(&remainingAge, +12)fmt.Printf("+++++七星灯续命成功++++\n【武侯】生命余额:%d 年\n",remainingAge)atomic.AddInt32(&remainingAge, -12)fmt.Printf("-----魏延踢翻七星灯----\n【武侯】生命余额:%d 年\n======= END 将星陨落 =====",remainingAge)}
输出
======== 进入五丈原 ========【武侯】生命余额:0 年+++++七星灯续命成功++++【武侯】生命余额:12 年-----魏延踢翻七星灯----【武侯】生命余额:0 年======= END 将星陨落 =====
4. 比较并交换
4.1 Swap的5个函数
func SwapInt64(addr *int64, new int64) (old int64)func SwapUint32(addr *uint32, new uint32) (old uint32)func SwapUint64(addr *uint64, new uint64) (old uint64)func SwapUintptr(addr *uintptr, new uintptr) (old uintptr)func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)
4.2 示例 (孙策之死——原子交换值)
说明:妖人于吉施法交换了孙策和于吉的剩余生命
func main() {var yuJiReAge int64 = 0var sunCeReAge int64 = 20fmt.Printf("【于吉】生命余额 %d 年\n【孙策】生命余额:%d 年\n",yuJiReAge,sunCeReAge)fmt.Println("====== 妖人施法了 ======")sunCeReAge = atomic.SwapInt64(&yuJiReAge,sunCeReAge)fmt.Printf("【于吉】生命余额 %d 年\n【孙策】生命余额:%d 年\n",yuJiReAge,sunCeReAge)}
输出
【于吉】生命余额 0 年【孙策】生命余额:20 年====== 妖人施法了 ======【于吉】生命余额 20 年【孙策】生命余额:0 年
5. 比较并交换
- 作用
比较old值,当old值为N的时候交换成新值。
5.1 CompareAndSwap的6个函数
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool)func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool)func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool)func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)
5.2 示例(妖人于吉——原子比较,伺机交换)
说明:妖人于吉法术提升:等待自己生命值降为零的时候,再获得孙策的阳寿。(注意,因为此函数不会再返回old值,因此他不能直接干掉孙策)
func main() {var sunCeReAge int64 = 30var yuJiReAge int64 = 3var i int64for i=1;i<100;i++{sunCeReAge -= 1yuJiReAge -= 1fmt.Printf("===== 第%d年====\n【于吉】生命余额 %d 年\n【孙策】生命余额:%d 年\n",i,yuJiReAge,sunCeReAge)time.Sleep(time.Second)if atomic.CompareAndSwapInt64(&yuJiReAge, 0, sunCeReAge) {fmt.Printf("+++++ 妖人法术被触发 +++++\n【于吉】生命余额 %d 年\n【孙策】生命余额:%d 年\n",yuJiReAge,sunCeReAge)break}}}
输出
===== 第1年====【于吉】生命余额 2 年【孙策】生命余额:29 年===== 第2年====【于吉】生命余额 1 年【孙策】生命余额:28 年===== 第3年====【于吉】生命余额 0 年【孙策】生命余额:27 年+++++ 妖人法术被触发 +++++【于吉】生命余额 27 年【孙策】生命余额:27 年
6. 一个并发的练习
gitlab获取库有很多页(假设我们不知道有多少个项目页不知道有多少页)
如果一个协程访问会很慢
设计一个函数获取gitlab所有项目数据。
func GetAllProjectsInfo() (results []GitlabProject,err error) {var page int32var lock sync.Mutexvar wg sync.WaitGroupwg.Add(20)for i := 0; i < 20; i++ {tmp := igo func(tmp int) {//fmt.Printf("go routine : %d starts\n",tmp)for {newPage := atomic.AddInt32(&page, 1)r, err := GetProjectsInfo(int(newPage))if err !=nil {log.Printf("线程 %d 获取 第 %d 页 数据失败,错误:%+v\n",tmp,newPage,err)continue}if len(r) == 0 {break}else {lock.Lock()results = append(results, r...)lock.Unlock()}}fmt.Printf("go routine : %d ends\n",tmp)wg.Done()}(tmp)}wg.Wait()fmt.Printf("result : %d \n",len(results))return}