c# 处理高并发的几种方法 c#高并发解决方案

来源:这里教程网 时间:2026-02-21 17:38:26 作者:

async/await 是高并发的起点,不是终点——它只解决 IO 等待问题,不解决 CPU 争抢、缓存不一致或数据库写冲突。

用 async/await 做对了,但为什么还卡?

很多人以为加了

async
/
await
就自动高并发了,结果压测时 QPS 上不去、线程池饥饿、GC 频繁。根本原因是:它只释放线程,不减少资源竞争。

必须所有 I/O 调用都异步化:
DbContext.SaveChangesAsync()
HttpClient.GetAsync()
FileStream.ReadAsync()
—— 混入一个
.Result
.Wait()
就可能死锁
避免在异步方法里调用同步阻塞 API(如
File.ReadAllText()
),否则线程池线程被占住,新请求排队等待
高频小任务(比如每秒数万次订单校验)建议用
ValueTask
替代
Task
,减少 GC 压力;实测内存分配可降 60%+

库存扣减、计数器更新这类共享操作怎么不出错?

lock
最简单,但会串行化,成为瓶颈;真正在意吞吐量时,得换更轻量、更可控的方式。

简单整数计数:优先用
Interlocked.Decrement(ref stock)
,零锁、原子、快
对象级状态变更(如订单状态机):用
ConcurrentDictionary<string order></string>
+
TryRemove
/
GetOrAdd
,比手动锁安全得多
分布式场景(多实例部署):必须上
Redis
分布式锁,但别用
SETNX
手写——用
StackExchange.Redis
LockTake
+ 自动续期,否则容易锁失效

100 个用户同时下单,数据库写崩了怎么办?

数据库是高并发链路中最难横向扩展的一环,不能靠“加索引”或“读写分离”一劳永逸。

写操作先过队列:把下单请求发到
RabbitMQ
Kafka
,Web 层立刻返回“已受理”,后台消费者按能力消费,削峰填谷
避免直接
UPDATE products SET stock = stock - 1 WHERE id = @id AND stock >= 1
—— 这种乐观锁在高并发下会大量失败重试;改用带版本号的更新(
version
字段)或 Redis Lua 脚本原子扣减
统计类查询(如“今日销量”)绝不实时算,用定时任务写入
summary_daily_sales
表,查时直读

缓存更新策略选错,数据就永远对不上

“先删缓存再更新 DB” 和 “先更新 DB 再删缓存” 看似只差一步,但在并发下行为完全不同。

推荐组合:更新数据库 → 延迟双删(删一次 + 100ms 后再删一次),能覆盖大部分缓存穿透+脏读场景 绝对不要用“先更新缓存再更新 DB”——DB 更新失败时,缓存就是脏数据,且无法回滚 Redis 缓存键设计要带业务维度和版本号,比如
order:detail:v2:12345
,升级逻辑时直接切版本,避免热更新风险

真正难的从来不是“怎么实现并发”,而是“怎么让并发下的状态始终可预期”。锁、队列、缓存、数据库,每个环节的取舍都会影响最终一致性边界——这点在金融、库存、支付类业务里,错一步就是资损。

相关推荐