再说多线程(四)——Semaphore类

news2024/11/17 9:49:20

Semaphore提供了更精细化的多线程控制,如果你看过上一节的Mutex介绍,那么你应该很容易理解Semaphore类。我们直接先以例子开头,然后在介绍这个类。

1.Semaphore实例

看下面的代码:

using System;
using System.Threading;

namespace SemaphoreDemo
{
    class Program
    {
        public static Semaphore semaphore = null;

        static void Main(string[] args)
        {
            try
            {
                //Try to Open the Semaphore if Exists, if not throw an exception
                semaphore = Semaphore.OpenExisting("SemaphoreDemo");
            }
            catch(Exception Ex)
            {
                //If Semaphore not Exists, create a semaphore instance
                //Here Maximum 2 external threads can access the code at the same time
                semaphore = new Semaphore(2, 2, "SemaphoreDemo");
            }

            Console.WriteLine("External Thread Trying to Acquiring");
            semaphore.WaitOne();
            //This section can be access by maximum two external threads: Start
            Console.WriteLine("External Thread Acquired");
            Console.ReadKey();
            //This section can be access by maximum two external threads: End
            semaphore.Release();
        }
    }
}

现在你编译生成可执行文件后,连续点击三次,就可以看到如下情形:

如果你读到这儿,完全看懂了Semaphore的代码,我想你可能不需要继续往下读了。没错,Semaphore就是控制外部线程数量,从而可以让多个实例运行。

2.Semaphore类介绍

C#中的信号量类用于限制可以同时访问共享资源的外部线程的数量。换句话说,我们可以说Semaphore允许一个或多个外部线程进入临界区并在线程安全的情况下并发执行任务。因此,在实时情况下,当我们的资源数量有限并且我们想限制可以使用它的线程数量时,我们需要使用信号量。

2.1构造函数

C# 中的 Semaphore 类提供了以下四个构造函数,我们可以使用它们来创建 Semaphore 类的实例。

  1. Semaphore(int initialCount, int maximumCount):它初始化 Semaphore 类的新实例,指定初始条目数和最大并发条目数。

  1. Semaphore(int initialCount, int maximumCount, string name):它初始化 Semaphore 类的新实例,指定初始条目数和最大并发条目数,并可选地指定系统信号量对象的名称。

  1. Semaphore(int initialCount, int maximumCount, string name, out bool createdNew):它初始化了一个新的Semaphore类实例,指定初始条目数和最大并发条目数,可选地指定系统信号量对象的名称,并指定一个变量,该变量接收一个值,该值指示是否创建了一个新的系统信号量。

  1. Semaphore(int initialCount, int maximumCount, string name, out bool createdNew, SemaphoreSecurity semaphoreSecurity):它初始化一个Semaphore类的新实例,指定初始条目数和最大并发条目数,可选地指定系统名称信号量对象,指定一个变量,该变量接收一个值,该值表示是否创建了一个新的系统信号量,并指定了对系统信号量的安全访问控制。

信号量类构造函数中使用的参数:

  1. initialCount:可以并发授予的信号量的初始请求数。如果 initialCount 大于 maximumCount,它会抛出 ArgumentException。

  1. maximumCount:可以同时授予的信号量的最大请求数。如果 maximumCount 小于 1 或 initialCount 小于 0,它将抛出 ArgumentOutOfRangeException。

  1. name:命名系统信号量对象的名称。

  1. createdNew:当此方法返回时,如果创建了本地信号量(即,如果名称为 null 或空字符串)或创建了指定的命名系统信号量,则包含 true;如果指定的命名系统信号量已经存在,则为 false。此参数在未初始化的情况下传递。

  1. semaphoreSecurity:一个 System.Security.AccessControl.SemaphoreSecurity 对象,表示要应用于指定系统信号量的访问控制安全性。

2.2成员函数

