• .Net SemaphoreSlim


    看Elsa-core源代码中看到的,Elsa-core中所有保存数据的方法似乎使用同一个Save方法。如下图:

    那么为什么要使用这玩意,我还是头一次见这玩意????

    好吧,我承认我自己菜。我自个儿也该保持谦虚态度学习学习了。

    先看下这个SemaphoreSlim类的描述

    (我买的正版Resharp 2022)反编译代码如上图。

    翻译过来就是 “限制当前访问资源或池中资源的线程数”,真的是这样的吗? 试一试...

    我的代码仓库  https://github.com/qiqiqiyaya/Learning-Case/tree/main/SemaphoreSlim ,测试例子。

    例一

    在B乎中看到一篇文章 ,链接地址  https://zhuanlan.zhihu.com/p/158777952

     我copy他的例子测试了下,如下图:

    复制代码
    // 现在有10个人要过桥
    // 但是一座桥上只能承受5个人,再多桥就会塌
    static void SemaphoreTest()
    {
        var semaphore = new SemaphoreSlim(5);
        for (int i = 1; i <= 10; i++)
        {
            Thread.Sleep(100); // 排队上桥
            var index = i; // 定义index 避免出现闭包的问题
            Task.Run(() =>
            {
                semaphore.Wait();
                try
                {
                    Console.WriteLine($"第{index}个人正在过桥。");
                    Thread.Sleep(5000); // 模拟过桥需要花费的时间
                }
                finally
                {
                    Console.WriteLine($"第{index}个人已经过桥。");
                    semaphore.Release();
                }
            });
        }
    }
    复制代码

     运行结果,与B乎上作者的结果一样。

    这里有个问题,该作者使用Task.Run 然后在其中添加了 Thread.Sleep(5000); ,这会阻塞当前的线程,导致执行 Task 任务的“任务调度器”开启了 10个线程。

    关于什么是 Task ,理解 Task ,请阅读大佬的文章,非常Nice链接 https://www.cnblogs.com/artech/p/task_scheduling.html

    列二,如果在异步Async/Await中是什么情况呢?

    复制代码
    static void SemaphoreTest1()
    {
        var semaphore = new SemaphoreSlim(5);
        for (int i = 1; i <= 10; i++)
        {
            Thread.Sleep(100); // 排队上桥
            var index = i; // 定义index 避免出现闭包的问题
            Task.Run(async () =>
            {
                Console.WriteLine($"第{index}个人已抵达桥边上。线程Id  " + Thread.CurrentThread.ManagedThreadId);
                semaphore.Wait();
                try
                {
                    Console.WriteLine($"第{index}个人正在过桥。线程Id  " + Thread.CurrentThread.ManagedThreadId);
                    //Thread.Sleep(5000); // 模拟过桥需要花费的时间
                    await Task.Delay(5000);
                }
                finally
                {
                    Console.WriteLine($"第{index}个人已经过桥。线程Id  " + Thread.CurrentThread.ManagedThreadId);
                    semaphore.Release();
                }
            });
        }
    }
    复制代码

    将 Thread.Sleep(5000); 换成了 await Task.Delay(5000); 使用 async/await 。结果如下:

     从结果上看,例如:第1个人 线程Id 3 上桥,在执行 await Task.Delay(5000); (模拟过桥需要花费的时间)后,线程Id变成了 12 。这是执行 Task 的任务调度器(Task默认调度器是线程池)作用的效果。这不是本次随笔的重点。

    再次强调关于理解Task,请参考 https://www.cnblogs.com/artech/p/task_scheduling.html 。

    再次强调Task与线程Thread是两个东西,Task可以理解为一个任务,那么这个任务由Thread去执行,至于由哪一个Thread 去执行,这就由 “任务调度器” 去决定了。

    从结果上看,执行了五个Task之后,就阻止后续Task再往下执行代码了。

    列二中使用了 semaphore.Wait(); 同步阻塞 , 导致后续Task中使用一个之前空闲的Id为3的线程 (Id为3的线程执行到 await Task.Delay(5000) 后阻塞了,该线程被调度器拿去执行新的Task任务),并创建了另外4个新的执行Task的线程。

    结论:

    1.SemaphoreSlim会限制访问资源的线程数

    2.在异步async/await情况下,SemaphoreSlim会限制正在执行访问资源的Task的数量

    例三

    复制代码
    static void SemaphoreTest2()
    {
        var semaphore = new SemaphoreSlim(5);
        for (int i = 1; i <= 10; i++)
        {
            Thread.Sleep(100); // 排队上桥
            var index = i; // 定义index 避免出现闭包的问题
            Task.Run(async () =>
            {
                Console.WriteLine($"第{index}个人已抵达桥边上。线程Id  " + Thread.CurrentThread.ManagedThreadId);
                await semaphore.WaitAsync();
                try
                {
                    Console.WriteLine($"第{index}个人正在过桥。线程Id  " + Thread.CurrentThread.ManagedThreadId);
                    await Task.Delay(5000);// 模拟过桥需要花费的时间
                }
                finally
                {
                    Console.WriteLine($"第{index}个人已经过桥。线程Id  " + Thread.CurrentThread.ManagedThreadId);
                    semaphore.Release();
                }
            });
        }
    }
    复制代码

    执行结果

    例三中我使用了 await semaphore.WaitAsync(); ,也就是说不存在同步阻塞代码了,全部都是异步。

    异步下遇到await,执行等待,那么当前执行这个Task的线程可能会被“任务调度器拿去执行别的Task”

    结论:

    1.在异步async/await情况下,SemaphoreSlim会限制正在执行访问资源的Task的数量,  与例二第二个结论一致。

    异步下的Lock锁

    鉴于SemaphoreSlim的特殊性,可以使用其通过程序角度来实现“”(叫乐观锁、还是悲观锁???,我去,两者好像都不是

    乐观锁与悲观锁定义如下图

    如下图代码:

    重点代码  SemaphoreSlim _semaphore = new(1) 

    如果真要说明,我觉得是最多像 悲观锁 ,但又不全是。这里的SemaphoreSlim只是说同一时刻只准一个线程或正在执行的Task做保存操作,那么如果是分布式程序呢,另一个程序此时也执行保存操作?

     我觉得其真实原因是:使用SemaphoreSlim可以实现异步下的 Lock 锁。, 使用 Lock 锁效果时 SemaphoreSlim 需是单例。

  • 相关阅读:
    Flutter Set存储自定义对象时 如何保证唯一
    三西格玛和六西格玛区别是什么?优思学院用一幅图告诉你
    (算法设计与分析)第四章贪心算法-第一节:贪心算法概述
    Mysql8 %中间%模糊搜索 强行 支持 使用 索引 代替全文搜索 全文检索数据库
    MySQL从库Error:“You cannot ‘Alter‘ a log table...“
    java计算机毕业设计ssm养老管理系统-敬老院系统
    数据库系统原理与应用教程(004)—— MySQL 安装与配置:重置 MySQL 登录密码(windows 环境)
    数据结构每日亿题(二)
    weblogic/CVE-2018-2894文件上传漏洞复现
    面试高频的CMS回收器
  • 原文地址:https://www.cnblogs.com/youlicc/p/16886825.html