C#多线程(5):资源池限制
Semaphore、SemaphoreSlim 类
两者都可以限制同时访问某一资源或资源池的线程数。
这里先不扯理论,我们从案例入手,通过示例代码,慢慢深入了解。
Semaphore 类
这里,先列出 Semaphore 类常用的 API。
其构造函数如下:
| 构造函数 | 说明 |
|---|---|
| Semaphore(Int32, Int32) | 初始化 Semaphore 类的新实例,并指定初始入口数和最大并发入口数。 |
| Semaphore(Int32, Int32, String) | 初始化 Semaphore 类的新实例,并指定初始入口数和最大并发入口数,根据需要指定系统信号灯对象的名称。 |
| Semaphore(Int32, Int32, String, Boolean) | 初始化 Semaphore 类的新实例,并指定初始入口数和最大并发入口数,还可以选择指定系统信号量对象的名称,以及指定一个变量来接收指示是否创建了新系统信号量的值。 |
Semaphore 使用纯粹的内核时间(kernel-time)方式(等待时间很短),并且支持在不同的进程间同步线程(像Mutex)。
Semaphore 常用方法如下:
| 方法 | 说明 |
|---|---|
| Close() | 释放由当前 WaitHandle占用的所有资源。 |
| OpenExisting(String) | 打开指定名称为信号量(如果已经存在)。 |
| Release() | 退出信号量并返回前一个计数。 |
| Release(Int32) | 以指定的次数退出信号量并返回前一个计数。 |
| TryOpenExisting(String, Semaphore) | 打开指定名称为信号量(如果已经存在),并返回指示操作是否成功的值。 |
| WaitOne() | 阻止当前线程,直到当前 WaitHandle 收到信号。 |
| WaitOne(Int32) | 阻止当前线程,直到当前 WaitHandle 收到信号,同时使用 32 位带符号整数指定时间间隔(以毫秒为单位)。 |
| WaitOne(Int32, Boolean) | 阻止当前线程,直到当前的 WaitHandle 收到信号为止,同时使用 32 位带符号整数指定时间间隔,并指定是否在等待之前退出同步域。 |
| WaitOne(TimeSpan) | 阻止当前线程,直到当前实例收到信号,同时使用 TimeSpan 指定时间间隔。 |
| WaitOne(TimeSpan, Boolean) | 阻止当前线程,直到当前实例收到信号为止,同时使用 TimeSpan 指定时间间隔,并指定是否在等待之前退出同步域。 |
示例
我们来直接写代码,这里使用 《原子操作 Interlocked》 中的示例,现在我们要求,采用多个线程执行计算,但是只允许最多三个线程同时执行运行。
使用 Semaphore ,有四个个步骤:
new 实例化 Semaphore,并设置最大线程数、初始化时可进入线程数;
使用 .WaitOne(); 获取进入权限(在获得进入权限前,线程处于阻塞状态)。
离开时使用 Release() 释放占用。
Close() 释放Semaphore 对象。
《原子操作 Interlocked》 中的示例改进如下:
class Program
{
// 求和
private static int sum = 0;
private static Semaphore _pool;
// 判断十个线程是否结束了。
private static int isComplete = 0;
// 第一个程序
static void Main(string[] args)
{
Console.WriteLine("执行程序");
// 设置允许最大三个线程进入资源池
// 一开始设置为0,就是初始化时允许几个线程进入
// 这里设置为0,后面按下按键时,可以放通三个线程
_pool = new Semaphore(0, 3);
for (int i = 0; i < 10; i++)
{
Thread thread = new Thread(new ParameterizedThreadStart(AddOne));
thread.Start(i + 1);
}
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("任意按下键(不要按关机键),可以打开资源池");
Console.ForegroundColor = ConsoleColor.White;
Console.ReadKey();
// 准许三个线程进入
_pool.Release(3);
// 这里没有任何意义,就单纯为了演示查看结果。
// 等待所有线程完成任务
while (true)
{
if (isComplete >= 10)
break;
Thread.Sleep(TimeSpan.FromSeconds(1));
}
Console.WriteLine("sum = " + sum);
// 释放池
_pool.Close();
}
public static void AddOne(object n)
{
Console.WriteLine($" 线程{(int)n}启动,进入队列");
// 进入队列等待
_pool.WaitOne();
Console.WriteLine($"第{(int)n}个线程进入资源池");
// 进入资源池
for (int i = 0; i < 10; i++)
{
Interlocked.Add(ref sum, 1);
Thread.Sleep(TimeSpan.FromMilliseconds(500));
}
// 解除占用的资源池
_pool.Release();
isComplete += 1;
Console.WriteLine($" 第{(int)n}个线程退出资源池");
}
}
看着代码有点多,快去运行一下,看看结果。
示例说明
实例化 Semaphore 使用了new Semaphore(0,3); ,其构造函数原型为
public Semaphore(int initialCount, int maximumCount);
initialCount 表示一开始允许几个进程进入资源池,如果设置为0,所有线程都不能进入,要一直等资源池放通。
maximumCount 表示最大允许几个线程进入资源池。
Release() 表示退出信号量并返回前一个计数。这个计数指的是资源池还可以进入多少个线程。
可以看一下下面的示例:
private static Semaphore _pool;
static void Main(string[] args)
{
_pool = new Semaphore(0, 5);
_pool.Release(5);
new Thread(AddOne).Start();
Thread.Sleep(TimeSpan.FromSeconds(10));
_pool.Close();
}
public static void AddOne()
{
_pool.WaitOne();
Thread.Sleep(1000);
int count = _pool.Release();
Console.WriteLine("在此线程退出资源池前,资源池还有多少线程可以进入?" + count);
}
信号量
前面我们学习到 Mutex,这个类是全局操作系统起作用的。我们从 Mutex 和 Semphore 中,也看到了 信号量这个东西。
信号量分为两种类型:本地信号量和命名系统信号量。
- 命名系统信号量在整个操作系统中均可见,可用于同步进程的活动。
- 局部信号量仅存在于进程内。
当 name 为 null 或者为空时,Mutex 的信号量时局部信号量,否则 Mutex 的信号量是命名系统信号量。
Semaphore 的话,也是两种情况都有。
如果使用接受名称的构造函数创建 Semaphor 对象,则该对象将与该名称的操作系统信号量关联。
两个构造函数:
Semaphore(Int32, Int32, String)
Semaphore(Int32, Int32, String, Boolean)
上面的构造函数可以创建多个表示同一命名系统信号量的 Semaphore 对象,并可以使用 OpenExisting 方法打开现有的已命名系统信号量。
我们上面使用的示例就是局部信号量,进程中引用本地 Semaphore 对象的所有线程都可以使用。 每个 Semaphore 对象都是单独的本地信号量。
SemaphoreSlim类
SemaphoreSlim 跟 Semaphore 有啥关系?
我看一下书再回答你。