C#中的信号量类提供了以下方法:

  1. OpenExisting(string name):此方法用于打开指定的已命名信号量(如果它已存在)。它返回一个代表命名系统信号量的对象。这里的参数name指定了要打开的系统信号量的名称。如果名称为空字符串,它将抛出 ArgumentException。- 或 - 名称超过 260 个字符。如果名称为空,它将抛出 ArgumentNullException。

  1. OpenExisting(string name, SemaphoreRights rights):此方法用于打开指定的命名信号量(如果它已经存在)并具有所需的安全访问权限。它返回一个代表命名系统信号量的对象。这里的参数name指定了要打开的系统信号量的名称。参数权限指定代表所需安全访问的枚举值的按位组合。

  1. TryOpenExisting(string name, out Semaphore result):该方法用于打开指定命名的Semaphore,如果已经存在,则返回一个值,表示操作是否成功。这里的参数name指定了要打开的系统Semaphore的名称。当此方法返回时,结果包含一个 Semaphore 对象,如果调用成功则表示命名的 Semaphore,如果调用失败则为 null。此参数被视为未初始化。如果命名的互斥体被成功打开,它返回真;否则,假的。

  1. TryOpenExisting(string name, SemaphoreRights rights, out Semaphore result):此方法用于打开指定的命名信号量,如果它已经存在,具有所需的安全访问权限,并返回一个指示操作是否成功的值。这里的参数name指定了要打开的系统Semaphore的名称。参数权限指定代表所需安全访问的枚举值的按位组合。当此方法返回时,结果包含一个 Semaphore 对象,如果调用成功则表示命名的 Semaphore,如果调用失败则为 null。此参数被视为未初始化。如果命名的信号量打开成功,则返回真;否则,假的。

  1. Release():此方法退出信号量并返回先前的计数。它在调用 Release 方法之前返回信号量的计数。

  1. Release(int releaseCount):此方法退出信号量指定次数并返回之前的计数。这里,参数releaseCount指定退出信号量的次数。它在调用 Release 方法之前返回信号量的计数。

  1. GetAccessControl():此方法获取指定系统信号量的访问控制安全性。

  1. SetAccessControl(SemaphoreSecurity semaphoreSecurity):此方法设置命名系统信号量的访问控制安全性。

注意: C#中的Semaphore类继承自WaitHandle抽象类,WaitHandle类提供了我们需要调用的WaitOne()方法来锁定资源。请注意,信号量对象只能从获取它的同一个线程中释放。

2.3内部机制

  1. 信号量在 C# 中如何工作?

信号量是存储在操作系统资源中的 Int32 变量。当我们初始化信号量对象时,我们用一个数字初始化它。这个数字基本上用来限制可以进入临界区的线程。

因此,当线程进入临界区时,它会将 Int32 变量的值减 1,当线程退出临界区时,它会将 Int32 变量的值加 1。最重要的一点是您需要请记住,当 Int32 变量的值为 0 时,则没有线程可以进入临界区。

  1. 如何在 C# 中创建信号量?

您可以使用以下语句在 C# 中创建信号量实例。在这里,我们使用带有两个参数的构造函数的重载版本来创建信号量类的实例。

 semaphoreObject = new Semaphore(initialCount: 2, maximumCount: 3);

正如在上面的语句中看到的,我们在初始化时将两个值传递给 Semaphore 类的构造函数。这两个值代表 InitialCount 和 MaximumCount。maximumCount 定义最多有多少个线程可以进入临界区,initialCount 设置 Int32 变量的值。

InitialCount 参数设置 Int32 变量的值。也就是说,它定义了可以并发授予的信号量的初始请求数。MaximumCount 参数定义可以同时授予的信号量请求的最大数量。

例如,如果我们将最大计数值设置为 3,初始计数值为 0,则表示已经有 3 个线程处于临界区,因此不再有新线程可以进入临界区。如果我们设置最大计数值为 3,初始计数值为 2。这意味着最多 3 个线程可以进入临界区,并且当前有 1 个线程处于临界区,因此可以有两个新线程进入临界区部分。

注1:线程进入临界区时,initialCount变量值减1,线程退出临界区时,initialCount变量值加1。当initialCount变量值为0时,则没有线程可以进入临界区。第二个参数 maximumCount 总是必须等于或大于第一个参数 initialCount 否则我们会得到一个异常。

