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》 中的示例,如今咱们要求,采用多个线程执行计算,可是只容许最多三个线程同时执行运行。code

使用 Semaphore ,有四个个步骤:对象

new 实例化 Semaphore,并设置最大线程数、初始化时可进入线程数;blog

使用 .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了。

相关文章
相关标签/搜索