博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
无锁版以时间为GUID的方法
阅读量:4707 次
发布时间:2019-06-10

本文共 2456 字,大约阅读时间需要 8 分钟。

  之前的博客 中,我使用了锁。我在实际的使用中,错将锁的释放放在了if语句中,这纯粹是我的失误,导致了很严重的错误。因此我在想是否有无锁的将时间作为GUID的方式,答案是使用Interlocked中的 CompareExchange方法,该方法是原子操作。说是无锁操作,其实就是让clr来保证操作的原子性,而不用自己写锁。没有锁,也就没有死锁的风险了(当然CLR也可能犯错,但是CLR犯错要比我犯错的概率低太多)。

  我在  中介绍了,DateTime.Now提供不了ms级的时间,虽然时间的最小单位是ms,但是该时间的误差至少在100ms以上,因此当多线程同时调用DateTime.Now.ToString("yyyyMMddHHmmssfff")的时候,如果在100ms内同时并发,就会出现同样的结果,而GUID是不能相同的,因此需要对该方法进行简单的改造,从而保证同一进程内,多线程访问时,时间GUID的唯一性。有了之前死锁的教训,我决定不适用锁来实现。该方法的实现方式是一种乐观并发的模式,《CLR via C#》中多线程部分有介绍。我的想法是,既然DateTime.now有误差,我可以在后面添加一个数字,这个数字每次会+1,这样无论多少个线程访问,都不会重复。代码如下:

static int c = 0;public static string GetTimeUtils(){    //这样做无法保证f的唯一性,因为其他线程在调用该方法时,有可能读取了相同的c,    //从而f++得到相同结果//return DateTime.Now.ToString("yyyyMMddHHmmssfff") + c++;    int f,z;    do    {        f = c;        z = f+1;        if (z >= 9999) z = 0;    }    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 = 0; i < 20; i++){            ConcurrentBag
list = new ConcurrentBag
(); TaskExtension.ParallelRun(9000, 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 = 0; 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的方法和测试代码,欢迎有疑问的小伙伴在评论区和我讨论。

转载于:https://www.cnblogs.com/jazzpop/p/9613627.html

你可能感兴趣的文章
数字图像处理 博客目录索引
查看>>
nodejs+redis使用
查看>>
prime算法的使用
查看>>
【转】如何避免OOM总结
查看>>
java 类与对象
查看>>
git push 每次都要输入用户名密码
查看>>
远程桌面无法复制粘贴
查看>>
对错排认识。
查看>>
Windows PowerShell
查看>>
几道前端的面试题
查看>>
进程间通信的四种方式
查看>>
Sentinel系统监控Redis主从节点
查看>>
Java设计模式之《享元模式》及应用场景
查看>>
TX2安装QT
查看>>
PHP使用curl替代file_get_contents
查看>>
Webstorm通用设置
查看>>
组合数的求法 (n<=1e8 可以过来看)
查看>>
jquery倾斜的动画导航菜单
查看>>
JAVA IO流的简单总结+收集日志异常信息
查看>>
NetCore的配置管理(1)
查看>>