C# 多线程十 信号量Semaphore/SemaphoreSlim的简单理解与运用

一.继承

Semaphore-> WaitHandle->->MarshalByRefObject
SemaphoreSlim->IDisposable

从继承上来看Semaphore是MarshalByRefObject的孙子类
我们前几篇了解到 互斥Mutex和事件AutoResetEvent/ManualResetEvent往上追溯会发现都继承了MarshalByRefObject

二.应用程序域中的对象的通信方式

一种是跨应用程序域边界传输对象副本。按值封送(marshal by value)
一种是使用代理交换消息。按引用封送(marshal by reference)

由此 我们了解了Mutex和AutoResetEvent/ManualResetEvent 跨进程的原因
因此 Semaphore也是可以跨进程的

三.定义

Semaphore 限制可同时访问某一资源或资源池的线程数
SemaphoreSlim对可同时访问资源或资源池的线程数加以限制的 Semaphore 的轻量替代
SemaphoreSlim 类表示一个轻量、快速的信号量,可在等待时间预计很短的情况下用于在单个进程内等待

四.理解

Semaphore/SemaphoreSlim 就像是一扇大门
构造函数 就是指定每次开门 只允许通过多少人
WaitOne  就是打开大门 开始计算出去了几个人 一旦人数达到 大门关闭 不再允许访问WaitOne后面的逻辑
Release  就是抹掉当前第一个出去的人的记录,让大门口排队的一个人可以出去接着走门外的逻辑
Release(Int32) 可以指定抹掉多少人的记录

五.Semaphore/SemaphoreSlim的区别

Semaphore表示一个命名(系统范围内)或本地信号量。它是对 Win32 信号量对象的封装,Win32 信号量是计数信号量,其可用于控制对资源池的访问,可以跨进程的
 SemaphoreSlim 类为一个轻量、快速的信号量,可在等待时间预计很短的情况下,用于在单个进程内的等待

六.方法

注:下面所有解释均来自.Net api 个人只做了代码解释

一.Semaphore

1.构造函数

Semaphore(Int32, Int32)     
初始化 Semaphore 类的新实例,并指定初始入口数和最大并发入口数。

Semaphore(Int32, Int32, String)     
初始化 Semaphore 类的新实例,并指定初始入口数和最大并发入口数,可以选择指定系统信号量对象的名称。

Semaphore(Int32, Int32, String, Boolean)     
初始化 Semaphore 类的新实例,并指定初始入口数和最大并发入口数,还可以选择指定系统信号量对象的名称,以及指定一个变量来接收指示是否创建了新系统信号量的值。

2.方法:

Release()     
退出信号量并返回前一个计数。

Release(Int32)     
以指定的次数退出信号量并返回前一个计数。

WaitOne()     
阻止当前线程,直到当前 WaitHandle 收到信号。
(继承自 WaitHandle)

WaitOne(Int32)     
阻止当前线程,直到当前 WaitHandle 收到信号,同时使用 32 位带符号整数指定时间间隔(以毫秒为单位)。
(继承自 WaitHandle)

WaitOne(Int32, Boolean)     
阻止当前线程,直到当前的 WaitHandle 收到信号为止,同时使用 32 位带符号整数指定时间间隔,并指定是否在等待之前退出同步域。
(继承自 WaitHandle)

WaitOne(TimeSpan)     
阻止当前线程,直到当前实例收到信号,同时使用 TimeSpan 指定时间间隔。
(继承自 WaitHandle)

WaitOne(TimeSpan, Boolean)     
阻止当前线程,直到当前实例收到信号为止,同时使用 TimeSpan 指定时间间隔,并指定是否在等待之前退出同步域。
(继承自 WaitHandle)


二.SemaphoreSlim

1.构造函数

SemaphoreSlim(Int32)     
初始化 SemaphoreSlim 类的新实例,以指定可同时授予的请求的初始数量。

