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 类不会对 WaitWaitAsyncRelease 方法的调用强制执行线程或任务标识。

而 Semaphor 类,会对此进行严格监控,如果对应调用数量不一致,会出现异常。

此外,如果使用 SemaphoreSlim(Int32 maximumCount) 构造函数来实例化 SemaphoreSlim 对象,获取其 CurrentCount 属性,其值可能会大于 maximumCount。 编程人员应负责确保调用一个 Wait 或 WaitAsync 方法,便调用一个 Release。

这就好像笔筒里面的笔,没有监控,使用这使用完毕后,都应该将笔放进去。如果原先有10支笔,每次使用不放进去,或者将别的地方的笔放进去,那么最后数量就不是10了。

C#多线程(5):资源池限制的更多相关文章

  1. Container and Injection in Java

    一.Container 1.为什么使用Container 通常,瘦客户端多层应用程序很难编写,因为它们涉及处理事务和状态管理.多线程.资源池和其他复杂的低级细节的复杂代码行.基于组件和独立于平台的Ja ...

  2. 论container的前世今生

    why Normally, thin-client multitiered applications are hard to write because they involve many lines ...

  3. C#多线程线程

    “线程同步”的含义   当一个进程启动了多个线程时,如果需要控制这些线程的推进顺序(比如A线程必须等待B和C线程执行完毕之后才能继续执行),则称这些线程需要进行“线程同步(thread synchro ...

  4. java多线程(精华版)

    在 Java 程序中使用多线程要比在 C 或 C++ 中容易得多,这是因为 Java 编程语言提供了语言级的支持.本文通过简单的编程示例来说明 Java 程序中的多线程是多么直观.读完本文以后,用户应 ...

  5. Servlet与多线程与IO操作

    1.JVM内存模型相关概念 2.Java多线程并发深入理解 3.Servlet.设计模式.SpringMVC深入理解 4.Java基础遗漏点补充 数据库连接池:JDBC connection pool ...

  6. Java并发和多线程(二)Executor框架

    Executor框架 1.Task?Thread? 很多人在学习多线程这部分知识的时候,容易搞混两个概念:任务(task)和线程(thread). 并发编程可以使我们的程序可以划分为多个分离的.独立运 ...

  7. Java 多线程间的通讯

    在前一小节,介绍了在多线程编程中使用同步机制的重要性,并学会了如何实现同步的方法来正确地访问共享资源.这些线程之间的关系是平等的,彼此之间并不存在任何依赖,它们各自竞争CPU资源,互不相让,并且还无条 ...

  8. 如何提高多线程程序的cpu利用率

    正如大家所知道的那样,多核多cpu越来越普遍了,而且编写多线程程序也是件很简单的事情.在Windows下面,调用CreateThread函数一次就能够以你想要的函数地址新建一个子线程运行.然后,事情确 ...

  9. C# 多线程详解

    1.使用多线程的几种方式 (1)不需要传递参数,也不需要返回参数 ThreadStart是一个委托,这个委托的定义为void ThreadStart(),没有参数与返回值. 复制代码 代码如下: cl ...

  10. OpenVPN多处理之-多队列TUN多线程

    1.有一点不正确劲 在改动了那个TUN驱动后,我在想,为何我总是对一些驱动程序进行修修补补而从来不从应用程序找解决方式呢?我改动了那个TUN驱动,可是能保证我的改动对别的应用一样可用吗?难道TUN驱动 ...

随机推荐

  1. MYSQL使用mydumper备份恢复操作简介

    MYSQL使用mydumper备份恢复操作简介 1. 环境准备 第一步是进行下载安装包的操作. 在github以及官网上面有相关的安装介质. 官网为: http://www.mydumper.org/ ...

  2. charles如何抓取https请求

    我们都知道charles下载安装后只能抓取http请求,要想抓取https请求需要下载安装证书 下面介绍pc端和移动端的配置方法 一.pc端(win) 1.打开charles,点击help>SS ...

  3. adb基础指令

    1.查看连接的设备 adb.exe  devices/adb devices device表示连接成功,offline表示设备未连接或未响应 2.进入命令模式,指定哪台设备 adb  -s  序列号  ...

  4. widows 安装docker

    1.安装docker 依次安装如下两个文件: 如启动docker报错:可以是hv没有开启,按如下方法解决 (23条消息) Windows10启动Docker报错:Hardware assisted v ...

  5. c++基础之表达式

    这次接着更新<c++ primer> 这本书的读书笔记,上一篇博文更新到了书中的第三章,本次将记录书中的第四章--表达式 左值与右值 在理解表达式之前需要先理解c++中左值和右值的概念. ...

  6. 通过docker-compose搭建mongo的replica set高可用

    通过docker-compose搭建mongo的replica set高可用 前言 备份数据 备份数据到本地 数据恢复 集群搭建 生成keyFile 创建yml文件 初始化副本集 增加副本集 将节点初 ...

  7. Linux 文件目录操作命令

    Linux 基础的文件目录操作命令,融合多部Linux经典著作,去除多余部分,保留实用部分. 显示目录或文件: 显示目标列表,在Linux系统中是使用率较高的命令.ls命令的输出信息可以进行彩色加亮显 ...

  8. 47从零开始用Rust编写nginx,配对还有这么多要求!负载均衡中的路径匹配

    wmproxy wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 负载均衡, 静态文件服务器,websocket代理,四层TCP/UDP转发,内网穿透等,会将实 ...

  9. ajax中的同步异步和跨域请求

    ajax中的同步异步和跨域请求 同步异步 demo.html <script> $.ajax({ type: "get", async: false, data: &q ...

  10. HarmonyOS 实战小项目开发(一)

    HarmonyOS 实战小项目开发(一) 日常逼逼叨 在经过一周多的Harmonyos 开发基础知识的学习后,自己通过对于Harmonyos基础知识的学习之后,结合自己的一些想法,独自完成了利用Ark ...