c# 如何编写可重入的(Reentrant)和线程安全的代码
技术百科
月夜之吻
发布时间:2026-01-16
浏览: 次 可重入指同一线程可多次调用未完成函数而不破坏状态;线程安全指多线程并发访问共享资源时不出现数据竞争。二者不等价:可重入关注单线程重入能力,线程安全关注多线程并发保护。
什么是可重入(Reentrant)和线程安全?先分清这两个概念
可重入 ≠ 线程安全,但二者常被混用。可重入指:**同一函数/方法在未执行完时,能被同一线程再次调用且不破坏内部状态**;典型场景是信号处理、递归调用或回调嵌套。lock 会阻塞同一线程再次进入(除非用 RecursiveLock 或 Monitor.TryEnter 配超时),所以普通 lock 块默认不可重入。
线程安全则关注**多线程并发访问共享资源时不出现数据竞争或状态不一致**。它不要求可重入——比如一个只读的静态缓存可能线程安全但无需支持重入。
在 C# 中,真正同时满足“可重入 + 线程安全”的常见做法是:用 Monitor 手动控制重入计数,或改用 AsyncLocal / 不可变对象 / 纯函数式设计来规避共享状态。
用 Monitor 实现可重入锁(ReentrantLock)
C# 的 lock 语句底层就是 Monitor.Enter/Exit,但它不暴露重入计数。要自己实现可重入行为,必须用 Monitor.TryEnter(obj, timeout) 并手动维护线程 ID 与进入次数映射。
注意:Monitor 本身是可重入的(.NET 运行时保证同一线程多次 Enter 不死锁),但你得确保每次 Exit 次数匹配,否则其他线程永远等不到释放。
-
Monitor.Enter和Monitor.Exit必须成对出现在同一个线程中;异常时需try/finally保障Exit - 不要跨线程调用
Monitor.Exit,会抛SynchronizationLockException - 避免在锁内调用未知第三方代码(可能引发重入或死锁)
public class ReentrantLock
{
private readonly object _syncRoot = new object();
private Thread? _owner;
private int _entryCount;
public void Enter()
{
var current = Thread.CurrentThread;
lock (_syncRoot)
{
if (_owner == current)
{
_entryCount++;
return;
}
Monitor.Enter(_syncRoot); // 等待获取锁
_owner = current;
_entryCount = 1;
}
}
public void Exit()
{
lock (_syncRoot)
{
if (Thread.CurrentThread != _owner)
throw new InvalidOperationException("Exit called from non-owner thread");
if (--_entryCount == 0)
{
_owner = null;
Monitor.Exit(_syncRoot);
}
}
}}
更轻量、更现代的替代方案:避免锁,用 AsyncLocal 或不可变状态
如果你的“可重入”需求本质是想让递归调用或异步嵌套保持上下文隔离(比如日志追踪 ID、事务作用域),AsyncLocal 是比手写可重入锁更安全、更符合 .NET 设计哲学的选择。
它自动随 async/await 流动,且每个逻辑执行路径拥有独立副本,天然线程安全、天然可重入——因为根本不共享状态。
-
AsyncLocal不适用于跨线程同步共享数据,只用于“逻辑上下文传播” - 若需共享可变状态,优先考虑
ImmutableArray+Interlocked更新引用,而非加锁 - 纯函数式风格(无副作用、输入决定输出)天然可重入且线程安全,适合配置解析、DTO 转换等场景
private static readonly AsyncLocal_traceId = new AsyncLocal (); public void DoWork(string id) { _traceId.Value = id; // 当前逻辑流独享 NestedCall(); }
private void NestedCall() { Console.WriteLine($"Current trace: {_traceId.Value}"); // 自动继承,不污染其他分支 }
容易踩的坑:别把 lock(this) 或 lock(typeof(T)) 当可重入锁用
这些写法不仅不可重入,还极易引发死锁或意外锁竞争:
-
lock(this)暴露了实例锁,外部代码也能锁它,导致协作失控 -
lock(typeof(T))是全 AppDomain/Assembly 级别锁,多个类库可能无意中锁住同一 Type 对象 -
Monitor.Enter(obj)后没配对Monitor.Exit(obj)—— 尤其在异常分支遗漏finally,会导致永久挂起 - 在
async方法里用lock:await 会切出线程,再回来时可能已不是原线程,Monit不允许跨线程 Exit
or
真正的难点不在“怎么写锁”,而在于判断“是否真的需要锁”。多数业务逻辑的可重入需求,其实源于状态管理混乱——把上下文塞进静态字段、单例属性,才被迫去解决线程安全问题。重构掉共享可变状态,往往比写一个完美的 ReentrantLock 更有效。
# ai
# 多个
# 它不
# 出现在
# 也能
# 而不
# 这两个
# 无意中
# app
# 递归
# 并发
# 对象
# c#
# typeof
# 重构
# .net
# 线程
# 异步
# 死锁
# this
# 多线程
# 作用域
# try
# finally
# 并发访问
相关栏目:
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
AI推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
SEO优化<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
技术百科<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
谷歌推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
百度推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
网络营销<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
案例网站<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
精选文章<?muma echo $count; ?>
】
相关推荐
- php怎么操作Redis_Redis扩展连接与基本
- Win11怎么开启智能存储_Windows11存储
- C#如何使用XPathNavigator高效查询X
- c# Task.ConfigureAwait(tr
- Win11怎样安装微信开发者工具_Win11安装开
- c++中如何计算坐标系中两点间距离_c++勾股定理
- Windows服务启动类型恢复方法_错误修改导致的
- Win11如何连接Xbox手柄 Win11蓝牙连接
- Python路径拼接规范_跨平台处理说明【指导】
- 如何从 Go 的 map[string]inter
- PHP怎么接收URL中的锚点参数_获取#后面参数值
- 如何在JavaScript中动态拼接PHP的bas
- Win11怎样安装网易云音乐_Win11安装网易云
- Mac版Final Cut Pro入门_Mac视频
- Win11怎么查看显卡显存_查询Win11显卡详细
- c++获取当前时间戳_c++ time函数使用详解
- Win11如何设置系统声音_Win11系统声音调整
- Win11怎么开启剪贴板历史记录_Windows1
- Windows10蓝屏代码DPC_WATCHDOG
- Win11开始菜单打不开_修复Windows 11
- Win10怎样安装Excel数据分析工具_Win1
- 如何正确访问 Laravel 模型或对象的属性而非
- c++怎么编写动态链接库dll_c++ __dec
- 如何使用Golang实现错误包装与传递_Golan
- 如何使用Golang开发基础文件下载功能_Gola
- Ajax提交表单PHP怎么接收_处理Ajax发送的
- 如何将竖排文本文件转换为横排字符串
- Win11声音忽大忽小怎么办 Win11音频增强功
- Win11怎么设置开机自动连接宽带_Windows
- Win10电脑怎么设置休眠快捷键_Windows1
- 如何使用Golang recover捕获panic
- Win11怎么设置组合键快捷方式_Windows1
- C++中的Pimpl idiom是什么,有什么好处
- Win11声音太小怎么办_Windows 11开启
- Windows系统被恶意软件破坏后的恢复策略_错误
- Win11怎么开启自动HDR画质_Windows1
- c++怎么处理多线程死锁_c++ lock_gua
- php修改数据怎么改富文本_update更新htm
- Win10怎么关闭自动更新错误重启 Win10策略
- Win10如何更改网络连接_Windows10以太
- Python正则表达式实战_模式匹配说明【教程】
- 如何在Golang中处理模块冲突_解决依赖版本不兼
- Win10怎样卸载自带Edge_Win10卸载Ed
- Win10如何更改电脑休眠时间_Windows10
- Windows7怎么找回经典开始菜单_Window
- Win11怎么设置夜间模式_Windows11显示
- 如何使用Golang进行HTTP服务性能测试_测量
- Mac电脑如何恢复出厂设置_Mac抹掉数据并重装系
- Python类装饰器使用_元编程解析【教程】
- Win11怎么设置系统还原_Windows11系统


QQ客服