背景

大多数企业开发人员都理解数据库乐观并发控制,不过很少有人听说过 CAS(我去年才听说这个概念),CAS 是多线程乐观并发控制策略的一种,一些无锁的支持并发的数据结构都会使用到 CAS,本文对比 CAS 和 数据库乐观并发控制,以此达到强化记忆的目的。

CAS

CAS = Compare And Swap

多线程环境下 this.i = this.i + 1 是没有办法保证线程安全的,因此就有了 CAS,CAS 可以保证上面代码的线程安全性,但是 CAS 并不会保证 Swap 的成功,只有 Compare 成功了才会 Swap,即:没有并发发生,即:在我读取和修改之间没有别人修改。另外说一点,如果 i 是局部变量,即:i = i + 1,那么这段代码是线程安全的,因为局部变量是线程独享的。

不明白 CAS 没关系,下面通过 CAS 的标准模式 和 一个简单的示例来理解 CAS。

CAS 的标准模式

伪代码

 1                     var localValue, currentValue;
2 do
3 {
4 localValue = this.
5
6 var newValue = 执行一些计算;
7
8 currentValue = Interlocked.CompareExchange(ref this.value, newValue, localValue);
9 } while (localValue != currentValue);

说明

把 this.value 看成是数据库数据,localValue 是某个用户读取的数据,newValue是用户想修改的值,这里有必要解释一下 CompareExchange 和 currentValue,它的内部实现代码是这样的(想想下面代码是线程安全的):

1 var currentValue = this.value
2 if(currentValue == localValue){
3 this.value = newValue;
4 }
5 return currentValue;

CompareExchange  用 sql 来类比就是:update xxx set value = newValue where value = localValue,只不过返回的值不同。通过 CompareExchange 的返回结果我们知道 CAS 是否成功了,即:是否出现并发了,即:是否在我读取和修改之间别人已经修改过了,如果是,可以选择重试。

累加示例

CAS 代码

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading; namespace InterlockStudy
{
class ConcurrentIncrease
{
public static void Test()
{
var sum = ; var tasks = new Task[];
for (var i = ; i <= ; i++)
{
tasks[i - ] = Task.Factory.StartNew((state) =>
{
int localSum, currentSum;
do
{
localSum = sum; Thread.Sleep();
var value = (int)state;
var newValue = localSum + value; currentSum = Interlocked.CompareExchange(ref sum, newValue, localSum);
} while (localSum != currentSum);
}, i);
} Task.WaitAll(tasks); Console.WriteLine(sum);
}
}
}

数据库并发代码

         public static void Test13()
{
var tasks = new Task[];
for (var i = ; i <= ; i++)
{
tasks[i - ] = Task.Factory.StartNew((state) =>
{
int localSum, result;
do
{
using (var con = new SqlConnection(CONNECTION_STRING))
{
con.Open();
var cmd = new SqlCommand("select sum from Tests where Id = 1", con);
var reader = cmd.ExecuteReader();
reader.Read();
localSum = reader.GetInt32(); System.Threading.Thread.Sleep();
var value = (int)state;
var newValue = localSum + value; cmd = new SqlCommand("update Tests set sum = " + newValue + " where sum = " + localSum + "", con);
result = cmd.ExecuteNonQuery();
}
} while (result == );
}, i);
} Task.WaitAll(tasks);
}
}

说明

我们发现 CAS 版本的代码和数据库版本的代码出奇的相似,数据库的CAS操作是通过 update + where 来完成的。

写着玩的 RingBuffer

