广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C#多线程系列之资源池限制
  • 247
分享到

C#多线程系列之资源池限制

2024-04-02 19:04:59 247人浏览 薄情痞子
摘要

Semaphore、SemaphoreSlim 类 两者都可以限制同时访问某一资源或资源池的线程数。 这里先不扯理论,我们从案例入手,通过示例代码,慢慢深入了解。 Semaphore

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

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

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

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

到此这篇关于C#多线程系列之资源池限制的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持编程网。

--结束END--

本文标题: C#多线程系列之资源池限制

本文链接: https://www.lsjlt.com/news/138996.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • C#多线程系列之资源池限制
    Semaphore、SemaphoreSlim 类 两者都可以限制同时访问某一资源或资源池的线程数。 这里先不扯理论,我们从案例入手,通过示例代码,慢慢深入了解。 Semaphore...
    99+
    2022-11-13
  • C#多线程系列之线程池
    目录线程池ThreadPool 常用属性和方法线程池说明和示例线程池线程数线程池线程数说明不支持的线程池异步委托任务取消功能计时器线程池 线程池全称为托管线程池,线程池受 .NET ...
    99+
    2022-11-13
  • C#多线程系列之线程通知
    AutoRestEvent 类用于从一个线程向另一个线程发送通知。 微软文档是这样介绍的:表示线程同步事件在一个等待线程释放后收到信号时自动重置。 其构造函数只有一个: 构造函数里面...
    99+
    2022-11-13
  • C#多线程系列之线程等待
    目录前言volatile 关键字三种常用等待再说自旋和阻塞SpinWait 结构属性和方法自旋示例新的实现SpinLock 结构属性和方法示例等待性能对比前言 volatile 关键...
    99+
    2022-11-13
  • C#多线程系列之线程完成数
    解决一个问题 假如,程序需要向一个 Web 发送 5 次请求,受网路波动影响,有一定几率请求失败。如果失败了,就需要重试。 示例代码如下: class Program ...
    99+
    2022-11-13
  • C#多线程系列之多线程锁lock和Monitor
    目录1,Locklock 原型lock 编写实例2,Monitor怎么用呢解释一下示例设置获取锁的时效1,Lock lock 用于读一个引用类型进行加锁,同一时刻内只有一个线程能够访...
    99+
    2022-11-13
  • C#多线程系列之多阶段并行线程
    前言 这一篇,我们将学习用于实现并行任务、使得多个线程有序同步完成多个阶段的任务。 应用场景主要是控制 N 个线程(可随时增加或减少执行的线程),使得多线程在能够在 M 个阶段中保持...
    99+
    2022-11-13
  • C#多线程系列之读写锁
    本篇的内容主要是介绍 ReaderWriterLockSlim 类,来实现多线程下的读写分离。 ReaderWriterLockSlim ReaderWriterLock 类:定义支...
    99+
    2022-11-13
  • C#多线程系列之手动线程通知
    区别与示例 AutoResetEvent 和 ManualResetEvent 十分相似。两者之间的区别,在于前者是自动(Auto),后者是手动(Manua)。 你可以先运行下面的示...
    99+
    2022-11-13
  • C#多线程系列之原子操作
    目录知识点竞争条件线程同步CPU时间片和上下文切换阻塞内核模式和用户模式Interlocked类1,出现问题2,Interlocked.Increment()3,Interlocke...
    99+
    2022-11-13
  • C#多线程系列之进程同步Mutex类
    Mutex 中文为互斥,Mutex 类叫做互斥锁。它还可用于进程间同步的同步基元。 Mutex 跟 lock 相似,但是 Mutex 支持多个进程。Mutex 大约比 lock 慢 ...
    99+
    2022-11-13
  • C#多线程系列之任务基础(一)
    目录多线程编程多线程编程模式探究优点任务操作两种创建任务的方式Task.Run() 创建任务取消任务父子任务任务返回结果以及异步获取返回结果捕获任务异常全局捕获任务异常多线程编程 多...
    99+
    2022-11-13
  • C#多线程系列之任务基础(二)
    目录判断任务状态再说父子任务组合任务/延续任务复杂的延续任务并行(异步)处理任务并行(同步)处理任务并行任务的 Task.WhenAny并行任务状态循环中值变化问题定时任务 Task...
    99+
    2022-11-13
  • C#多线程系列之任务基础(三)
    目录TaskAwaiter延续的另一种方法另一种创建任务的方法实现一个支持同步和异步任务的类型Task.FromCanceled()如何在内部取消任务Yield 关键字补充知识点Ta...
    99+
    2022-11-13
  • C#多线程系列之工作流实现
    目录前言节点ThenParallelScheduleDelay试用一下顺序节点并行任务编写工作流接口构建器工作流构建器依赖注入实现工作流解析前言 前面学习了很多多线程和任务的基础知识...
    99+
    2022-11-13
  • C#多线程系列之线程的创建和生命周期
    目录1,获取当前线程信息2,管理线程状态2.1启动与参数传递2.1.1ParameterizedThreadStart2.1.2使用静态变量或类成员变量2.1.3委托与Lambda2...
    99+
    2022-11-13
  • C#多线程系列之async和await用法详解
    目录async和awaitasyncawait从以往知识推导创建异步任务创建异步任务并返回Task异步改同步说说 await Task说说 async Task<TR...
    99+
    2022-11-13
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作