哦哦哦,微软文档说:
SemaphoreSlim 表示对可同时访问资源或资源池的线程数加以限制的 Semaphore 的轻量替代。
SemaphoreSlim 不使用信号量,不支持进程间同步,只能在进程内使用。
它有两个构造函数:
| 构造函数 | 说明 |
|---|---|
| SemaphoreSlim(Int32) | 初始化 SemaphoreSlim 类的新实例,以指定可同时授予的请求的初始数量。 |
| SemaphoreSlim(Int32, Int32) | 初始化 SemaphoreSlim 类的新实例,同时指定可同时授予的请求的初始数量和最大数量。 |
示例
我们改造一下前面 Semaphore 中的示例:
class Program
{
// 求和
private static int sum = 0;
private static SemaphoreSlim _pool;
// 判断十个线程是否结束了。
private static int isComplete = 0;
static void Main(string[] args)
{
Console.WriteLine("执行程序");
// 设置允许最大三个线程进入资源池
// 一开始设置为0,就是初始化时允许几个线程进入
// 这里设置为0,后面按下按键时,可以放通三个线程
_pool = new SemaphoreSlim(0, 3);
for (int i = 0; i < 10; i++)
{
Thread thread = new Thread(new ParameterizedThreadStart(AddOne));
thread.Start(i + 1);
}
Console.WriteLine("任意按下键(不要按关机键),可以打开资源池");
Console.ReadKey();
//
_pool.Release(3);
// 这里没有任何意义,就单纯为了演示查看结果。
// 等待所有线程完成任务
while (true)
{
if (isComplete >= 10)
break;
Thread.Sleep(TimeSpan.FromSeconds(1));
}
Console.WriteLine("sum = " + sum);
// 释放池
}
public static void AddOne(object n)
{
Console.WriteLine($" 线程{(int)n}启动,进入队列");
// 进入队列等待
_pool.Wait();
Console.WriteLine($"第{(int)n}个线程进入资源池");
// 进入资源池
for (int i = 0; i < 10; i++)
{
Interlocked.Add(ref sum, 1);
Thread.Sleep(TimeSpan.FromMilliseconds(200));
}
// 解除占用的资源池
_pool.Release();
isComplete += 1;
Console.WriteLine($" 第{(int)n}个线程退出资源池");
}
}
SemaphoreSlim 不需要 Close()。
两者在代码上的区别是就这么简单。
区别
如果使用下面的构造函数实例化 Semaphor(参数name不能为空),那么创建的对象在整个操作系统内都有效。
public Semaphore (int initialCount, int maximumCount, string name);
Semaphorslim 则只在进程内内有效。
SemaphoreSlim 类不会对 Wait、WaitAsync 和 Release 方法的调用强制执行线程或任务标识。
而 Semaphor 类,会对此进行严格监控,如果对应调用数量不一致,会出现异常。
这就好像笔筒里面的笔,没有监控,使用这使用完毕后,都应该将笔放进去。如果原先有10支笔,每次使用不放进去,或者将别的地方的笔放进去,那么最后数量就不是10了。

