当我们需要对一个或多个资源进行控制时,可以使用信号量。信号量是一种同步原语,它可以被用来跟踪资源的可用性。在这篇文章中,我们将会讲解C#中的信号量用法,包括信号量的基本操作和信号量用法的示例。
基本用法
在C#中,信号量可以通过System.Threading命名空间的Semaphore类来实现。Semaphore类封装了Windows内核对象,可以根据需要增加或删除元素。常用的构造函数有以下两种:
- Semaphore(int initialCount, int maximumCount):初始化一个 Semaphore 实例,它同时限制了同时访问信号量的线程数量。
initialCount
参数指定最初可使用的锁定数量;maximumCount
指定信号量的最大值,最大值不能小于initialCount
值。 - Semaphore(int initialCount, int maximumCount, string name, out bool createdNew):初始化具有指定名称的 Semaphore 对象,如果 Semaphore 对象不存在,则新建一个。
name
参数指定 Semaphore 实例的名称,createdNew
参数指示是否创建了 Semaphore 实例。
Semaphore类有以下一些常见的方法:
- WaitOne():尝试减小 Semaphore 对象的可用计数器。如果当前可用的计数器为 0,则会阻止调用线程,直到 Semaphore 可用为止。
- WaitForMultipleObjects(IntPtr[], bool, int):暂停当前线程,直到在多个等待线程句柄中的一个变为终止状态或等待线程时间到达限制。在接收到一个等待的句柄后,方法会将其他等待的句柄从等待状态下移除。
- Release():增加 Semaphore 对象计数器的值,从而允许创建者线程使用 Semaphore。
接下来是Semaphore类的一个简单示例:
using System;
using System.Threading;
class SemaphoreDemo
{
static Semaphore _pool = new Semaphore(0, 2);
static void Main(string[] args)
{
for (int i = 1; i <= 5; i++)
{
Thread t = new Thread(new ParameterizedThreadStart(Worker));
t.Start(i);
}
Console.ReadLine();
}
static void Worker(object id)
{
Console.WriteLine("Worker {0} 开始执行...", id);
_pool.WaitOne();
Console.WriteLine("Worker {0} 开始使用资源...", id);
Thread.Sleep(5000);
Console.WriteLine("Worker {0} 使用资源结束...", id);
_pool.Release();
}
}
在这个示例中,Semaphore对象的最大计数器值被设置为2,以限制最多只有两个线程可以同时访问受信号量保护的资源。五个不同的工作线程被创建并启动,它们共享两个信号量。当一个工作线程需要访问资源时,它会等待另一个线程释放一个信号量,然后才能继续执行。
示例1:控制并发访问Web API
一个常见的示例是,你的应用程序需要同步地访问一个Web API。由于API的限制,你最多只能有三个并发连接。为了管理你的连接,你可以使用一个信号量。
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
class WebApiManager
{
static Semaphore _semaphore;
static WebApiManager()
{
_semaphore = new Semaphore(3, 3);
}
public static async Task<string> GetDataAsync(string url)
{
_semaphore.WaitOne();
try
{
using (var client = new HttpClient())
{
var response = await client.GetStringAsync(url);
return response;
}
}
finally
{
_semaphore.Release();
}
}
}
在上面的示例中,使用了一个Semaphore对象来限制API调用的并发量。Semaphore对象的初始计数是3,因此最多只能有3个线程同时调用GetDataAsync()
方法。在传入url参数之后,线程通过调用Semaphore对象的WaitOne()
方法来等待可用的信号量。如果当前没有可用的信号量,线程将被阻塞,直到有一个信号量可用或者超时。当信号量可用时,线程可以继续执行,使用HttpClient发起API调用。当线程完成调用时,它调用信号量的Release()
方法来释放一个信号量。
示例2:实现资源池
另一个示例是,你需要管理一个资源池,例如数据库连接、网络连接等。由于这些资源是有限的,你需要一个机制来限制资源的并发使用量。你可以使用一个信号量来实现这个功能。
class ResourcePool<T>
{
private Stack<T> _resources;
private Semaphore _semaphore;
public ResourcePool(IEnumerable<T> resources, int maxCount)
{
_resources = new Stack<T>(resources);
_semaphore = new Semaphore(maxCount, maxCount);
}
public T Acquire()
{
_semaphore.WaitOne();
T resource;
lock (_resources)
{
resource = _resources.Pop();
}
return resource;
}
public void Release(T resource)
{
lock (_resources)
{
_resources.Push(resource);
}
_semaphore.Release();
}
}
在上面的示例中,使用一个Semaphore对象来限制ResourcePool对象中的资源并发使用量。在构造函数中,你需要指定初始资源列表和Semaphore最大计数器值。在Acquire()方法中,线程等待一个可用的信号量,并且从资源池中弹出一个可用的资源。在Release()方法中,线程将资源放回资源池,并释放一个信号量。
这样,就实现了一个Thread-Safe的资源池,可以在多线程环境下被安全地访问。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C#信号量用法简单示例 - Python技术站