热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

C#多线程(5):资源池限制

目录Semaphore、SemaphoreSlim类Semaphore类示例示例说明信号量SemaphoreSlim类示例区别两者都可以限制同时访问某一资源或资源池的线程数。这里先

目录
  • Semaphore、SemaphoreSlim 类
    • 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 有啥关系?

我看一下书再回答你。

C#多线程(5):资源池限制

哦哦哦,微软文档说:

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):资源池限制


推荐阅读
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
author-avatar
流浪汉中的小男人
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有