潘达叔

V1

2022/05/05阅读:77主题:默认主题

Golang:初等同步-CAS

前言

        深入一门语言,增加自我价值
        学习遇到什么困难不要怕,记下笔记面对它,消除不懂最好的办法就是不放弃一点点积累
        不要过度追求完美,而要今日获得完成感

一、背景知识

问题描述

Golang中如果一个形如的var someCounter int64资源进行“线程安全地”操作。

英文赏析

例文1:
In computer science, compare-and-swap (CAS) is an atomic instruction used in multithreading to achieve synchronization.
来自 https://en.wikipedia.org/wiki/Compare-and-swap

例文2:
The atomicity guarantees that the new value is calculated based on up-to-date information; if the value had been updated by another thread in the meantime, the write would fail.
来自 https://en.wikipedia.org/wiki/Compare-and-swap

例文3:
The multiple threads of a given process may be executed concurrently (via multithreading capabilities), sharing resources such as memory, while different processes do not share these resources.
来自 https://en.wikipedia.org/wiki/Thread_(computing)#Multithreading

例文4:
Thread safety is a computer programming concept applicable to multi-threaded code. Thread-safe code only manipulates shared data structures in a manner that ensures that all threads behave properly and fulfill their design specifications without unintended interaction.
来自 https://en.wikipedia.org/wiki/Thread_safety

二、方案对比

Golang中可用于数据同步&并发控制的概念

  • 初等
    • 【今日介绍】CAS指令
    • 【后续介绍】互斥锁 Mutex、读写锁 RWMutex、并发编排 WaitGroup、条件变量 Cond、Channel等。
  • 高等
    • 【后续介绍】CSP原理

实验方案设计

  • 设计一个主函数 main() 启动const GoNums = 1000个go程对对资源var someCounter int64进行并发地(concurrently)操作。
  • 设计一个go程函数 increase() 使用sync.atomic包下的CAS命令,对资源p *int64进行计数,且一定重试达到预期的const Times = 10000次。
  • 打印资源someCounter验证程序的正确性
  • 打印每个go程的``raceCnt```观察数据竞争的次数。

三、结果与性能分析

原理分析

CAS要达到固定的次数,例如我们的例程中:1000个go程做1000次计数,目标someCounter要达到1e7次。
而这个过程中CAS失败并重试,是消耗CPU资源的。可以认为这种模式是消耗CPU为代价的。

运行结果

如图,正确性性符合预期。
go程间的竞争非常明显。

性能跑分

TODO

四、源码剖析

package main

import (
 "fmt"
 "sync/atomic"
)

const GoNums = 1000 // (1)
const Times = 10000

func main() {
 fmt.Println("<< The Synchronization With CAS but CSP >>")
 fmt.Println("<< 初等同步:CAS >>")
 //
 var someCounter int64 // (2) 资源定义
 raceCh := make(chan int// (3) A部启动
 for i := 0; i < GoNums; i++ {
   go increase(&someCounter, Times, raceCh)
 }
  
 for i := 0; i < GoNums; i++ { // (4) B部回收
  raceCnt := <-raceCh
  fmt.Printf("raceCnt[%d]:%d\n", i, raceCnt) // (5)
 }
 fmt.Println("someCounter:", someCounter) // (6)
}

func increase(p *int64, times int, raceCh chan<- int) { // (7)
 i, raceCnt := 00
 for i < times {
  if ok := atomic.CompareAndSwapInt64(p, *p, *p+1); ok {
   i++ // (8)
   continue
  }
  raceCnt++ // (9)
 }
 raceCh <- raceCnt // (10)
}
  • (1) 为我们的实验程序预留“调参”
  • (2) 本实验要操作的“资源”
  • (3) A部启动,接收通道定义+迭代 3行
  • (4) B部回收,迭代2行
  • (5) 输出我们CAS失败的次数,我暂时称CAS失败为发生了data race
  • (6) 输出资源的最终值,检查:是否符合预期
  • (7) go程的函数不应该有返回值,可用于等待完成(wait)的“接收通道”
  • (8) CAS成功才计数,保证业务逻辑
  • (9) 否则记一次data race的次数
  • (10) 当业务完成后向外“通信”,有人接收后,本go程结束

分类:

后端

标签:

后端

作者介绍

潘达叔
V1