锁 和 CPU CAS 原子操作

锁 和 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 := 1for 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 := 1for 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原子性操作慢很多的

并发操作字符串

   func Test() {test := "test"//开启携程异步操作go func() {i := 1for {i++//这个取余数的效果是为了让test有值的转换每执行一次值都会有变动if i%2 == 1 {//给了20个王test = "王王王王王王王王王王王王王王王王王王王王"} else {//把test在还给变量test = "test"}time.Sleep(1 * time.Millisecond)}}()//开启携程异步操作go func() {for {time.Sleep(1 * time.Millisecond)//把test的值取出来v := test//因为以上写的逻辑 test最终的结果只有 test 或者是 20个王//但是由于是并发操作,所以可能20个王并不是完整的//这边输出的就是如果你包含了一个王但是没有满足20个王的情况,那么就会输出出来if strings.Contains(cast.ToString(v), "王") && !strings.Contains(cast.ToString(v), "王王王王王王王王王王王王王王王王王王王王") {fmt.Println(v)}}}()}//输出了 王�
//说明并发操作的原因导致test有乱码且不完整
//我们改程 CPU cas操作func Test() {//定义 atomictest := atomic.Value{}//使用 CPU cas赋值test.Store("test")//开启携程异步操作go func() {i := 1for {i++//这个取余数的效果是为了让test有值的转换每执行一次值都会有变动if i%2 == 1 {//给了20个王test.Store("王王王王王王王王王王王王王王王王王王王王")} else {//把test在还给变量test.Store("test")}time.Sleep(1 * time.Millisecond)}}()//开启携程异步操作go func() {for {time.Sleep(1 * time.Millisecond)//把test的值取出来v := test.Load().(string)//因为以上写的逻辑 test最终的结果只有 test 或者是 20个王//但是由于是并发操作,所以可能20个王并不是完整的//这边输出的就是如果你包含了一个王但是没有满足20个王的情况,那么就会输出出来if strings.Contains(cast.ToString(v), "王") && !strings.Contains(cast.ToString(v), "王王王王王王王王王王王王王王王王王王王王") {fmt.Println(v)}}}()}使用 CPU cas 控制后,就不会出现这种情况了

结构就不做展示了,和string的效果类似,

就是结构里有些属性已经修改,有些则没有

本文链接:https://my.lmcjl.com/post/10470.html

展开阅读全文

4 评论

留下您的评论.