10、使用信号量限制线程数量
使用信号量限制线程数量
知识点
什么是信号量
信号量(Semaphore)是一种同步原语,用于控制可以同时访问特定资源的线程数量。它维护一个计数器,表示可用资源的数量。
信号量的工作原理
- 计数器:维护可用资源的数量
- 获取资源:计数器减1,如果计数器为0则阻塞等待
- 释放资源:计数器加1,唤醒等待的线程
- 容量限制:设置最大并发访问数量
.NET中的信号量类型
- Semaphore:系统级信号量,支持跨进程
- SemaphoreSlim:轻量级信号量,仅支持进程内,性能更好
使用场景
- 限制并发连接数
- 控制资源池大小
- 限制同时执行的任务数量
- 实现生产者消费者模式
信号量vs其他同步机制
| 特性 | Semaphore | Mutex | Lock |
|---|---|---|---|
| 并发数 | 可配置 | 1 | 1 |
| 跨进程 | 支持 | 支持 | 不支持 |
| 性能 | 中等 | 慢 | 快 |
代码案例
案例1:基本信号量使用
using System;
using System.Threading;
using System.Threading.Tasks;
class BasicSemaphoreExample
{
// 创建一个信号量,最多允许3个线程同时访问
private static readonly SemaphoreSlim semaphore = new SemaphoreSlim(3, 3);
static void Main(string[] args)
{
Console.WriteLine("基本信号量示例 - 最多允许3个线程同时执行");
// 启动10个任务,但只有3个能同时执行
Task[] tasks = new Task[10];
for (int i = 0; i < tasks.Length; i++)
{
int taskId = i;
tasks[i] = Task.Run(() => DoWork(taskId));
}
Task.WaitAll(tasks);
Console.WriteLine("所有任务完成");
Console.WriteLine($"信号量当前计数: {semaphore.CurrentCount}");
semaphore.Dispose();
}
static async Task DoWork(int taskId)
{
Console.WriteLine($"任务{taskId}: 等待信号量...");
// 等待信号量
await semaphore.WaitAsync();
try
{
Console.WriteLine($"任务{taskId}: 获得信号量,开始工作");
Console.WriteLine($"当前活跃任务数: {3 - semaphore.CurrentCount}");
// 模拟工作
await Task.Delay(2000);
Console.WriteLine($"任务{taskId}: 工作完成");
}
finally
{
// 释放信号量
semaphore.Release();
Console.WriteLine($"任务{taskId}: 释放信号量");
}
}
}
案例2:限制并发下载数量
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
class ConcurrentDownloadLimiter
{
// 限制同时下载的文件数量为5个
private static readonly SemaphoreSlim downloadSemaphore = new SemaphoreSlim(5, 5);
private static readonly HttpClient httpClient = new HttpClient();
static async Task Main(string[] args)
{
Console.WriteLine("并发下载限制示例");
// 模拟下载URL列表
List<string> urls = new List<string>();
for (int i = 1; i <= 20; i++)
{
urls.Add($"https://httpbin.org/delay/{i % 3 + 1}"); // 模拟不同延迟的下载
}
Console.WriteLine($"开始下载 {urls.Count} 个文件,最大并发数: 5");
// 启动所有下载任务
Task<DownloadResult>[] downloadTasks = new Task<DownloadResult>[urls.Count];
for (int i = 0; i < urls.Count; i++)
{
int index = i;
downloadTasks[i] = DownloadFileAsync(urls[index], index + 1);
}
// 等待所有下载完成
DownloadResult[] results = await Task.WhenAll(downloadTasks);
// 显示结果
Console.WriteLine("\n=== 下载结果统计 ===");
int successCount = 0;
int failCount = 0;
foreach (var result in results)
{
if (result.Success)
{
successCount++;
Console.WriteLine($"文件{result.FileId}: 成功 - 大小: {result.Size} bytes, 耗时: {result.Duration.TotalSeconds:F2}s");
}
else
{
failCount++;
Console.WriteLine($"文件{result.FileId}: 失败 - {result.ErrorMessage}");
}
}
Console.WriteLine($"\n成功: {successCount}, 失败: {failCount}");
Console.WriteLine($"信号量最终计数: {downloadSemaphore.CurrentCount}");
downloadSemaphore.Dispose();
httpClient.Dispose();
}
static async Task<DownloadResult> DownloadFileAsync(string url, int fileId)
{
var result = new DownloadResult { FileId = fileId };
var startTime = DateTime.Now;
Console.WriteLine($"文件{fileId}: 等待下载槽位...");
// 等待信号量
await downloadSemaphore.WaitAsync();
try
{
Console.WriteLine($"文件{fileId}: 开始下载 {url}");
Console.WriteLine($"当前活跃下载数: {5 - downloadSemaphore.CurrentCount}");
// 执行下载
var response = await httpClient.GetAsync(url);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsByteArrayAsync();
result.Success = true;
result.Size = content.Length;
}
else
{
result.Success = false;
result.ErrorMessage = $"HTTP {response.StatusCode}";
}
}
catch (Exception ex)
{
result.Success = false;
result.ErrorMessage = ex.Message;
}
finally
{
result.Duration = DateTime.Now - startTime;
downloadSemaphore.Release();
Console.WriteLine($"文件{fileId}: 下载完成,释放槽位");
}
return result;
}
class DownloadResult
{
public int FileId { get; set; }
public bool Success { get; set; }
public int Size { get; set; }
public TimeSpan Duration { get; set; }
public string ErrorMessage { get; set; }
}
}
案例3:数据库连接池模拟
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
class DatabaseConnectionPool
{
private readonly SemaphoreSlim connectionSemaphore;
private readonly ConcurrentQueue<DatabaseConnection> availableConnections;
private readonly int maxConnections;
private int currentConnections = 0;
public DatabaseConnectionPool(int maxConnections)
{
this.maxConnections = maxConnections;
this.connectionSemaphore = new SemaphoreSlim(maxConnections, maxConnections);
this.availableConnections = new ConcurrentQueue<DatabaseConnection>();
Console.WriteLine($"数据库连接池初始化,最大连接数: {maxConnections}");
}
public async Task<DatabaseConnection> GetConnectionAsync(TimeSpan timeout)
{
Console.WriteLine("请求数据库连接...");
// 等待可用连接槽位
if (!await connectionSemaphore.WaitAsync(timeout))
{
throw new TimeoutException("获取数据库连接超时");
}
try
{
// 尝试从池中获取现有连接
if (availableConnections.TryDequeue(out DatabaseConnection connection))
{
Console.WriteLine($"从池中获取现有连接 {connection.Id}");
return connection;
}
// 如果没有现有连接,创建新连接
int connectionId = Interlocked.Increment(ref currentConnections);
var newConnection = new DatabaseConnection(connectionId);
await newConnection.OpenAsync();
Console.WriteLine($"创建新的数据库连接 {newConnection.Id}");
return newConnection;
}
catch
{
// 如果创建连接失败,释放信号量
connectionSemaphore.Release();
throw;
}
}
public void ReturnConnection(DatabaseConnection connection)
{
if (connection != null && connection.IsOpen)
{
Console.WriteLine($"返回连接 {connection.Id} 到池中");
availableConnections.Enqueue(connection);
}
else
{
Console.WriteLine($"连接 {connection?.Id} 已关闭或无效");
}
// 释放信号量槽位
connectionSemaphore.Release();
}
public void Dispose()
{
// 关闭所有连接
while (availableConnections.TryDequeue(out DatabaseConnection connection))
{
connection.Close();
}
connectionSemaphore.Dispose();
Console.WriteLine("数据库连接池已关闭");
}
public int ActiveConnections => maxConnections - connectionSemaphore.CurrentCount;
}
class DatabaseConnection
{
public int Id { get; }
public bool IsOpen { get; private set; }
public DatabaseConnection(int id)
{
Id = id;
}
public async Task OpenAsync()
{
// 模拟连接建立时间
await Task.Delay(100);
IsOpen = true;
Console.WriteLine($"数据库连接 {Id} 已建立");
}
public async Task<string> ExecuteQueryAsync(string query)
{
if (!IsOpen)
throw new InvalidOperationException("连接未打开");
// 模拟查询执行时间
await Task.Delay(500);
return $"查询结果: {query} (连接 {Id})";
}
public void Close()
{
IsOpen = false;
Console.WriteLine($"数据库连接 {Id} 已关闭");
}
}
class DatabaseConnectionPoolExample
{
static async Task Main(string[] args)
{
Console.WriteLine("数据库连接池示例");
// 创建连接池,最大5个连接
var connectionPool = new DatabaseConnectionPool(5);
// 启动多个任务模拟并发数据库操作
Task[] tasks = new Task[12];
for (int i = 0; i < tasks.Length; i++)
{
int taskId = i;
tasks[i] = Task.Run(() => DatabaseOperationAsync(connectionPool, taskId));
}
await Task.WhenAll(tasks);
Console.WriteLine("所有数据库操作完成");
connectionPool.Dispose();
}
static async Task DatabaseOperationAsync(DatabaseConnectionPool pool, int taskId)
{
DatabaseConnection connection = null;
try
{
Console.WriteLine($"任务{taskId}: 请求数据库连接");
// 获取连接,设置5秒超时
connection = await pool.GetConnectionAsync(TimeSpan.FromSeconds(5));
Console.WriteLine($"任务{taskId}: 获得连接 {connection.Id},当前活跃连接数: {pool.ActiveConnections}");
// 执行多个查询
for (int i = 0; i < 3; i++)
{
string query = $"SELECT * FROM table{i} WHERE task_id = {taskId}";
string result = await connection.ExecuteQueryAsync(query);
Console.WriteLine($"任务{taskId}: {result}");
}
// 模拟处理时间
await Task.Delay(1000);
Console.WriteLine($"任务{taskId}: 数据库操作完成");
}
catch (TimeoutException)
{
Console.WriteLine($"任务{taskId}: 获取数据库连接超时");
}
catch (Exception ex)
{
Console.WriteLine($"任务{taskId}: 数据库操作异常 - {ex.Message}");
}
finally
{
// 返回连接到池中
if (connection != null)
{
pool.ReturnConnection(connection);
}
}
}
}
案例4:生产者消费者with信号量
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
class ProducerConsumerWithSemaphore
{
private static readonly SemaphoreSlim emptySlots = new SemaphoreSlim(10, 10); // 空槽位
private static readonly SemaphoreSlim filledSlots = new SemaphoreSlim(0, 10); // 已填充槽位
private static readonly ConcurrentQueue<WorkItem> buffer = new ConcurrentQueue<WorkItem>();
private static volatile bool isProducing = true;
class WorkItem
{
public int Id { get; set; }
public string Data { get; set; }
public DateTime CreatedTime { get; set; }
}
static async Task Main(string[] args)
{
Console.WriteLine("生产者消费者模式(信号量实现)");
Console.WriteLine("缓冲区大小: 10");
// 启动生产者
Task[] producers = new Task[2];
for (int i = 0; i < producers.Length; i++)
{
int producerId = i;
producers[i] = Task.Run(() => ProducerAsync(producerId));
}
// 启动消费者
Task[] consumers = new Task[3];
for (int i = 0; i < consumers.Length; i++)
{
int consumerId = i;
consumers[i] = Task.Run(() => ConsumerAsync(consumerId));
}
// 运行10秒后停止生产
await Task.Delay(10000);
isProducing = false;
Console.WriteLine("停止生产信号已发出");
// 等待生产者完成
await Task.WhenAll(producers);
// 通知消费者没有更多数据
for (int i = 0; i < consumers.Length; i++)
{
filledSlots.Release(); // 释放信号让消费者能够检查退出条件
}
// 等待消费者完成
await Task.WhenAll(consumers);
Console.WriteLine($"程序结束,缓冲区剩余项目: {buffer.Count}");
emptySlots.Dispose();
filledSlots.Dispose();
}
static async Task ProducerAsync(int producerId)
{
int itemId = 0;
while (isProducing)
{
try
{
// 等待空槽位(最多等待1秒)
if (await emptySlots.WaitAsync(1000))
{
// 创建工作项
var workItem = new WorkItem
{
Id = itemId++,
Data = $"Producer{producerId}_Item{itemId}",
CreatedTime = DateTime.Now
};
// 添加到缓冲区
buffer.Enqueue(workItem);
Console.WriteLine($"生产者{producerId}: 生产了 {workItem.Data},缓冲区大小: {buffer.Count}");
// 通知有新的填充槽位
filledSlots.Release();
}
else
{
Console.WriteLine($"生产者{producerId}: 等待空槽位超时");
}
}
catch (Exception ex)
{
Console.WriteLine($"生产者{producerId}: 异常 - {ex.Message}");
}
// 模拟生产时间
await Task.Delay(500);
}
Console.WriteLine($"生产者{producerId}: 停止生产");
}
static async Task ConsumerAsync(int consumerId)
{
while (true)
{
try
{
// 等待填充槽位(最多等待2秒)
if (await filledSlots.WaitAsync(2000))
{
// 尝试从缓冲区获取项目
if (buffer.TryDequeue(out WorkItem workItem))
{
Console.WriteLine($"消费者{consumerId}: 消费了 {workItem.Data},缓冲区大小: {buffer.Count}");
Console.WriteLine($"消费者{consumerId}: 处理时间延迟: {(DateTime.Now - workItem.CreatedTime).TotalMilliseconds:F0}ms");
// 模拟处理时间
await Task.Delay(1000);
// 通知有新的空槽位
emptySlots.Release();
}
else if (!isProducing)
{
// 如果缓冲区为空且不再生产,退出
Console.WriteLine($"消费者{consumerId}: 没有更多项目,退出");
break;
}
}
else
{
Console.WriteLine($"消费者{consumerId}: 等待项目超时");
// 如果不再生产且等待超时,可能需要退出
if (!isProducing && buffer.IsEmpty)
{
Console.WriteLine($"消费者{consumerId}: 生产已停止且缓冲区为空,退出");
break;
}
}
}
catch (Exception ex)
{
Console.WriteLine($"消费者{consumerId}: 异常 - {ex.Message}");
}
}
Console.WriteLine($"消费者{consumerId}: 已退出");
}
}
知识点总结
-
信号量的核心概念:
- 计数器机制控制并发访问数量
- 获取资源时计数器减1,释放时加1
- 计数器为0时后续访问被阻塞
-
SemaphoreSlim vs Semaphore:
- SemaphoreSlim:轻量级,仅进程内,支持异步
- Semaphore:重量级,支持跨进程,仅同步操作
-
最佳实践:
- 总是在finally块中释放信号量
- 使用using模式确保资源清理
- 设置合理的超时时间
- 避免死锁和资源泄漏
-
适用场景:
- 连接池管理
- 限制并发下载/上传
- 资源配额控制
- 生产者消费者模式
-
性能优化:
- 选择合适的信号量类型
- 避免频繁的获取/释放操作
- 合理设置初始计数和最大计数
- 考虑异步操作减少线程阻塞

浙公网安备 33010602011771号