8

C# 多线程访问之 SemaphoreSlim(信号量)【C# 进阶】 - 橙子家

 1 year ago
source link: https://www.cnblogs.com/czzj/p/16866137.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

C# 多线程访问之 SemaphoreSlim(信号量)【C# 进阶】



SemaphoreSlim 是对可同时访问某一共享资源或资源池的线程数加以限制的 Semaphore 的轻量替代,也可在等待时间预计很短的情况下用于在单个进程内等待。

由于 SemaphoreSlim 更加轻量、快速,因此推荐使用,本文也着重介绍。

相较于线程锁的使一块代码只能一个线程访问,SemaphoreSlim 则是让同一块代码让多个线程同时访问,并且总数量可控。

SemaphoreSlim 尽可能多地依赖公共语言运行时 (CLR) 提供的同步基元。 还提供延迟初始化、基于内核的等待句柄。

SemaphoreSlim 也支持使用取消标记,但不支持命名信号量或使用用于同步的等待句柄。

线程通过调用从 WaitHandle 类中继承的 WaitOne 方法进入信号量,无论对于 System.Threading.Semaphore 对象、SemaphoreSlim.Wait 或 SemaphoreSlim.WaitAsync 方法还是 SemaphoreSlim 对象都适用。

当调用返回时,信号量计数会减少,当线程请求进入且计数为零时,此线程受到阻止。 线程通过调用 Semaphore.Release 或 SemaphoreSlim.Release 方法释放信号量时,允许受阻线程进入,此时信号量计数会增加。

受阻线程进入信号量无保证的顺序,比如先进先出 (FIFO) 或按后进先出 (LIFO)。

二、用法示例

关于 SemaphoreSlim、Wait()、Release() 的一个示例。

特别注意:若初始信号量为 0,则需要手动释放(Release())信号量。

class Program
{
    private static SemaphoreSlim semaphore;
    private static int padding; // 增加固定时间间隔,使输出更有序
    static void Main(string[] args)
    { 
        // 创建 semaphore 对象
        semaphore = new SemaphoreSlim(0, 4); // (初始数量,最大数量)
        Console.WriteLine($"semaphore 中现有 {semaphore.CurrentCount} 个信号量");
        Task[] tasks = new Task[10];
        for (int i = 0; i <= 9; i++)
        {
            tasks[i] = Task.Run(() =>
            {
                Console.WriteLine($"任务 {Task.CurrentId} 准备进入 semaphore");
                int semaphoreCount;
                semaphore.Wait(); // 调用 Wait() 方法,标记等待进入信号量
                try
                {
                    Interlocked.Add(ref padding, 100);
                    Console.WriteLine($"任务 {Task.CurrentId} 进入 semaphore");
                    Thread.Sleep(1000 + padding);
                }
                finally
                {
                    semaphoreCount = semaphore.Release(); // 调用 Release() 方法,释放信号量
                    // semaphoreCount:释放当前信号量之前的信号量
                }
                Console.WriteLine($"任务 {Task.CurrentId} 完成,释放 semaphore 一个信号量;释放前信号量:{semaphoreCount}");
            });
        }
        Thread.Sleep(500); // 暂时阻塞主线程,让全部线程到位
        Console.Write("主线程调用 Release(4) 释放四个信号量--> ");
        semaphore.Release(4); // 由于初始数量为 0 所以需要手动释放信号量
        Console.WriteLine($"semaphore 现有信号量 {semaphore.CurrentCount}");
        Task.WaitAll(tasks); // 等待全部线程完成
        Console.WriteLine("主线程退出");
    }
}

 输出结果:

  

1868241-20221107170203499-563956916.png

 三、属性 or 函数 or 方法释义

属性-AvailableWaitHandle

  返回一个 WaitHandle 对象,即封装等待对共享资源的独占访问的操作系统对象。

属性-CurrentCount

  指的是对于 SemaphoreSlim 对象,可以输入信号量的剩余线程数。

  属性的初始值 CurrentCount 由对类构造函数的 SemaphoreSlim 调用设置。 每次对 Wait 或 WaitAsync 方法的调用会递减,并按对 Release 方法的每此调用递增。

构造方法-SemaphoreSlim(Int32)

public SemaphoreSlim (int initialCount);

  初始化 SemaphoreSlim 类的新实例,以指定可同时授予的请求的初始数量,但未指定最大请求数。

  若初始数量为 0,则需要手动释放指定数量的信号量;若小于 0,则抛出异常:ArgumentOutOfRangeException。

构造方法-SemaphoreSlim(Int32, Int32)

public SemaphoreSlim (int initialCount, int maxCount);

  初始化 SemaphoreSlim 类的新实例,同时指定可同时授予的请求的初始数量和最大数量

  若initialCount 小于 0,或 initialCount 大于 maxCount,或 maxCount 小于等于 0,则抛出异常:ArgumentOutOfRangeException。

方法-Dispose

   用于释放由 SemaphoreSlim 类的当前实例使用的所有资源。

  SemaphoreSlim 与大多数成员不同,Dispose 不是线程安全的,不能与此实例的其他成员同时使用。

// 若要释放托管资源和非托管资源,则为 true(缺省默认);
// 若仅释放非托管资源,则为 false
protected virtual void Dispose (bool disposing);

 方法-Release

  对 Release() 方法的调用将属性递增一个 CurrentCount 数。 如果在调用此方法之前 CurrentCount 属性值为零,该方法还允许调用 Wait 或 WaitAsync 方法,阻止一个线程或任务进入信号灯。

// 释放 SemaphoreSlim 对象指定的次数
public int Release (int releaseCount);

方法-Wait

  阻止当前线程,直至它可进入 SemaphoreSlim 对象为止。

// 使用 TimeSpan 来指定超时,同时监视取消动作 CancellationToken
[System.Runtime.Versioning.UnsupportedOSPlatform("browser")]
public bool Wait (TimeSpan timeout, System.Threading.CancellationToken cancellationToken);
// TimeSpan 表示要等待的毫秒数,-1 代表无限等待,0 代表立即返回-测试用例
// 阻止当前线程,直至它可进入 SemaphoreSlim 为止
// 并使用 32 位带符号整数来指定超时(-1 代表无限等待),同时监视取消操作 CancellationToken
[System.Runtime.Versioning.UnsupportedOSPlatform("browser")]
public bool Wait (int millisecondsTimeout, System.Threading.CancellationToken cancellationToken);
// 返回值 bool:如果当前线程成功进入 SemaphoreSlim,则为 true;否则为 false
// 阻止当前线程,直至它可进入 SemaphoreSlim 为止
[System.Runtime.Versioning.UnsupportedOSPlatform("browser")]
public void Wait ();

方法-WaitAsync

  此为 Wait 方法的异步方式,优势在于对线程不会独占,即不会独占当前线程直到释放信号量。

  若将本文第二部分中的代码:(两处修改)

semaphore.Wait();
// 1/2 改为以下,异步方式
semaphore.WaitAsync();

// 2/2 并记录线程 ID(在如下位置添加一行打印信息)
try
{
    Console.WriteLine($"任务 {Task.CurrentId} 进入 semaphore");
    Console.WriteLine($"ProcessorId {Thread.GetCurrentProcessorId()}");// 新增行
    Interlocked.Add(ref padding, 100);
    Thread.Sleep(1000 + padding);
}

  输出的结果:(可见打印出来的线程 ID 有相同的情况,说明并非独占)

   

1868241-20221107183514589-1494575807.png

参考官方:SemaphoreSlim 类

注:个人记录,有问题欢迎指正。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK