当前位置: 首页 > news >正文

C# SpinLock 类 使用详解

总目录


前言

SpinLock 是 C# 中一种轻量级的自旋锁,属于 System.Threading 命名空间,专为极短时间锁竞争的高性能场景设计。它通过忙等待(自旋)而非阻塞线程来减少上下文切换开销,适用于锁持有时间极短(如微秒级)的多线程操作。


一、SpinLock 概述

SpinLock 是 .NET Framework 4.0+ 引入的轻量级同步锁机制,位于 System.Threading 命名空间下。与 Monitorlock 不同,SpinLock 通过“自旋”等待资源释放(忙等待),而非立即让线程进入阻塞状态。这减少了上下文切换的开销,但可能增加 CPU 占用。

1. 核心概念

  • 自旋机制:通过循环检查锁的状态来避免线程进入阻塞状态,适合于短时间等待。
  • 轻量级
    • 相比 Monitor(即 lock 关键字),SpinLock 更高效,但需手动管理锁的生命周期。
    • 适用于高频率、短时间的锁定操作,如等待某个资源的状态变化。
  • 线程追踪:可启用线程 ID 追踪(通过构造函数参数),辅助调试死锁问题。
  • 非递归锁:默认不支持递归获取锁(同一线程重复获取会抛出异常)。
  • 自适应行为:SpinLock 会根据系统负载自动调整其行为,最初进行忙等待,随后如果等待时间较长,则会切换到更高效的阻塞等待。

2. 适用场景

  • 极短时间的锁持有(如 <1ms 的临界区操作)
    • 例如,在等待某个标志位的变化时,使用忙等待可以减少上下文切换的开销。
  • 高并发、低竞争环境(如多核 CPU 频繁访问共享资源)
  • 避免阻塞等待:在某些实时性要求较高的应用中,忙等待可以避免因阻塞等待导致的延迟。
  • 替代 lockMonitor 以优化性能(需实际测试验证)
    • SpinLock 的使用和 Monitor 比较相似,都是处理线程安全的一种锁,只不过SpinLock 是自旋锁

二、主要方法和属性

1. 初始化

SpinLock spinLock = new SpinLock(); // 默认启用线程跟踪(调试用)
SpinLock spinLockNoTracking = new SpinLock(enableThreadOwnerTracking: false); // 禁用跟踪(提升性能)
  • enableThreadOwnerTracking:若为 true,记录持有锁的线程 ID,方便调试,但略微增加开销。

2. 主要方法和属性

方法/属性作用
Enter(ref bool lockTaken)尝试获取锁,并将 lockTaken 设置为 true 表示成功获取锁,
设置为 false 表示未能获取锁。
Exit()释放锁。
IsHeld获取一个值,指示当前线程是否持有该锁。
IsHeldByCurrentThread获取一个值,指示当前线程是否持有该锁。
SpinCount获取或设置旋转计数,表示忙等待的最大次数。

3. 使用示例

1)获取与释放锁

bool lockTaken = false;
try
{spinLock.Enter(ref lockTaken); // 尝试获取锁// 临界区操作(如修改共享资源)
}
finally
{if (lockTaken) spinLock.Exit(); // 必须释放锁
}
  • lockTaken 参数:必须通过 ref 传递,用于检测是否成功获取锁。
  • 必须使用 try-finally:确保锁在异常时也能释放,避免死锁。

2)获取与释放锁高级方法:TryEnter

bool lockTaken = false;
spinLock.TryEnter(TimeSpan.FromMilliseconds(50), ref lockTaken); // 设置超时
if (lockTaken)
{try { /* 临界区 */ }finally { spinLock.Exit(); }
}
else
{// 超时处理(如记录日志或重试)
}
  • 超时机制:避免无限自旋,适用于潜在的高竞争场景。

三、性能优化示例

1. 线程安全计数器

using System;
using System.Threading;
using System.Threading.Tasks;class Program
{static SpinLock spinLock = new SpinLock();static int sharedCounter = 0;static void Main(string[] args){// 启动多个任务以并发地修改共享资源var tasks = new List<Task>();for (int i = 0; i < 5; i++){int j= i+1;tasks.Add(Task.Run(() => IncrementCounter($"Task {j}")));}// 等待所有任务完成Task.WaitAll(tasks.ToArray());Console.WriteLine($"最终计数值: {sharedCounter}");}static void IncrementCounter(string taskName){bool lockTaken = false;try{spinLock.Enter(ref lockTaken);Console.WriteLine($"{taskName} 进入临界区");sharedCounter++; // 模拟对共享资源的操作Thread.Sleep(100); // 模拟一些工作Console.WriteLine($"{taskName} 退出临界区");}finally{if (lockTaken){spinLock.Exit();}}}
}
using System.Threading;class Program
{static SpinLock spinLock = new SpinLock();static int _counter = 0;static void Main(){Parallel.For(0, 1000, _ => IncrementCounter());Console.WriteLine($"最终计数: {_counter}"); // 输出 1000}static void IncrementCounter(){bool lockTaken = false;try{spinLock.Enter(ref lockTaken);_counter++;}finally{if (lockTaken) spinLock.Exit();}}
}

2. 示例2

using System;
using System.Threading;class Program
{static SpinLock spinLock = new SpinLock();static int sharedValue = 0;static void Main(){// 启动多个任务Task[] tasks = new Task[10];for (int i = 0; i < 10; i++){tasks[i] = Task.Run(() => IncrementSharedValue());}// 等待所有任务完成Task.WaitAll(tasks);Console.WriteLine($"最终结果:{sharedValue}");}static void IncrementSharedValue(){bool lockTaken = false;SpinWait spinWait = new SpinWait();try{spinLock.TryEnter(ref lockTaken);while (!lockTaken){spinWait.SpinOnce(); // 自定义自旋策略}sharedValue++;}finally{if (lockTaken){spinLock.Exit(); // 释放锁}}}
}

四、注意事项

1. 不可递归获取

// 错误示例:同一线程重复获取 SpinLock 导致死锁!
SpinLock spinLock = new SpinLock();
bool lockTaken1 = false, lockTaken2 = false;
spinLock.Enter(ref lockTaken1);
spinLock.Enter(ref lockTaken2); // 此处会死锁!
  • SpinLock 不支持递归:同一线程多次获取锁会引发死锁。
  • 替代方案:使用 MonitorMutex 支持递归的锁。

2. 避免值类型陷阱

class MyClass
{private readonly SpinLock spinLock; // 错误!readonly 结构体可能导致副本问题public void Method(){bool lockTaken = false;spinLock.Enter(ref lockTaken); // 操作的是 spinLock 的副本!}
}
  • SpinLock 是结构体:避免作为 readonly 字段,否则可能因值拷贝导致锁失效。

3. 避免长时间自旋

适用场景:锁持有时间极短(如 <1μs)。
长时间自旋的代价:浪费 CPU 资源,应改用 Monitor 或混合锁(如 SpinWait + Thread.Yield)。

4. 线程追踪模式

  • 启用追踪:初始化时设置 enableThreadOwnerTracking=true,可检测锁持有线程。
  • 调试辅助:通过 IsHeldByCurrentThread 检查当前线程是否持有锁。
if (spinLock.IsHeldByCurrentThread)
{// 当前线程已持有锁
}

五、何时选择 SpinLock?

场景推荐锁类型
锁持有时间极短(纳秒级)SpinLock
锁持有时间较长Monitor/lock、Semaphore
需要递归锁Monitor、Mutex
跨进程同步Mutex、Semaphore

六、最佳实践

  • 严格限制锁范围:确保临界区代码尽可能简短。
  • 禁用递归:避免因递归调用导致死锁。
  • 异常处理:始终使用 try-finally 确保锁释放。
  • 性能测试:通过基准测试验证是否适合场景(如 BenchmarkDotNet)。

结语

回到目录页:C#/.NET 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。

相关文章:

C# SpinLock 类 使用详解

总目录 前言 SpinLock 是 C# 中一种轻量级的自旋锁&#xff0c;属于 System.Threading 命名空间&#xff0c;专为极短时间锁竞争的高性能场景设计。它通过忙等待&#xff08;自旋&#xff09;而非阻塞线程来减少上下文切换开销&#xff0c;适用于锁持有时间极短&#xff08;如…...

【linux】在 Linux 上部署 DeepSeek-r1:32/70b:解决下载中断问题

【linux】在 Linux 上部署 DeepSeek-r1:32/70b:解决下载中断问题 【承接商业广告,如需商业合作请+v17740568442】 文章目录 【linux】在 Linux 上部署 DeepSeek-r1:32/70b:解决下载中断问题问题描述:解决方法方法一:手动中断并重启下载方法二:使用 Bash 脚本自动化下载在…...

机器学习所需要的数学知识【01】

总览 导数 行列式 偏导数 概理论 凸优化-梯度下降 kkt条件...

4.【线性代数】——矩阵的LU分解

四 矩阵的LU分解 1. AB的逆矩阵2. 转置矩阵3. ALU3.1 2x2矩阵3.2 3x3矩阵3.3 nxn的矩阵分解的次数&#xff1f; 1. AB的逆矩阵 { ( A B ) ( B − 1 A − 1 ) I ( B − 1 A − 1 ) ( A B ) I ⇒ ( A B ) − 1 B − 1 A − 1 \begin{cases} (AB)(B^{-1}A^{-1}) I\\ (B^{-1}A^…...

【清晰教程】本地部署DeepSeek-r1模型

【清晰教程】通过Docker为本地DeepSeek-r1部署WebUI界面-CSDN博客 目录 Ollama 安装Ollama DeepSeek-r1模型 安装DeepSeek-r1模型 Ollama Ollama 是一个开源工具&#xff0c;专注于简化大型语言模型&#xff08;LLMs&#xff09;的本地部署和管理。它允许用户在本地计算机…...

Spring Cloud工程搭建

目录 工程搭建 搭建父子工程 创建父工程 Spring Cloud版本 创建子项目-订单服务 声明项⽬依赖 和 项⽬构建插件 创建子项目-商品服务 声明项⽬依赖 和 项⽬构建插件 工程搭建 因为拆分成了微服务&#xff0c;所以要拆分出多个项目&#xff0c;但是IDEA只能一个窗口有一…...

使用Redis实现分布式锁,基于原本单体系统进行业务改造

一、单体系统下&#xff0c;使用锁机制实现秒杀功能&#xff0c;并限制一人一单功能 1.流程图&#xff1a; 2.代码实现&#xff1a; Service public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderSe…...

【MediaTek】 T750 openwrt-23.05编 cannot find dependency libexpat for libmesode

MediaTek T750 T750 采用先进的 7nm 制程,高度集成 5G 调制解调器和四核 Arm CPU,提供较强的功能和配置,设备制造商得以打造精巧的高性能 CPE 产品,如固定无线接入(FWA)路由器和移动热点。 MediaTek T750 平台是一款综合的芯片组,集成了 5G SoC MT6890、12nm 制程…...

CHARMM-GUI EnzyDocker: 一个基于网络的用于酶中多个反应状态的蛋白质 - 配体对接的计算平台

❝ "CHARMM-GUI EnzyDocker for Protein−Ligand Docking of Multiple Reactive States along a Reaction Coordinate in Enzymes"介绍了 CHARMM-GUI EnzyDocker&#xff0c;这是一个基于网络的计算平台&#xff0c;旨在简化和加速 EnzyDock 对接模拟的设置过程&…...

c# 2025/2/17 周一

16. 《表达式&#xff0c;语句详解4》 20 未完。。 表达式&#xff0c;语句详解_4_哔哩哔哩_bilibili...

vite【详解】常用配置 vite.config.js / vite.config.ts

官网 https://cn.vitejs.dev/guide/ vite 常用配置 Vite 配置文件通常是 vite.config.js &#xff08;使用 CommonJS 语法&#xff09;或者 vite.config.ts&#xff08;使用 TypeScript 语法&#xff09;&#xff0c;默认内容为 import { defineConfig } from vite import vue…...

最新智能优化算法: 阿尔法进化(Alpha Evolution,AE)算法求解23个经典函数测试集,MATLAB代码

一、阿尔法进化算法 阿尔法进化&#xff08;Alpha Evolution&#xff0c;AE&#xff09;算法是2024年提出的一种新型进化算法&#xff0c;其核心在于通过自适应基向量和随机步长的设计来更新解&#xff0c;从而提高算法的性能。以下是AE算法的主要步骤和特点&#xff1a; 主…...

用于可靠工业通信的5G-TSN集成原型:基于帧复制与消除可靠性的研究

论文标题 中文标题&#xff1a;用于可靠工业通信的5G-TSN集成原型&#xff1a;基于帧复制与消除可靠性的研究 英文标题&#xff1a;5G-TSN Integrated Prototype for Reliable Industrial Communication Using Frame Replication and Elimination for Reliability 作者信息 …...

HaProxy源码安装(Rocky8)

haproxy具有高性能、高可用性、灵活的负载均衡策略和强大的将恐和日志功能&#xff0c;是法国开发者 威利塔罗(Willy Tarreau)在2000年使用C语言开发的一个开源软件&#xff0c;是一款具 备高并发(一万以上)、高性能的TCP和HTTP负载均衡器&#xff0c;支持基于cookie的持久性&a…...

shell脚本备份MySQL数据库和库下表

目录 注意&#xff1a; 一.脚本内容 二.执行效果 三.创建定时任务 注意&#xff1a; 以下为对MySQL5.7.42版本数据库备份shell脚本参考运行备份的机器请确认mysqldump版本>5.7&#xff0c;否则备份参数--set-gtid-purgedOFF无效&#xff0c;考虑到一般数据库节点和备份…...

23. AI-大语言模型

文章目录 前言一、LLM1. 简介2. 工作原理和结构3. 应用场景4. 最新研究进展5. 比较 二、Transformer架构1. 简介2. 基本原理和结构3. 应用场景4. 最新进展 三、开源1. 开源概念2. 开源模式3. 模型权重 四、再谈DeepSeek 前言 AI‌ 一、LLM LLM&#xff08;Large Language Mod…...

Linux /dev/null

/dev/null 是 Linux 和类 Unix 系统中一个特殊且非常有用的设备文件&#xff0c;也被称为空设备。下面为你详细介绍它的特点、用途和使用示例。 特点 写入丢弃&#xff1a;当向 /dev/null 写入数据时&#xff0c;这些数据会被立即丢弃&#xff0c;不会被保存到任何地方&#…...

Unity CommandBuffer绘制粒子系统网格显示

CommandBuffer是 Unity 提供的一种在渲染流程中插入自定义渲染命令的机制。在渲染粒子系统时&#xff0c;常规的渲染流程可能无法满足特定的渲染需求&#xff0c;而CommandBuffer允许开发者灵活地设置渲染参数、控制渲染顺序以及执行自定义的绘制操作。通过它&#xff0c;可以精…...

Java延时定时刷新Redis缓存

延时定时刷新Redis缓存 一、背景 项目需求&#xff1a;订阅接收一批实时数据&#xff0c;每分钟最高可接收120万条数据&#xff0c;并且分别更新到redis和数据库中&#xff1b;而用户请求查询消息只是低频操作。资源限制&#xff1a;由于项目预算有限&#xff0c;只有4台4C16…...

智能硬件定位技术发展趋势

在科技飞速进步的当下&#xff0c;智能硬件定位技术作为众多领域的关键支撑&#xff0c;正沿着多元且极具创新性的路径蓬勃发展&#xff0c;持续重塑我们的生活与工作方式。 一、精度提升的极致追求 当前&#xff0c;智能硬件定位精度虽已满足诸多日常应用&#xff0c;但未来…...

应用升级/灾备测试时使用guarantee 闪回点迅速回退

1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间&#xff0c; 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点&#xff0c;不需要开启数据库闪回。…...

Day131 | 灵神 | 回溯算法 | 子集型 子集

Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者写过很多次这道题了&#xff0c;不想写题解了&#xff0c;大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

ssc377d修改flash分区大小

1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

初学 pytest 记录

安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...

JAVA后端开发——多租户

数据隔离是多租户系统中的核心概念&#xff0c;确保一个租户&#xff08;在这个系统中可能是一个公司或一个独立的客户&#xff09;的数据对其他租户是不可见的。在 RuoYi 框架&#xff08;您当前项目所使用的基础框架&#xff09;中&#xff0c;这通常是通过在数据表中增加一个…...

Java + Spring Boot + Mybatis 实现批量插入

在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法&#xff1a;使用 MyBatis 的 <foreach> 标签和批处理模式&#xff08;ExecutorType.BATCH&#xff09;。 方法一&#xff1a;使用 XML 的 <foreach> 标签&#xff…...

MFC 抛体运动模拟:常见问题解决与界面美化

在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)

LeetCode 3309. 连接二进制表示可形成的最大数值&#xff08;中等&#xff09; 题目描述解题思路Java代码 题目描述 题目链接&#xff1a;LeetCode 3309. 连接二进制表示可形成的最大数值&#xff08;中等&#xff09; 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧

上周三&#xff0c;HubSpot宣布已构建与ChatGPT的深度集成&#xff0c;这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋&#xff0c;但同时也存在一些关于数据安全的担忧。 许多网络声音声称&#xff0c;这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...

深入理解Optional:处理空指针异常

1. 使用Optional处理可能为空的集合 在Java开发中&#xff0c;集合判空是一个常见但容易出错的场景。传统方式虽然可行&#xff0c;但存在一些潜在问题&#xff1a; // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...