潘达叔
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 := 0, 0
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程结束
作者介绍