SemaphoreSlim(Int32, Int32)     
初始化 SemaphoreSlim 类的新实例,同时指定可同时授予的请求的初始数量和最大数量。

2.方法

Release()     
释放 SemaphoreSlim 对象一次。

Release(Int32)     
释放 SemaphoreSlim 对象指定的次数。

Wait()     
阻止当前线程,直至它可进入 SemaphoreSlim 为止。

Wait(CancellationToken)     
阻止当前线程,直至它可进入 SemaphoreSlim 为止,同时观察 CancellationToken。

代码:

static Dictionary<int, string> dic = new Dictionary<int, string>();
    static List<Task> tasks = new List<Task>();
    static void Main(string[] args) {
        CancellationTokenSource srcToken = new CancellationTokenSource();
        SemaphoreSlim semaphoreSlim = new SemaphoreSlim(0);
        for(int i = 0; i < 10; i++) {
            dic.Add(i, i.ToString());
        }
        foreach(var i in dic) {
            tasks.Add(Task.Run(() => {
                Thread.Sleep(100 * i.Key);
                Console.WriteLine($"task:{i.Value} task线程ID:{Thread.CurrentThread.ManagedThreadId}");
                semaphoreSlim.Release();
            }, srcToken.Token));
        }
        for(int i = 0; i < 10; i++) {
            semaphoreSlim.Wait(srcToken.Token);
            Console.WriteLine($"for:{i} 线程ID:{Thread.CurrentThread.ManagedThreadId}");
            if(i == 9) srcToken.Cancel();
        }

        Console.ReadKey();
    }

打印:

由打印可以看出

当我设置SemaphoreSlim semaphoreSlim = new SemaphoreSlim(0)的时候 需要等待semaphoreSlim.Release()后 才会走入semaphoreSlim.Wait(srcToken.Token)后的逻辑
且阻塞了线程1

提前结束: 

static Dictionary<int, string> dic = new Dictionary<int, string>();
    static List<Task> tasks = new List<Task>();
    static void Main(string[] args) {
        CancellationTokenSource srcToken = new CancellationTokenSource();
        SemaphoreSlim semaphoreSlim = new SemaphoreSlim(0);
        for(int i = 0; i < 10; i++) {
            dic.Add(i, i.ToString());
        }
        foreach(var i in dic) {
            tasks.Add(Task.Run(() => {
                Thread.Sleep(100 * i.Key);
                Console.WriteLine($"task:{i.Value} task线程ID:{Thread.CurrentThread.ManagedThreadId}");
                semaphoreSlim.Release();
            }, srcToken.Token));
        }
        srcToken.Cancel();
        for(int i = 0; i < 10; i++) {
            semaphoreSlim.Wait(srcToken.Token);
            Console.WriteLine($"for:{i} 线程ID:{Thread.CurrentThread.ManagedThreadId}");
        }

        Console.ReadKey();
    }

打印:

Wait(Int32)     
阻止当前线程,直至它可进入 SemaphoreSlim 为止,同时使用 32 位带符号整数来指定超时。

代码:

static Dictionary<int, string> dic = new Dictionary<int, string>();
    static List<Task> tasks = new List<Task>();
    static void Main(string[] args) {
        CancellationTokenSource srcToken = new CancellationTokenSource();
        SemaphoreSlim semaphoreSlim = new SemaphoreSlim(3);
        for(int i = 0; i < 10; i++) {
            dic.Add(i, i.ToString());
        }
        foreach(var i in dic) {
            tasks.Add(Task.Run(() => {
                Thread.Sleep(100 * (i.Key + 1));
                Console.WriteLine($"task:{i.Value} task线程ID:{Thread.CurrentThread.ManagedThreadId}");
                semaphoreSlim.Release();
            }, srcToken.Token));
        }
        for(int i = 0; i < 10; i++) {
            semaphoreSlim.Wait(1000);
            Console.WriteLine($"for:{i} 线程ID:{Thread.CurrentThread.ManagedThreadId}");
        }

        Console.ReadKey();
    }

