C#之高并发处理过程
高并发本质是系统在单位时间内处理大量并行请求的能力。
在C#中处理这个问题需要分层解决:首先是架构层面,比如是否采用分布式;然后是语言特性层面,比如异步编程;最后是基础设施层面,比如数据库优化。
1.异步编程(async/await)
核心思想
避免阻塞线程。当一个操作(如 I/O - 文件读写、网络请求、数据库查询)需要等待外部资源时,释放当前线程去处理其他请求,待操作完成后再由线程池分配线程继续执行。
优势
显著提高线程池线程的利用率(一个线程可处理多个请求),用更少的线程服务更多的并发请求,减少线程上下文切换开销,提高系统吞吐量和响应能力。
C# 实现
使用 async 关键字标记异步方法,在需要等待的操作前使用 await 关键字。
public async Task<ActionResult> GetData()
{
var data = await _dbContext.Data.ToListAsync(); // 异步数据库操作
return Ok(data);
}关键点:
所有 I/O 操作(数据库、网络、文件)必须异步
避免
Task.Wait()或Task.Result(会导致死锁)
2. 分布式架构
(1) 消息队列(削峰填谷)
核心思想:
- 将耗时的、非实时的操作(如发送邮件、生成报告、复杂数据处理)异步化。
- 请求到达后,将任务信息放入消息队列(如 RabbitMQ, Azure Service Bus, Kafka, Amazon SQS),立即返回响应。
- 后台有专门的“消费者”进程从队列中取出消息并处理。
优势:
- 削峰填谷: 突发的高流量可以被队列缓冲,消费者按自身能力消费,避免后端服务瞬时过载崩溃。
- 解耦: 生产者和消费者完全解耦,互不影响,提高系统可靠性和可维护性。
- 异步处理: 释放 Web 服务器线程,使其专注于处理用户请求。
- 重试机制: 消息队列通常支持消息传递失败后的重试。
// 使用 RabbitMQ.Client 发送
using var channel = _connection.CreateModel();
channel.QueueDeclare("orders");
var body = Encoding.UTF8.GetBytes(orderJson);
channel.BasicPublish("", "orders", body); // 异步解耦- 推荐工具:RabbitMQ、Kafka、Azure Service Bus
(2)分布式缓存
核心思想:
将频繁读取但不经常变化的数据(如配置、热门商品信息、会话状态)存储在独立于应用服务器、高性能的内存缓存服务(如 Redis, Memcached)中。
优势:
- 极大减少对后端数据库(通常是性能瓶颈)的访问次数。
- 数据存储在内存中,访问速度极快。
- 支持分布式部署,多个应用实例共享同一缓存,保证数据一致性。
- 提高应用的可扩展性(水平扩展应用服务器时,缓存层通常更容易扩展)
推荐工具:
- Redis, Memcached
// 使用 StackExchange.Redis
IDatabase cache = Connection.GetDatabase();
await cache.StringSetAsync("key", "value", TimeSpan.FromMinutes(10));
string value = await cache.StringGetAsync("key");3. 数据库优化
连接池:
ADO.NET 和 ORM(如 EF Core)默认管理数据库连接池。
确保配置合理的
MinPoolSize和MaxPoolSize。
异步数据库操作:
务必使用 ORM 或 ADO.NET 提供的异步方法(如
ToListAsync(),SaveChangesAsync(),ExecuteReaderAsync())来执行数据库查询和命令。
优化查询:
- 使用索引避免全表扫描。
- 只查询需要的字段(
Select)。 - 避免 N+1 查询问题(EF Core 中注意使用
Include或投影)。 - 合理设计数据模型。
- 考虑读写分离、分库分表(在数据量极大时)。
NoSQL 考虑:
- 对于某些特定场景(如文档存储、键值对、宽列存储、图数据库),NoSQL 数据库(如 MongoDB, Cassandra, Cosmos DB)可能比关系型数据库(如 SQL Server, PostgreSQL)更适合高并发读写和水平扩展。
4. 使用锁(lock)和互斥量(Mutex)
核心思想: 当多个线程需要访问共享资源(如静态变量、单例实例、文件句柄)时,必须协调它们的访问,防止数据损坏或状态不一致。
常用机制:
lock语句: 最常用,基于Monitor类,提供互斥锁。
虽然锁在高并发场景下可能会引起性能瓶颈,但在某些情况下仍然需要使用。确保只在必要时使用,并尽可能减少锁定范围。
private object lockObject = new object();
public void ProcessData(int data)
{
lock (lockObject)
{
// 执行需要同步的代码块
}
}乐观锁和Redis分布式锁都是处理高并发场景的核心方案
乐观锁
乐观锁 通过版本号(Version)或时间戳(Timestamp)实现“无锁”并发控制:
- 读阶段:读取数据时记录当前版本号。
- 写阶段:提交更新前校验版本号是否未变化,若变化则重试或失败。
典型实现包括:
- 数据库乐观锁:通过SQL语句(如
UPDATE ... WHERE version=old_version)。 - Redis的WATCH/MULTI:监视Key变化,事务中执行CAS操作
Redis分布式锁
原理:
基于Redis的原子操作(如SET key value NX PX)实现互斥访问:
- 加锁:通过
SETNX设置唯一值并附加超时时间。 - 续期:Redisson的
Watchdog线程自动延长锁有效期。 - 释放:校验持有者身份后删除Key。
适用场景
低冲突场景:如读多写少的业务(商品浏览、配置读取)。
突发流量:秒杀系统中库存扣减(配合重试机制)。
需高吞吐:避免锁竞争,提升并发能力
乐观锁和Redis分布式锁是处理高并发的互补方案而非互斥:
- 乐观锁:轻量、高吞吐,适合低冲突场景,需防范ABA问题。
- Redis分布式锁:强一致、易用,需优化主从容错与锁粒度。
实际系统中常组合使用(如Redis锁拦截请求+数据库乐观锁保证最终一致)
Monitor 类: lock 的底层实现,提供更细粒度控制(如 TryEnter, Wait, Pulse)。
Mutex: 进程间或跨 AppDomain 的互斥锁,重量级。
//当前电脑只能启动一个WCS程序
using (System.Threading.Mutex m = new System.Threading.Mutex(true, AppConfig.Instance.GetConfig("主界面名称"), out Started))
{
if (Started)
{
if (DbManagerBase.Instance.GetDbTime() == false)
{
MessageBox.Show("数据库连接失败,请检查原因!");
}
else
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new FrmMain());
}
}
else
{
LibManager.WriteLog("WCS程序启动失败,与运行中WCS程序的主界面名称重复", RecordLogTypeEnum.Error.ToString(), "");
MessageBox.Show("当前WCS程序已启动!");
}
}关键原则:
最小化锁范围: 只在绝对必要的地方加锁,并尽快释放锁。
避免锁嵌套: 容易导致死锁。
锁粒度: 根据共享资源的范围选择合适的锁(细粒度锁通常并发性更好)。
优先使用并发集合: 在可能的情况下,用
ConcurrentDictionary等代替自己用锁实现的集合
5. 使用线程池(ThreadPool)和Task.Run
对于需要大量并发线程的场景,可以使用Task.Run来启动一个新任务到线程池中。
.NET 运行时维护一个预先创建的线程池,用于执行后台任务和处理异步回调。
这比手动创建和销毁线程更高效。
Task[] tasks = new Task[10];
for (int i = 0; i < 10; i++)
{
tasks[i] = Task.Run(() => DoWork());
}
await Task.WhenAll(tasks);典型误区
过度使用
Task.Run:I/O 操作直接用async而非封装线程全局静态锁:导致无谓的线程阻塞
同步调用异步方法:
GetAwaiter().GetResult()引发死锁忽略连接池配置:数据库连接成为瓶颈
黄金法则:
异步化所有 I/O 路径 + 避免共享状态 + 队列解耦
通过负载测试(如 JMeter/LoadRunner)持续验证系统极限。
6. 并发集合 (System.Collections.Concurrent)
核心思想
提供线程安全的集合类,允许多个线程安全地添加、移除或访问集合中的元素,而无需开发者手动加锁。
常用类
ConcurrentQueue<T>: 先进先出 (FIFO) 队列。ConcurrentStack<T>: 后进先出 (LIFO) 栈。ConcurrentDictionary<TKey, TValue>: 键值对字典。ConcurrentBag<T>: 无序集合,适用于对象池等场景。BlockingCollection<T>: 提供阻塞和限制功能的集合,常用于生产者-消费者模式。
优势:
简化多线程编程,避免锁竞争(内部使用高效的锁或无锁技术),提高并发访问性能。
示例:
使用 ConcurrentQueue 实现简单的生产者-消费者。
using System.Collections.Concurrent; private static readonly ConcurrentDictionary<Type, string> assemblyQualifiedNameCache = new ConcurrentDictionary<Type, string>();
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
相关文章
C#判断字符串中是否包含指定字符串及contains与indexof方法效率问题
这篇文章主要介绍了C#判断字符串中是否包含指定字符串及contains与indexof方法效率问题 ,文中给大家列举通过两种方法来判断,需要的朋友可以参考下2018-10-10


最新评论