锁 和 CPU CAS 原子操作
提示:具体围绕着CPU CAS场景讲,代码写在最后
问题简介
问题1:
在我们写代码时定义了一个变量,给它赋值的同时
去查询该变量,会发现值不对.
string 有乱码
对象结构缺少部分数据
为什么会发生这种情况呢?
因为你在并发修改和读取时
会发生一部分字节并未完全赋值
所以读出来的数据只是一部分.
问题2:
当多线程时,为了控制并发安全,锁性能太慢咋办.
下面的描述部分都是以问题1为主
嫌锁性能太慢就不要用锁,因为它阻塞在那里,它也快不了。
换一种不阻塞的方式就好了.
问题举例
问题1:其实都一样,string、结构 操作系统在高低位进行赋值时
都有可能会存在没有赋完的情况,这个时候取出来都会有问题.
这种情况的发生是因为cpu本身是乱序执行的
类型的赋值(除了基本类型)都不是原子性,比如go、java
除非类型的本身有保证原子性的机制
疑惑
问题1:有些朋友估计还是比较疑惑的,为啥自己写了这么久的程序,
没有遇到过呢?因为大部分的操作是由固定变量+同步执行来完成的.
就算是多线程的异步操作,在获取数值时去改变数据,也不会那么容易重现
因为cpu的存储指令操作是特别快的,瞬间就完成了
虽然是极少数发生的情况,但是不可忽略,因为许多信息、数据对于企业
都是特别重要的,当真的发生问题时,再想去用补偿机制挽回,
往往不会那么容易.
解决方案
可能朋友最先想到的就是锁了,锁肯定是能解决,但性能低效,
还有可能会发生死锁等问题.
这里推荐的就是本篇所要说的CPU CAS操作
CPU cas
分为两种模式:总线锁定、缓存锁定
总线锁定:
cpu在下达指令操作时,都会由一个"总线"来操作,
当我们的具体操作由总线来实现时,就能实现其他操作
无法处理这个共享变量,但这样会增大性能负担,
因为其他请求都阻塞到了
缓存锁定:
现在处理器差不多都提供缓存锁定机制了,
它的机制是在你修改共享变量时,会预测你修改得值
有没有被修改过,如果被修改则会重新访问,否则就直接修改掉
CPU cas 为什么比锁得性能快
这个其实就是相对性的了,小结构得情况下,
可能会比锁快个几十倍甚至更高,
但是如果是map等一些复杂大型的结构,
缓存锁定所复制map和存储map的开销也非常大.
所以需要看是什么类型。
快的具体原因:
cas操作类似于乐观锁那种,当后续请求一直不通过时,
会不停的请求访问。
锁:可以粗略的理解为悲观锁,它会有一个线程沉睡唤醒的概念,
当请求不通过时就会沉睡,需要通过cpu来进行唤醒再次执行,
这中间就有cpu上下文切换所带来的所资源消耗,
这个消耗是比较耗时的.
当然要是请求过多cpu cas操作也会特别消耗性能,但锁也是一样
CPU cas 操作
最常用的两种:
1.解决变量并发操作问题
2.可以实现类似于锁的效果,但无法实现锁阻塞效果,
如果是单例模式直接退出的情况,那这个速度就非常nice
上代码:
锁 和 CPU cas 性能操作对比
func TestCas() {
value := test{}
//atomic操作 实现cpu cas操作
b := atomic.Value{}
b.Store(value)
//group确保携程全部跑完在退出
var wg sync.WaitGroup
//当前时间
t1 := time.Now()
//计数
i := 1
for i < 10 {
wg.Add(1)
//开启携程异步来跑
go func() {
defer wg.Done()
//mutex.Lock()
i++
//使用cas 赋值
b.Store(test{i, string(i), string(i), i, i})
time.Sleep(1 * time.Millisecond)
//mutex.Unlock()
}()
}
for i > 0 {
wg.Add(1)
//开启携程异步来跑
go func() {
defer wg.Done()
//mutex.Lock()
i--
time.Sleep(1 * time.Millisecond)
value = b.Load().(test)
//mutex.Unlock()
}()
}
wg.Wait()
fmt.Println(time.Now().Sub(t1).Seconds())
}
func TestLock() {
//锁定义
var mutex sync.Mutex
//group确保携程全部跑完在退出
var wg sync.WaitGroup
//当前时间
t1 := time.Now()
//计数
i := 1
for i < 10 {
wg.Add(1)
//开启携程异步来跑
go func() {
defer wg.Done()
//锁
mutex.Lock()
i++
//忽略修改某个值
time.Sleep(1 * time.Millisecond)
//解锁
mutex.Unlock()
}()
}
for i > 0 {
wg.Add(1)
//开启携程异步来跑
go func() {
defer wg.Done()
//锁
mutex.Lock()
i--
//忽略取某个值
time.Sleep(1 * time.Millisecond)
//解锁
mutex.Unlock()
}()
}
wg.Wait()
fmt.Println(time.Now().Sub(t1).Seconds())
}
TestCas用时0.001332754秒
TestLock用时9.75207781秒
由此可见锁在某些情况下是比CPU cas原子性操作慢很多的
并发操作字符串
结构就不做展示了,和string的效果类似,
就是结构里有些属性已经修改,有些则没有