打印:

由打印可以看出

SemaphoreSlim semaphoreSlim = new SemaphoreSlim(3)

初始允许 for走了三次之后才开始阻塞线程

修改一下超时检测值:

static Dictionary<int, string> dic = new Dictionary<int, string>();
    static List<Task> tasks = new List<Task>();
    static void Main(string[] args) {
        CancellationTokenSource srcToken = new CancellationTokenSource();
        SemaphoreSlim semaphoreSlim = new SemaphoreSlim(3);
        for(int i = 0; i < 10; i++) {
            dic.Add(i, i.ToString());
        }
        foreach(var i in dic) {
            tasks.Add(Task.Run(() => {
                Thread.Sleep(100 * (i.Key + 1));
                Console.WriteLine($"task:{i.Value} task线程ID:{Thread.CurrentThread.ManagedThreadId}");
                semaphoreSlim.Release();
            }, srcToken.Token));
        }
        for(int i = 0; i < 10; i++) {
            semaphoreSlim.Wait(1);
            Console.WriteLine($"for:{i} 线程ID:{Thread.CurrentThread.ManagedThreadId}");
        }

        Console.ReadKey();
    }

 打印:

超时之后也会释放掉


Wait(Int32, CancellationToken)     
阻止当前线程,直至它可进入 SemaphoreSlim 为止,并使用 32 位带符号整数来指定超时,同时观察 CancellationToken。

Wait(TimeSpan)     
阻止当前线程,直至它可进入 SemaphoreSlim 为止,同时使用 TimeSpan 来指定超时。

代码:

static Dictionary<int, string> dic = new Dictionary<int, string>();
    static List<Task> tasks = new List<Task>();
    static void Main(string[] args) {
        CancellationTokenSource srcToken = new CancellationTokenSource();
        SemaphoreSlim semaphoreSlim = new SemaphoreSlim(3);
        for(int i = 0; i < 10; i++) {
            dic.Add(i, i.ToString());
        }
        foreach(var i in dic) {
            tasks.Add(Task.Run(() => {
                Thread.Sleep(100 * (i.Key + 1));
                Console.WriteLine($"task:{i.Value} task线程ID:{Thread.CurrentThread.ManagedThreadId}");
                semaphoreSlim.Release();
            }, srcToken.Token));
        }
        for(int i = 0; i < 10; i++) {
            semaphoreSlim.Wait(TimeSpan.FromSeconds(1));
            Console.WriteLine($"for:{i} 线程ID:{Thread.CurrentThread.ManagedThreadId}");
        }

        Console.ReadKey();
    }

 打印:

修改一下释放值:

static Dictionary<int, string> dic = new Dictionary<int, string>();
    static List<Task> tasks = new List<Task>();
    static void Main(string[] args) {
        CancellationTokenSource srcToken = new CancellationTokenSource();
        SemaphoreSlim semaphoreSlim = new SemaphoreSlim(3);
        for(int i = 0; i < 10; i++) {
            dic.Add(i, i.ToString());
        }
        foreach(var i in dic) {
            tasks.Add(Task.Run(() => {
                Thread.Sleep(100 * (i.Key + 1));
                Console.WriteLine($"task:{i.Value} task线程ID:{Thread.CurrentThread.ManagedThreadId}");
                semaphoreSlim.Release(5);
            }, srcToken.Token));
        }
        for(int i = 0; i < 10; i++) {
            semaphoreSlim.Wait(TimeSpan.FromSeconds(1));
            Console.WriteLine($"for:{i} 线程ID:{Thread.CurrentThread.ManagedThreadId}");
        }

        Console.ReadKey();
    }

 打印:

可以发现 尽管我初始化的时候设定了3 可是当修改每次释放5个后 允许for走了5次之后才开始阻塞线程