注2:当线程要退出临界区时,我们需要调用Release()方法。调用此方法时,它会增加由信号量对象维护的 Int32 变量。

3.Semaphore举例

看一个稍微复杂点的例子:

using System;
using System.Threading;

namespace SemaphoreDemo
{
    class Program
    {
        public static Semaphore semaphore = new Semaphore(2, 3);

        static void Main(string[] args)
        {
            for (int i = 1; i <= 10; i++)
            {
                Thread threadObject = new Thread(DoSomeTask)
                {
                    Name = "Thread " + i
                };
                threadObject.Start();
            }
            Console.ReadKey();
        }

        static void DoSomeTask()
        {

            Console.WriteLine(Thread.CurrentThread.Name + " Wants to Enter into Critical Section for processing");
            try
            {
                //Blocks the current thread until the current WaitHandle receives a signal.   
                semaphore.WaitOne();
                //Decrease the Initial Count Variable by 1
                Console.WriteLine("Success: " + Thread.CurrentThread.Name + " is Doing its work");
                Thread.Sleep(5000);
                Console.WriteLine(Thread.CurrentThread.Name + "Exit.");
            }
            finally
            {
                //Release() method to release semaphore  
                //Increase the Initial Count Variable by 1
                semaphore.Release();
            }
        }
    }
}

运行结果如下:

具体过程就不分析了。

值得注意的是Semaphore与操作系统是有关联的,上面的例子可以跨平台运行,但是第一个例子因为使用OpenExisting函数,在linux上运行可能会失败。

4.SemaphoreSlim

如果的你的程序允许多个实例,互不影响,建议在每隔实例内部使用 C# 中的 SemaphoreSlim 类进行同步。轻量级信号量控制对应用程序本地资源池的访问。它代表了信号量的轻量级替代方案,它限制了可以并发访问资源或资源池的线程数。

4.1SemaphoreSlim类的构造函数和方法

C# 中 SemaphoreSlim 类的构造函数

C# 中的 SemaphoreSlim 类提供了以下两个构造函数,我们可以使用它们来创建 SemaphoreSlim 类的实例。

  1. SemaphoreSlim(int initialCount):它初始化 SemaphoreSlim 类的新实例,指定可以并发授予的初始请求数。这里,参数initialCount指定了可以并发授予的信号量的初始请求数。如果 initialCount 小于 0,它将抛出 ArgumentOutOfRangeException。

  1. SemaphoreSlim(int initialCount, int maxCount):它初始化 SemaphoreSlim 类的一个新实例,指定可以同时授予的初始和最大请求数。这里,参数initialCount指定了可以并发授予的信号量的初始请求数。而参数 maxCount 指定了可以并发授予的信号量的最大请求数。如果 initialCount 小于 0,或者 initialCount 大于 maxCount,或者 maxCount 等于或小于 0,它将抛出 ArgumentOutOfRangeException。

C#中SemaphoreSlim类的方法:

C# 中的 SemaphoreSlim 类提供了以下方法。

在 SemaphoreSlim 类中有多个可用的 Wait 方法的重载版本。它们如下:

  1. Wait():它阻塞当前线程,直到它可以进入 System.Threading.SemaphoreSlim。

  1. Wait(TimeSpan timeout):它阻塞当前线程,直到它可以进入SemaphoreSlim,使用一个TimeSpan来指定超时。当前线程成功进入SemaphoreSlim则返回true;否则,假的。

  1. Wait(CancellationToken cancellationToken): 它阻塞当前线程,直到它可以进入 SemaphoreSlim,同时观察一个 CancellationToken。

  1. Wait(TimeSpan timeout, CancellationToken cancellationToken):它阻塞当前线程,直到它可以进入SemaphoreSlim,使用指定超时的TimeSpan,同时观察一个CancellationToken。当前线程成功进入SemaphoreSlim则返回true;否则,假的。

  1. Wait(int millisecondsTimeout):它阻塞当前线程,直到它可以进入 SemaphoreSlim,使用指定超时的 32 位带符号整数。当前线程成功进入SemaphoreSlim则返回true;否则,假的。

  1. Wait(int millisecondsTimeout, CancellationToken cancellationToken):它会阻塞当前线程,直到它可以进入 SemaphoreSlim,使用指定超时的 32 位带符号整数,同时观察 CancellationToken。当前线程成功进入SemaphoreSlim则返回true;否则,假的。