C#多线程(5):资源池限制的更多相关文章
- Container and Injection in Java
一.Container 1.为什么使用Container 通常,瘦客户端多层应用程序很难编写,因为它们涉及处理事务和状态管理.多线程.资源池和其他复杂的低级细节的复杂代码行.基于组件和独立于平台的Ja ...
- 论container的前世今生
why Normally, thin-client multitiered applications are hard to write because they involve many lines ...
- C#多线程线程
“线程同步”的含义 当一个进程启动了多个线程时,如果需要控制这些线程的推进顺序(比如A线程必须等待B和C线程执行完毕之后才能继续执行),则称这些线程需要进行“线程同步(thread synchro ...
- java多线程(精华版)
在 Java 程序中使用多线程要比在 C 或 C++ 中容易得多,这是因为 Java 编程语言提供了语言级的支持.本文通过简单的编程示例来说明 Java 程序中的多线程是多么直观.读完本文以后,用户应 ...
- Servlet与多线程与IO操作
1.JVM内存模型相关概念 2.Java多线程并发深入理解 3.Servlet.设计模式.SpringMVC深入理解 4.Java基础遗漏点补充 数据库连接池:JDBC connection pool ...
- Java并发和多线程(二)Executor框架
Executor框架 1.Task?Thread? 很多人在学习多线程这部分知识的时候,容易搞混两个概念:任务(task)和线程(thread). 并发编程可以使我们的程序可以划分为多个分离的.独立运 ...
- Java 多线程间的通讯
在前一小节,介绍了在多线程编程中使用同步机制的重要性,并学会了如何实现同步的方法来正确地访问共享资源.这些线程之间的关系是平等的,彼此之间并不存在任何依赖,它们各自竞争CPU资源,互不相让,并且还无条 ...
- 如何提高多线程程序的cpu利用率
正如大家所知道的那样,多核多cpu越来越普遍了,而且编写多线程程序也是件很简单的事情.在Windows下面,调用CreateThread函数一次就能够以你想要的函数地址新建一个子线程运行.然后,事情确 ...
- C# 多线程详解
1.使用多线程的几种方式 (1)不需要传递参数,也不需要返回参数 ThreadStart是一个委托,这个委托的定义为void ThreadStart(),没有参数与返回值. 复制代码 代码如下: cl ...
- OpenVPN多处理之-多队列TUN多线程
1.有一点不正确劲 在改动了那个TUN驱动后,我在想,为何我总是对一些驱动程序进行修修补补而从来不从应用程序找解决方式呢?我改动了那个TUN驱动,可是能保证我的改动对别的应用一样可用吗?难道TUN驱动 ...
随机推荐
- 【团队效率提升】Python-PyWebIO介绍
作者:京东零售 关键 Q&A快速了解PyWebIO Q:首先,什么是PyWebIO? A:PyWebIO提供了一系列命令式的交互函数,能够让咱们用只用Python就可以编写 Web 应用, 不 ...
- 谈JVM参数GC线程数ParallelGCThreads合理性设置
作者:京东零售 刘乐 导读:本篇文章聚焦JVM参数GC线程数的合理配置,从ParallelGCThreads参数含义.参数设置,到参数实验以及修改意见进行解析. 1. ParallelGCThread ...
- [postgres]配置主从异步流复制
前言 环境信息 IP 角色 操作系统 PostgreSQL版本 192.168.1.112 主库 Debian 12 15.3 192.168.1.113 从库 Debian 12 15.3 配置主从 ...
- 使用 NuGet.Server 创建和部署 ASP.NET Web 应用程序搭建私有Nuget服务器
使用 NuGet.Server 创建和部署 ASP.NET Web 应用程序搭建私有Nuget服务器 在Visual Studio中,选择"新建>文件>"Project ...
- Pytest 源码解读 [1] - [pluggy] 核心设计理念浅读
背景: Pytest 是一个功能强大的 Python 测试框架,它使用了一个名为 "pluggy" 的插件系统来扩展其功能.在 Pytest 的源码中,pluggy 模块负责实现插 ...
- 【一】分布式训练---单机多卡多机多卡(飞桨paddle1.8)
1.分布式训练简介 分布式训练的核心目的: 加快模型的训练速度.通过对训练任务按照一定方法拆分分配到多个计算节点进行计算,再按照一定的方法对需要汇总的信息进行聚合,从而实现加快训练速度的目的. 1.1 ...
- OpenSSH 服务配置与管理
SSH 是一种以安全的方式提供远程登陆的协议,也是目前远程管理Linux系统的首选方式,SSH由IETF的网络小组所制定,SSH为建立在应用层基础上的安全协议,SSH是目前较可靠,专为远程登录会话和其 ...
- Linux的用户和权限 [补档-2023-07-07]
Linux用户和权限 3-1. su用户切换命令 exit用户退出命令 用户切换命令的语法: su [-] [用户名] 其中: - 可选,表示是否在切换用户后加载环境变量,建议带上. ...
- 从嘉手札<2023-11-18>
随便补一个~ 1.我也不是不快乐,我其实挺快乐的,和朋友出去玩,看电影,刷搞笑视频,我都能表现的很好,但这些都不是真正让我感受到快乐的东西,它就像膝跳反应一样,碰我一下我就会笑,但笑完就结束了.甚至在 ...
- iOS 常用命令行工具总结
平时工作中会经常用到命令行工具Command Lines Tool.而Command Line Tool本质是一个命令行工具包,内部有很多有用的工具,如Apple LLVM compiler.Make ...