学习目标:
在工作中用到信号量,记录一下
一、概念
Semaphore 一般译作 信号量,它也是一种线程同步工具,主要用于多个线程对共享资源进行并行操作的一种工具类。它代表了一种许可的概念,是否允许多线程对同一资源进行操作的许可,使用 Semaphore 可以控制并发访问资源的线程个数。
注意本片引入一个比喻;信号量就是你要去一个房间但是有一个门卫在房间门口检查;
二、基础使用
2.1初始化 public Semaphore(int initialCount, int maximumCount);
参数 1:initialCount 初始可用资源数
参数initialCount是设置默认最多可以有几个访问,也是设置初始有多少;比如你设置2,那就是最开始就有2个资源可以用可以去访问,不用等释放;但是最多也是只有同时2个访问;也就是一开始门卫就允许2个人进去,而且也规定了门卫一次只允许2个人进去房间,
参数 2:maximumCount 最大资源上限
- 含义:信号量计数器能达到的最大值,封顶值
- 硬性规则: 每次调用
Release(n)会把计数器 +n; 如果相加后 新计数 > maximumCount → 直接抛出SemaphoreFullException报错。 - 作用:限流保护,防止无限释放资源导致并发失控
比如你设置了5,信号量计数器能达到的最大值最多就是只能是5;也就是门卫只允许最多同时有5个人在房间附近而且包括了在房间里的;你得从房间走了1个才能再来一个;
2.2 阻塞,等待允许访问的信号量 s.WaitOne()
WaitOne()其实是做减法;一条指令就是减一;就相当于门卫一次只允许同时两个人进去,进去一个人就少一个名额;名额减少到了0那就要排队等;
2.3 s.Release(int x) 释放资源
这个相当于叫人来排队;但是要注意门卫的可接受最多人数;比如设置了(3,7),已经有3个人在访问了,在这三个人干完活之后,这时候只能最多再叫3个人来排队,理论上来说就是s.WaitOne()多少就s.Release()多少
三、使用场景
信号量有两种使用场景,一种是并发限流也就是控制多少个线程可以访问一个方法;还有是生产者消费者任务通知;
3.1 并发限流
多线程抢固定名额,WaitOne 和 Release 必须成对,基础格式如下:
WaitOne():占用 1 个名额,计数器 -1- 执行需要限流的耗时逻辑
Release(1):归还名额,计数器 +1
然后多个线程一起执行这个方法,但是因为规定了最多是5,所以虽然创建了一百个但是制最多只能有五个线程执行,之后要等一个线程执行完了才到下一个;
csSemaphore s = new Semaphore(5,5); void TaskWork() { s.WaitOne(); // 抢名额 try { // 并发受限的业务代码 Thread.Sleep(2000); } finally { s.Release(1); // 用完必须归还 } } ///////////////////// int p = 0; Thread x; for(int i=0;i<100;i++) { x = new Thread(TaskWork); x.Start(); }
3.2 生产者消费者任务通知
生产者放任务 Release,消费者取任务 WaitOne,消费者不 Release
- 生产者(addTaskThread / 界面按钮):往队列加任务 →
Release(n),计数器 +n - 消费者(唯一通信线程):
WaitOne()计数器 -n,取出任务执行 - 消费者执行完任务不归还资源
关键特征
- 不对称:生产者 Release,消费者只 Wait 不 Release;
- 全程只有1 个消费线程,不存在并发控制;
- initialCount 固定 0,启动无任务直接阻塞;
- maximumCount 是任务堆积上限,不是线程上限。
cs// 生产者:新增任务 lock(lockObj) { queue.Enqueue(task); } _TaskSemaphore.Release(1); // 消费者(单一线程循环) Task.Run(()=>{ while(true) { _TaskSemaphore.WaitOne(); lock(lockObj) { var task = queue.Dequeue(); } // 执行Modbus通信,没有Release } });
3.3 特殊的一种
一个按钮就是干活,但是点了超过5次就不会输入ok,只有点击另外一个按钮Release一个才会继续输出ok;
cs
Semaphore s = new Semaphore(5,5);
private void button4_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
s.WaitOne();
Console.WriteLine("ok");
}
);
}
private void button5_Click(object sender, EventArgs e)
{
s.Release(1);
}
学习时间:
26.07.13