代码

 using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading; namespace InterlockStudy
{
internal class Node<T>
{
public T Data { get; set; } public bool HasValue { get; set; }
} class RingBuffer<T>
{
private readonly Node<T>[] _nodes;
private long _tailIndex = -;
private long _headIndex = -;
private AutoResetEvent _readEvent = new AutoResetEvent(false);
private AutoResetEvent _writeEvent = new AutoResetEvent(false); public RingBuffer(int maxSize)
{
_nodes = new Node<T>[maxSize]; for (var i = ; i < maxSize; i++)
{
_nodes[i] = new Node<T>();
}
} public void EnQueue(T data)
{
while (true)
{
if (this.TryEnQueue(data))
{
_readEvent.Set();
return;
} _writeEvent.WaitOne();
} } public T DeQueue()
{
while (true)
{
T data;
if (this.TryDeQueue(out data))
{
_writeEvent.Set();
return data;
} _readEvent.WaitOne();
} } public bool TryEnQueue(T data)
{
long localTailIndex, newTailIndex, currentTailIndex;
do
{
localTailIndex = _tailIndex; if (!this.CanWrite(localTailIndex))
{
return false;
} newTailIndex = localTailIndex + ; if (_nodes[newTailIndex % _nodes.Length].HasValue)
{
return false;
} currentTailIndex = Interlocked.CompareExchange(ref _tailIndex, newTailIndex, localTailIndex);
}
while (localTailIndex != currentTailIndex); _nodes[newTailIndex % _nodes.Length].Data = data;
_nodes[newTailIndex % _nodes.Length].HasValue = true; return true;
} public bool TryDeQueue(out T data)
{
long localHeadIndex, newHeadIndex, currentHeadIndex;
do
{
localHeadIndex = _headIndex; if (!this.CanRead(localHeadIndex))
{
data = default(T);
return false;
} newHeadIndex = localHeadIndex + ;
if (_nodes[newHeadIndex % _nodes.Length].HasValue == false)
{
data = default(T);
return false;
} currentHeadIndex = Interlocked.CompareExchange(ref _headIndex, newHeadIndex, localHeadIndex);
}
while (localHeadIndex != currentHeadIndex); data = _nodes[newHeadIndex % _nodes.Length].Data;
_nodes[newHeadIndex % _nodes.Length].HasValue = false; return true;
} private bool CanWrite(long localTailIndex)
{
return localTailIndex - _headIndex < _nodes.Length;
} private bool CanRead(long localHeadIndex)
{
return _tailIndex - localHeadIndex > ;
}
}
}

备注

仓促成文,如果有必要可以再写篇文章,希望大家多批评。

