无锁版以时间为GUID的方法
之前的博客 将时间作为GUID的方法 中,我使用了锁。我在实际的使用中,错将锁的释放放在了if语句中,这纯粹是我的失误,导致了很严重的错误。因此我在想是否有无锁的将时间作为GUID的方式,答案是使用Interlocked中的 CompareExchange方法,该方法是原子操作。说是无锁操作,其实就是让clr来保证操作的原子性,而不用自己写锁。没有锁,也就没有死锁的风险了(当然CLR也可能犯错,但是CLR犯错要比我犯错的概率低太多)。
我在 将时间作为GUID的方法 中介绍了,DateTime.Now提供不了ms级的时间,虽然时间的最小单位是ms,但是该时间的误差至少在100ms以上,因此当多线程同时调用DateTime.Now.ToString("yyyyMMddHHmmssfff")的时候,如果在100ms内同时并发,就会出现同样的结果,而GUID是不能相同的,因此需要对该方法进行简单的改造,从而保证同一进程内,多线程访问时,时间GUID的唯一性。有了之前死锁的教训,我决定不适用锁来实现。该方法的实现方式是一种乐观并发的模式,《CLR via C#》中多线程部分有介绍。我的想法是,既然DateTime.now有误差,我可以在后面添加一个数字,这个数字每次会+1,这样无论多少个线程访问,都不会重复。代码如下:
static int c = ;
public static string GetTimeUtils()
{
//这样做无法保证f的唯一性,因为其他线程在调用该方法时,有可能读取了相同的c,
//从而f++得到相同结果//return DateTime.Now.ToString("yyyyMMddHHmmssfff") + c++;
int f,z;
do
{
f = c;
z = f+;
if (z >= ) z = ;
}
while (Interlocked.CompareExchange(ref c, z, f) != f);
return DateTime.Now.ToString("yyyyMMddHHmmssfff") + f;
}
先解释下Interlocked.CompareExchange方法,该方法是原子操作,意思是如果c==f,则c=z,然后返回c原来的值。将其放进while循环中,是为了保证f读取的c是最新的值。如果f读到的值不是最新的值,就表明这期间有其他线程对c++,这时就重新计算。这其实相当于一次“原子操作”,只不过,这个原子操作不是利用锁来获得的,而是利用线程执行间歇来恰巧获得的。这有点像自旋锁的意思,都有一个while循环。如果在执行期间有其他线程也对c++,那么重新来,直到找到了只有一个线程执行的间歇。这样解释下来,这个方法还真是很“乐观”。如果方法的执行时间较长,并发数较高,这样的间歇是非常不好找的,也就不适用这种无锁模式。该方法适合需要同步的代码量较小,执行时间非常短的情况。
总结一下,该方法的想法是将f=c++原子化,办法是找到多线程的间歇。
下面我们来验证一下该方法是否能够保证唯一性:
class Program
{
static void Main(string[] args){
for (int i = ; i < ; i++){
ConcurrentBag<string> list = new ConcurrentBag<string>();
TaskExtension.ParallelRun(, true, () => list.Add(Test.GetTimeUtils()))
.Then(() => Console.WriteLine($"是否有重复?{list.Count() != list.Distinct().Count()}"));
}
Console.Read();
}
}
public static class TaskExtension
{
public static Task[] ParallelRun(int runCount, bool start, Action action){
var tasks = new Task[runCount];
for (int i = ; i < runCount; i++){
tasks[i] = new Task(action);
if (start) tasks[i].Start();
}
return tasks;
}
public static async Task Then(this Task[] t, Action action){
await Task.WhenAll(t);
action();
}
}
可以看到,是否有重复,结果为false,证明不会产生重复。
我也对该方法和带锁的方法进行了对比,发现该方法和有锁版在性能上基本没有优势。我使用的是lock,该锁的后台实现是Monitor,是一个混合锁,在较短时间的时候,会先自旋,因此性能较好。但是一旦使用锁,就有被死锁的风险,而无锁版是不用担心这个问题的。也推荐大家去看看Interlocked中的方法,里面提供了一些简单的原子操作。
以上为实现以时间作为GUID的方法和测试代码,欢迎有疑问的小伙伴在评论区和我讨论。
无锁版以时间为GUID的方法的更多相关文章
- 以当前时间作为GUID的方法
在C#中,系统提供了GUID类,用户可以通过该类来获得128位的唯一标识,但是该标识不具有可读性,很难把该GUID显示在界面上,以当前时间精确到毫秒来作为GUID是一个比较不错的做法,但是DateTi ...
- 使用CAS实现无锁列队-链表
#include <stdlib.h> #include <stdio.h> #include <pthread.h> #include <iostream& ...
- CAS原子操作实现无锁及性能分析
CAS原子操作实现无锁及性能分析 Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csdn.net/chen19870707 ...
- 2022年5月11日,NBMiner发布了41.3版本,在内核中加入了100%LHR解锁器,从此NVIDIA的显卡再无锁卡一说
2022年5月11日,NBMiner发布NBMiner_41.3版本,主要提升了稳定性. 2022年5月8日,NBMiner发布NBMiner_41.0版本,在最新的内核 ...
- Java高并发-无锁
一.无锁类的原理 1.1 CAS CAS算法的过程是这样:它包含3个参数CAS(V,E,N).V表示要更新的变量,E表示预期值,N表示新值.仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同, ...
- JDK1.8 LongAdder 空间换时间: 比AtomicLong还高效的无锁实现
我们知道,AtomicLong的实现方式是内部有个value 变量,当多线程并发自增,自减时,均通过CAS 指令从机器指令级别操作保证并发的原子性. // setup to use Unsafe.co ...
- 一个无锁消息队列引发的血案(六)——RingQueue(中) 休眠的艺术 [续]
目录 (一)起因 (二)混合自旋锁 (三)q3.h 与 RingBuffer (四)RingQueue(上) 自旋锁 (五)RingQueue(中) 休眠的艺术 (六)RingQueue(中) 休眠的 ...
- 一个无锁消息队列引发的血案(五)——RingQueue(中) 休眠的艺术
目录 (一)起因 (二)混合自旋锁 (三)q3.h 与 RingBuffer (四)RingQueue(上) 自旋锁 (五)RingQueue(中) 休眠的艺术 (六)RingQueue(中) 休眠的 ...
- 一个无锁消息队列引发的血案(四)——月:RingQueue(上) 自旋锁
目录 (一)起因 (二)混合自旋锁 (三)q3.h 与 RingBuffer (四)RingQueue(上) 自旋锁 (五)RingQueue(中) 休眠的艺术 (六)RingQueue(中) 休眠的 ...
随机推荐
- JMS学习八(ActiveMQ消息持久化)
ActiveMQ的消息持久化机制有JDBC,AMQ,KahaDB和LevelDB,还有一种内存存储的方式,由于内存不属于持久化范畴,而且如果使用内存队列,可以考虑使用更合适的产品,如ZeroMQ.所以 ...
- 7.Python编码规范(PEP 8)
在讲解具体的 Python 编码规范之前,先来看看图 1 中的代码: 图 1 两段功能相同的 Python 代码 对比图 1 中的两段代码你会发现,它们所包含的代码时完全相同的,但很明显,右侧的代码编 ...
- ArrayList类源码浅析(二)
1.removeAll(Collection<?> c)和retainAll(Collection<?> c)方法 第一个是从list中删除指定的匹配的集合元素,第二个方法是用 ...
- 关于MySQL 中 EXISTS 的用法
在MySQL中 EXISTS 和 IN 的用法有什么关系和区别呢? 假定数据库中有两个表 分别为 表 a 和表 b create table a ( a_id int, a_name varchar( ...
- 树状数组的理解(前缀和 and 差分)
二更—— 有神仙反映数星星那个题外链炸了,我决定把图给你们粘一下,汉语翻译的话在一本通提高篇的树状数组那一章里有,同时也修改了一些汉语语法的错误 这段时间学了线段树组,当神仙们都在学kmp和hash的 ...
- python - socket通信笔记
参考: 通过编写聊天程序来熟悉python中多线程和socket的用法:https://www.cnblogs.com/mingjiatang/p/4905395.html python socket ...
- 自定义配置节点configSections的使用
//App.config <?xml version="1.0" encoding="utf-8" ?><configuration> ...
- 阶段3 1.Mybatis_05.使用Mybatis完成CRUD_5 Mybatis的CRUD-查询返回一行一列和占位符分析
聚合函数 模糊查询的另外一种写法 如果用户这种方式里面的value是固定的 因为在源码分析中,绑定的就是固定的value值 所以这里传参数的 没必要在用百分号了 删掉后 xml里面应该用这种方式来注释 ...
- C#异常操作
C#异常处理子系统包括: Try:需要异常机制的函数在其中运行 Catch:捕获异常 Throw:抛出异常 Finally:在try结束实现 C#异常主要在Exception类中,而在CLR机制中的异 ...
- PSP周总结03
周日 周一 周二 周三 周四 周五 周六 所花时间 120 150 190 180 180 300 代码量 159 372 博客量 1 了解的知识点 阅读构建之法 软件工程概论 阅读构建之法 图书 ...