Wait(TimeSpan, CancellationToken)     
阻止当前线程,直至它可进入 SemaphoreSlim 为止,并使用 TimeSpan 来指定超时,同时观察 CancellationToken。

注:使用Wait时是同步的 即一旦开始阻止会阻塞当前线程


WaitAsync()     
输入 SemaphoreSlim 的异步等待。

WaitAsync(CancellationToken)     
在观察 CancellationToken 时,输入 SemaphoreSlim 的异步等待。

WaitAsync(Int32)     
输入 SemaphoreSlim 的异步等待,使用 32 位带符号整数度量时间间隔。

WaitAsync(Int32, CancellationToken)     
在观察 CancellationToken 时,输入 SemaphoreSlim 的异步等待,使用 32 位带符号整数度量时间间隔。

WaitAsync(TimeSpan)     
输入 SemaphoreSlim 的异步等待,使用 TimeSpan 度量时间间隔。

WaitAsync(TimeSpan, CancellationToken)     
在观察 SemaphoreSlim 时,输入 TimeSpan 的异步等待,使用 CancellationToken 度量时间间隔。

注:使用WaitAsync时是异步的 不阻塞当前线程

代码:

static Dictionary<int, string> dic = new Dictionary<int, string>();
    static List<Task> tasks = new List<Task>();
    static void Main(string[] args) {
        CancellationTokenSource srcToken = new CancellationTokenSource();
        SemaphoreSlim semaphoreSlim = new SemaphoreSlim(0);
        for(int i = 0; i < 10; i++) {
            dic.Add(i, i.ToString());
        }
        foreach(var i in dic) {
            tasks.Add(Task.Run(() => {
                Thread.Sleep(100 * (i.Key + 1));
                Console.WriteLine($"task:{i.Value} task线程ID:{Thread.CurrentThread.ManagedThreadId}");
                semaphoreSlim.Release();
            }, srcToken.Token));
        }
        for(int i = 0; i < 10; i++) {
            semaphoreSlim.WaitAsync();
            Console.WriteLine($"for:{i} 线程ID:{Thread.CurrentThread.ManagedThreadId}");
        }

        Console.ReadKey();
    }

打印:

 

修改之后发现并没有阻塞线程 而是开了一个线程去等待

验证一下:

static Dictionary<int, string> dic = new Dictionary<int, string>();
    static List<Task> tasks = new List<Task>();
    static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(0);
    static void Main(string[] args) {
        CancellationTokenSource srcToken = new CancellationTokenSource();

        for(int i = 0; i < 10; i++) {
            dic.Add(i, i.ToString());
        }
        foreach(var i in dic) {
            tasks.Add(Task.Run(() => {
                Thread.Sleep(100 * (i.Key + 1));
                Console.WriteLine($"task:{i.Value} task线程ID:{Thread.CurrentThread.ManagedThreadId}");
                semaphoreSlim.Release();
            }, srcToken.Token));
        }
        DoSth();
        Console.WriteLine($"主线程ID:{Thread.CurrentThread.ManagedThreadId}");
        Console.ReadKey();
    }

    static async void DoSth() {
        for(int i = 0; i < 10; i++) {
            await semaphoreSlim.WaitAsync();
            Console.WriteLine($"for:{i} 线程ID:{Thread.CurrentThread.ManagedThreadId}");
        }
    }

 打印:

注:

使用semaphoreSlim.Wait(CancellationToken cancellationToken);等待时如果cancellationToken提前释放会抛出System.OperationCanceledException异常


使用semaphoreSlim.Wait(int millisecondsTimeout); -1 表示无限期超时 非-1的负数会抛出 System.ArgumentOutOfRangeException异常

至此:关于多线程算是告一段落了,当然没有全部囊括,只是写了一些自己比较常用了,以后有时间接着更。

如果有不对的地方希望能指出来 感激不尽。

另外,不熟悉的代码一定要写一下加深记忆 只用看的记不了太久。

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一梭键盘任平生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值