.NET:通过 CAS 来理解数据库乐观并发控制,顺便给出无锁的 RingBuffer。的更多相关文章

  1. 如何在高并发环境下设计出无锁的数据库操作(Java版本)

    一个在线2k的游戏,每秒钟并发都吓死人.传统的hibernate直接插库基本上是不可行的.我就一步步推导出一个无锁的数据库操作. 1. 并发中如何无锁. 一个很简单的思路,把并发转化成为单线程.Jav ...

  2. 理解 Memory barrier(内存屏障)无锁环形队列

    原文:https://www.cnblogs.com/my_life/articles/5220172.html Memory barrier 简介 程序在运行时内存实际的访问顺序和程序代码编写的访问 ...

  3. CAS无锁操作

    https://coolshell.cn/articles/8239.html 主要讲的是<Implementing Lock-Free Queues>的论点,具体直接看论文最好.这里总结 ...

  4. [数据库事务与锁]详解八:底理解数据库事务乐观锁的一种实现方式——CAS

    注明: 本文转载自http://www.hollischuang.com/archives/1537 在深入理解乐观锁与悲观锁一文中我们介绍过锁.本文在这篇文章的基础上,深入分析一下乐观锁的实现机制, ...

  5. 简论数据库乐观悲观锁与并发编程中的CAS

    为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/ShiJiaqi. http://www.cnblogs.com/shijiaqi1066/p/5783205. ...

  6. 数据库乐观锁和悲观锁的理解和实现(转载&总结)

    数据的锁定分为两种,第一种叫作悲观锁,第二种叫作乐观锁. 1.悲观锁,就是对数据的冲突采取一种悲观的态度,也就是说假设数据肯定会冲突,所以在数据开始读取的时候就把数据锁定住.[数据锁定:数据将暂时不会 ...

  7. 深入理解MySQL的并发控制、锁和事务【转】

    本文主要是针对MySQL/InnoDB的并发控制和加锁技术做一个比较深入的剖析,并且对其中涉及到的重要的概念,如多版本并发控制(MVCC),脏读(dirty read),幻读(phantom read ...

  8. 第18/24周 乐观并发控制(Optimistic Concurrency)

    大家好,欢迎回到性能调优培训.上个星期我通过讨论悲观并发模式拉开了第5个月培训的序幕.今天我们继续,讨论下乐观并发模式(Optimistic Concurrency). 行版本(Row Version ...

  9. Entity Framework 乐观并发控制

    一.背景 我们知道,为了防止并发而出现脏读脏写的情况,可以使用Lock语句关键字,这属于悲观并发控制的一种技术,,但在分布式站点下,锁的作用几乎不存在,因为虽然锁住了A服务器的实例对象,但B服务器上的 ...

随机推荐

  1. 2016-2017-2 20155309 南皓芯《java程序设计》第八周学习总结

    教材学习内容总结 本周学习的主要是第十四章,第十五章的内容. NIO与NIO2 同步非阻塞IO(Java NIO) : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多 ...

  2. js中的异步[Important]

    js作为前端最主流的语言,主要处理页面显示变化(mutation)和异步(asynchronicity), js语言的基本要素和使用惯例的演化大都围绕着这两大主题,两者均值得总结和思考的主题, 这里先 ...

  3. java解析Xml格式的字符串

    最近在工作中,需要调别的接口,接口返回的是一个字符串,而且内容是xml格式的,结果在解析json的时候报错,最终修改了接口的返回方式,以Map返回, 才得以接收到这个xml的字符串,然后通过dom4j ...

  4. WinForm界面开发之 启动界面

    我们在开发桌面应用程序的时候,由于程序启动比较慢,往往为了提高用户的体验,增加一个闪屏,也就是SplashScreen,好处有:1.让用户看到加载的过程,提高程序的交互响应:2.可以简短展示或者介绍程 ...

  5. Owin 自定义中间件(2)中间件进阶

    前面一篇文章简单的介绍了中间件的应用 下面编写一个自定义的中间件类库,并加入中间件参数以及引入日志记录中间件的异常 下面来看下中间件的构造,参数可以自定义 这里我定义了一个参数类 编写中间件需要引入 ...

  6. 000 Excel获取数据

    1.目标网址 http://data.10jqka.com.cn/funds/ggzjl/field/zjjlr 二:需求一 1.需求 爬单个页面的数据 2.变化网址 http://data.10jq ...

  7. sublime text3安装Package Control和Vue Syntax Highlight

    一.下载Sublime3 https://www.sublimetext.com/3 二.安装Package Control 在线安装: https://packagecontrol.io/insta ...

  8. 支撑大规模公有云的Kubernetes改进与优化 (1)

    Kubernetes是设计用来实施私有容器云的,然而容器作为公有云,同样需要一个管理平台,在Swarm,Mesos,Kubernetes中,基于Kubernetes已经逐渐成为容器编排的最热最主流的平 ...

  9. WinForm 数据库无限填充树目录 treeView

    我自己想的是处理数据库每一条数据,然后来插入子节点的子节点. 奈何没有插入子节点的子节点的办法,百度来百度去,一看全都是递归. 本来我是绝望的, 但是没办法,老板的需求不能驳回啊,于是就来ctrl c ...

  10. luoguP4715 [英语]Z语言 平衡树+hash

    显然只能有$hash$来做.... 我们需要一个东西来维护$\sum i * seed^{rank[i]}$ 很自然地联想到平衡树 如果以序列下标建立一棵平衡树,那么无法处理 因此,可以以权值为下标建 ...