参数:

以下是 Wait 方法中使用的参数说明。

  1. timeout:表示等待毫秒数的 TimeSpan,表示 -1 毫秒无限期等待的 TimeSpan,或表示 0 毫秒的 TimeSpan 以测试等待句柄并立即返回。

  1. cancellationToken:要观察的 System.Threading.CancellationToken。

  1. millisecondsTimeout:等待的毫秒数,System.Threading.Timeout.Infinite(-1) 无限期等待,或零以测试等待句柄的状态并立即返回。

注意:上述所有方法的异步版本也可用

释放方式:

SemaphoreSlim 类中有两个 Release 方法的重载版本。它们如下:

  1. Release():释放一次 SemaphoreSlim 对象。它返回 SemaphoreSlim 的先前计数。

  1. Release(int releaseCount): 它释放 SemaphoreSlim 对象指定的次数。它返回 SemaphoreSlim 的先前计数。这里,参数releaseCount指定退出信号量的次数。

4.2SemaphoreSlim案例

这里直接贴一个官方的例子,比较典型了

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
    // Create the semaphore.
    private static SemaphoreSlim semaphore = new SemaphoreSlim(0, 3);

    // A padding interval to make the output more orderly.
    private static int padding;

    public static void Main()
    {
        Console.WriteLine($"{semaphore.CurrentCount} tasks can enter the semaphore");
        Task[] tasks = new Task[5];

        // Create and start five numbered tasks.
        for (int i = 0; i <= 4; i++)
        {
            tasks[i] = Task.Run(() =>
            {
                // Each task begins by requesting the semaphore.
                Console.WriteLine($"Task {Task.CurrentId} begins and waits for the semaphore");

                int semaphoreCount;
                semaphore.Wait();
                try
                {
                    Interlocked.Add(ref padding, 100);
                    Console.WriteLine($"Task {Task.CurrentId} enters the semaphore");
                    // The task just sleeps for 1+ seconds.
                    Thread.Sleep(1000 + padding);
                }
                finally
                {
                    semaphoreCount = semaphore.Release();
                }
                Console.WriteLine($"Task {Task.CurrentId} releases the semaphore; previous count: {semaphoreCount}");
            });
        }

        // Wait for one second, to allow all the tasks to start and block.
        Thread.Sleep(1000);

        // Restore the semaphore count to its maximum value.
        Console.Write("Main thread calls Release(3) --> ");
        semaphore.Release(3);
        Console.WriteLine($"{semaphore.CurrentCount} tasks can enter the semaphore");
        // Main thread waits for the tasks to complete.
        Task.WaitAll(tasks);

        Console.WriteLine("Main thread Exits");
        Console.ReadKey();
    }
}

因为SemaphoreSlim支持异步等待,所以个人觉得一般情况下用SemaphoreSlim可能更好。运行结果如下:

0 tasks can enter the semaphore
Task 2 begins and waits for the semaphore
Task 1 begins and waits for the semaphore
Task 4 begins and waits for the semaphore
Task 5 begins and waits for the semaphore
Task 3 begins and waits for the semaphore
Main thread calls Release(3) --> 3 tasks can enter the semaphore
Task 4 enters the semaphore
Task 1 enters the semaphore
Task 2 enters the semaphore
Task 2 releases the semaphore; previous count: 1
Task 4 releases the semaphore; previous count: 0
Task 1 releases the semaphore; previous count: 2
Task 5 enters the semaphore
Task 3 enters the semaphore
Task 3 releases the semaphore; previous count: 1
Task 5 releases the semaphore; previous count: 2
Main thread Exits

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/160436.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【爪洼岛冒险记】第3站:任务1:学会int和String之间的转换;任务2:将Java运算符全部收入囊中~ 任务3:拿下Java中的类型提升

&#x1f331;博主简介&#xff1a;是瑶瑶子啦&#xff0c;一名大一计科生&#xff0c;目前在努力学习C进阶,JavaSE。热爱写博客~正在努力成为一个厉害的开发程序媛&#xff01; &#x1f4dc;所属专栏&#xff1a;爪洼岛冒险记 ✈往期博文回顾:【爪洼岛冒险记】第2站&#xff…

Allegro172版本如何快速打开和关闭层面操作指导

Allegro172版本如何快速打开和关闭层面操作指导 在做PCB设计的时候,打开和关闭某个层面是非常频繁的操作,尤其是丝印等等层面。 Allgeo升级到了172版本的时候,可以将常用的层面添加到Visibility菜单里,就不需要频繁打开颜色管理器打卡和关闭层面了,如下图 具体操作如下 打…

常见锁策略,CAS,synchronized原理

1.常见锁策略锁策略不仅仅局限于java,任何与"锁"相关的话题(操作系统,数据库...),都会涉及到锁策略,这些策略是给锁的实现者用来参考的1.1乐观锁vs悲观锁这个不是两把具体的锁.而是两类锁,是在锁冲突的概率上进行区分的乐观锁指的是预测锁竞争不是很激烈(做的工作相对…

Ambari2.7.5安装Flink1.14

文章目录下载Flink配置安装源下载ambari-flink-service服务修改配置文件创建用户和组重启Ambari登录Ambari安装Flink提交Flink任务Flink 直接单独提交到 On Yarn指定Flink在Yarn跑的容器运行Flink异常异常1异常2异常3下载Flink配置安装源 wget https://archive.apache.org/dis…

Goby+AWVS 联动

系列文章 AWVS安装与激活 AWVS扫描Web应用程序 AWVS扫描报告分析 GobyAWVS 联动 1.Goby简介 Goby是针对目标企业梳理最全面的工具&#xff0c;同构goby可以清晰的扫描出ip地址开放的端口&#xff0c;以及端口对应的服务&#xff0c;于此同事会根据开放的端口及应用进行实战…

分享116个ASP源码,总有一款适合您

ASP源码 分享116个ASP源码&#xff0c;总有一款适合您 116个ASP源码下载链接&#xff1a;https://pan.baidu.com/s/1LEs24-feWjvhac6vzyVecg?pwdnosx 提取码&#xff1a;nosx 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&am…

2.线性表

##线性结构 基本特点&#xff1a;结构中各元素之间满足线性关系。 线性关系&#xff1a;数据元素之间存在一对一的关系 1.存在唯一的开始元素 2.存在唯一的终止元素 3.除了开始元素和终止元素&#xff0c;其他元素均有且仅有一个直接前驱元素和一个直接后驱元素。 所有元素可排…

Sass进阶指南 -- 写出更优雅的样式表

我以为已经会了&#xff0c;之前在公司写项目基本都是用sass写样式&#xff0c;十分顺手。直到有段时间&#xff0c;我准备参考Element Plus来设计自己组件库的工程结构&#xff0c;看到Element Plus那些优雅的sass用法时&#xff0c;我开始为我的浅薄和无知感到羞愧。这便开始…

Python学习笔记——集合

集合&#xff08;set&#xff09;是一个无序的不重复元素序列。可以使用大括号 { } 或者 set() 函数创建集合&#xff0c;注意&#xff1a;创建一个空集合必须用 set() 而不是 { }&#xff0c;因为 { } 是用来创建一个空字典。创建格式&#xff1a;parame {value01,value02,..…

LeetCode[313]超级丑数

难度&#xff1a;中等题目&#xff1a;超级丑数 是一个正整数&#xff0c;并满足其所有质因数都出现在质数数组 primes中。给你一个整数 n和一个整数数组 primes&#xff0c;返回第 n个 超级丑数 。题目数据保证第 n个 超级丑数 在 32-bit 带符号整数范围内。示例 1&#xff1a…

C++ 类和对象(三)

类和对象&#xff08;一&#xff09; 类和对象&#xff08;二&#xff09; 日期类 目录 一. 再谈构造函数 1.构造函数体赋值 2.初始化列表 3.隐式类型转换与explicit关键字 4.C11优化 二. static成员 1.概念 2.特性 三. 友元 1.友元函数 2.友元类 四. 内部…

Java 如何设计一款小游戏详细讲解(二)

继续上面(一)&#xff0c;Java 如何设计一款小游戏详细讲解(一)步骤步骤一接下来我们主要完成GameJFrame&#xff0c;就是拼图游戏的界面及代码。这里接下来完成菜单的设置&#xff0c;将主界面的代码放到一个方法中&#xff0c;像下面这样&#xff0c;接下来我们书写菜单的代码…

基于Python实现种差值方法(完整代码详细教程)

三种插值方法都是使用Python自己实现的。1.1 最近邻插值寻找每个中心点周围的八个点中有无未丢失的点&#xff0c;如果有的话就赋值为第一个找到的点&#xff0c;如果没有就扩大范围再次寻找&#xff0c;在最大范围内都找不到的话就跳过。1.2 双线性插值使用解方程的方法求解&a…

(十八)Threads异步和多线程(Thread、Threadpool、Task)-语言进阶2

Threads异步和多线程-语言进阶2一、Thread1. 线程启动2. 线程等待3.前台线程/后台线程4.扩展thread封装回调二、Threadpool1.线程池2.线程池使用3.ManualResetEvent 线程池等待三、Task1. Task启动方式2.waitall 、waitany1.waitall2.waitany3.WaitAll、waitany场景4. 应用&…

Spring为什么这么火 之 Spring的创建及存储、获取Bean对象

目录 1、创建Spring项目 1.1、创建一个Maven项目 1.2、添加Spring框架支持 1.3、添加启动类 2、存储Bean对象 2.1、创建Bean对象 2.2、将Bean对象注册到Spring容器中 3、获取、使用Bean对象 3.1、得到Spring上下文对象 使用ApplicationContext作为Spring的上下文 【更…

服务器相关命令(docker相关)

一:安装docker 之前安装过旧版本&#xff0c;使用以下命令可以卸载(整个copy过去): yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-selinux \docker-engine-selinux \docker-e…

AOP面向切面编程

AOP 面向切面编程 AOP是什么 AOP 为 Aspect Oriented Programming 的缩写&#xff0c;意为&#xff1a;面向切面编程&#xff0c;通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。 AOP 是 OOP 的延续&#xff0c;是软件开发中的一个热点&#xff0c;是 j…

【Linux】-- 环境变量

目录 环境变量 常见环境变量 HOME 和环境变量相关的命令 通过代码如何获取环境变量 环境变量参数 通过第三方变量environ获取 通过getenv函数来特定访问获取 通过setenv函数来特定访问获取 环境变量 命令&#xff1a;which ls 将命令ls的完整路径写入到标准输出。 #in…

用R语言理解洛必达法则

文章目录5 洛必达法则极限的种类洛必达法则作用于幂函数5 洛必达法则 极限的种类 令NNN为常数&#xff0c;则常规的极限运算大致有以下几种 ∞N∞∞⋇N∞(N̸0)N∔∞∞N−∞−∞N/∞0N/0∞N∞∞(N̸1)∞N∞(N̸0)\begin{matrix} &\infty\pm N\infty\quad&\infty\divi…

MySQL版本由5.7.37更新到5.7.39

一、前景 由于mysql5.7.37存在漏洞&#xff0c;影响系统安全&#xff0c;所以需要将mysql版本升级到5.7的最新版本5.7.39。 二、步骤 1、下载5.7.39的安装包&#xff1a; 下载链接如下&#xff1a; https://downloads.mysql.com/archives/get/p/23/file/mysql-5.7.39-1.el…