原子操作 . 原理与底层实现

一.概述

原子操作(atomic operation)指的是由多步操作组成的一个操作。如果该操作不能原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。

现代操作系统中,一般都提供了原子操作来实现一些同步操作,所谓原子操作,也就是一个独立而不可分割的操作。在单核环境中,一般的意义下原子操作中线程不会被切换,线程切换要么在原子操作之前,要么在原子操作完成之后。更广泛的意义下原子操作是指一系列必须整体完成的操作步骤,如果任何一步操作没有完成,那么所有完成的步骤都必须回滚,这样就可以保证要么所有操作步骤都未完成,要么所有操作步骤都被完成。

例如在单核系统里,单个的机器指令可以看成是原子操作(如果有编译器优化、乱序执行等情况除外);在多核系统中,单个的机器指令就不是原子操作,因为多核系统里是多指令流并行运行的,一个核在执行一个指令时,其他核同时执行的指令有可能操作同一块内存区域,从而出现数据竞争现象。多核系统中的原子操作通常使用内存栅障(memory barrier)来实现,即一个CPU核在执行原子操作时,其他CPU核必须停止对内存操作或者不对指定的内存进行操作,这样才能避免数据竞争问题。

在C++11之前,C++标准中并没有对原子操作进行规定。vs和gcc编译器提供了原子操作的api。

二.原子操作的底层实现

实现方式:基于缓存加锁与总线加锁。

1.总线加锁

所谓总线锁就是使用处理器提供的一个lock#信号,当一个处理器在总线上输出此信号时,其他处理器的请求会被阻塞住,那么该处理器可以独占共享内存。

但总线锁定把cpu和内存之间的通信锁住了,这使得锁定期间,其他处理器不能操作其他内存地址的数据,所以开销比较大。

2.缓存加锁

第二个机制是通过缓存锁定来保证原子性。在同一时刻,我们只需保证对某个内存地址的操作是原子性即可,但总线锁定把CPU和内存之间的通信锁住了,这使得锁定期间,其他处理器不能操作其他内存地址的数据,所以总线锁定的开销比较大,目前处理器在某些场合下使用缓存锁定代替总线锁定来进行优化。

频繁使用的内存会缓存在处理器的L1、L2和L3高速缓存里,那么原子操作就可以直接在处理器内部缓存中进行,并不需要声明总线锁,在Pentium 6和目前的处理器中可以使用“缓存锁定”的方式来实现复杂的原子性。所谓“缓存锁定”是指内存区域如果被缓存在处理器的缓存行中,并且在Lock操作期间被锁定,那么当它执行锁操作回写到内存时,处理器不在总线上声言LOCK#信号,而是修改内部的内存地址,并允许它的缓存一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据,当其他处理器回写已被锁定的缓存行的数据时,会使缓存行无效,在如图2-3所示的例子中,当CPU1修改缓存行中的i时使用了缓存锁定,那么CPU2就不能同时缓存i的缓存行。
但是有两种情况下处理器不会使用缓存锁定。
第一种情况是:当操作的数据不能被缓存在处理器内部,或操作的数据跨多个缓存行(cache line)时,则处理器会调用总线锁定。
第二种情况是:有些处理器不支持缓存锁定。对于Intel 486和Pentium处理器,就算锁定的内存区域在处理器的缓存行中也会调用总线锁定。
针对以上两个机制,我们通过Intel处理器提供了很多Lock前缀的指令来实现。例如,位测试和修改指令:BTS、BTR、BTC;交换指令XADD、CMPXCHG,以及其他一些操作数和逻辑指令(如ADD、OR)等,被这些指令操作的内存区域就会加锁,导致其他处理器不能同时访问它。

三.编程使用

windows原子操作API:
Win32 API中常用的原子操作主要有三类,一种是加1减1操作,一种是比较交换操作,另外一种是赋值(写)操作。

(1)原子加1减1操作

LONG InterlockedIncrement( LONG volatile* Addend);
LONG InterlockedDecrement( LONG volatile* Addend);

(2)  比较并交换操作
 

LONG InterlockedCompareExchange( LONG volatile*Destination, LONG Exchange, LONG Comperand );

这个操作是先将Comperand的值和Destination指向变量的值进行比较,如果相等就将Exchange变量的值赋给Destination指向的变量。返回值为未修改前的Destination位置的初始值。

(3)原子写操作
 

LONG InterlockedExchange( LONG volatile* Target, LONG Value);

InterlockedExchange的作用为将Value的值赋给Target指向的变量,返回Target指向变量未被赋值前的值。

GCC编译器提供的原子操作API:
 

type __sync_fetch_and_add (type *ptr, type value);
type __sync_fetch_and_sub (type *ptr, type value);
type __sync_fetch_and_or (type *ptr, type value);
type __sync_fetch_and_and (type *ptr, type value);
type __sync_fetch_and_xor (type *ptr, type value);
type __sync_fetch_and_nand (type *ptr, type value);
type __sync_add_and_fetch (type *ptr, type value);
type __sync_sub_and_fetch (type *ptr, type value);
type __sync_or_and_fetch (type *ptr, type value);
type __sync_and_and_fetch (type *ptr, type value);
type __sync_xor_and_fetch (type *ptr, type value);
type __sync_nand_and_fetch (type *ptr, type value);

C++11提供的原子操作:
C++11中在<atomic>中定义了atomic模板类,atomic的模板参数类型可以为int、long、bool等等,C++中称为trivially copyable type。atomic_int、atomic_long为atomic模板实例化后的宏定义。atomic具体的原子操作函数可以参考http://www.cplusplus.com/reference/atomic/atomic/?kw=atomic。

代码:

#include <stdio.h>


#if 0

gcc数值原子操作API原理及应用
n++类:
	type __sync_fetch_and_add(type* ptr, type value, ...);// m+n
	type __sync_fetch_and_sub(type* ptr, type value, ...);// m-n
	type __sync_fetch_and_or(type* ptr, type value, ...);  // m|n
	type __sync_fetch_and_and(type* ptr, type value, ...);// m&n
	type __sync_fetch_and_xor(type* ptr, type value, ...);// m^n
	type __sync_fetch_and_nand(type* ptr, type value, ...);// (~m)&n
	/* 对应的伪代码 */
	{tmp = *ptr; *ptr op = value; returntmp; }
	{tmp = *ptr; *ptr = (~tmp) & value; returntmp; }   // nand

++n类:
	type __sync_add_and_fetch(type* ptr, type value, ...);// m+n
	type __sync_sub_and_fetch(type* ptr, type value, ...);// m-n
	type __sync_or_and_fetch(type* ptr, type value, ...);// m|n
	type __sync_and_and_fetch(type* ptr, type value, ...);// m&n
	type __sync_xor_and_fetch(type* ptr, type value, ...);// m^n
	type __sync_nand_and_fetch(type* ptr, type value, ...);// (~m)&n
	/* 对应的伪代码 */
	{*ptr op = value; return*ptr; }
	{*ptr = (~*ptr) & value; return*ptr; }// nand

CAS类:
	bool__sync_bool_compare_and_swap(type* ptr, type oldval, type newval, ...);
	type __sync_val_compare_and_swap(type* ptr, type oldval, type newval, ...);
	/* 对应的伪代码 */
	{if (*ptr == oldval) { *ptr = newval; returntrue; } else { returnfalse; }}
	{if (*ptr == oldval) { *ptr = newval; }returnoldval; }

#endif

int main() 
{
	int num = 0;

	/*
	 * n++;
	 * __sync_fetch_and_add(10, 3) = 10
	 * num = 13
	 */

	num = 10;
	printf("__sync_fetch_and_add(%d, %d) = %d\n", 10, 3, __sync_fetch_and_add(&num, 3));
	printf("num = %d\n", num);

	/*
	 * ++n;
	 * __sync_and_add_and_fetch(10, 3) = 13
	 * num = 13
	 */

	num = 10;
	printf("__sync_and_add_and_fetch(%d, %d) = %d\n", 10, 3, __sync_add_and_fetch(&num, 3));
	printf("num = %d\n", num);

	/*
	 * CAS, match
	 * __sync_val_compare_and_swap(10, 10, 2) = 10
	 * num = 2
	 */

	num = 10;
	printf("__sync_val_compare_and_swap(%d, %d, %d) = %d\n", 10, 10, 2, __sync_val_compare_and_swap(&num, 10, 2));
	printf("num = %d\n", num);

	/*
	 * CAS, not match
	 * __sync_val_compare_and_swap(10, 3, 5) = 10
	 * num = 10
	 */

	num = 10;
	printf("__sync_val_compare_and_swap(%d, %d, %d) = %d\n", 10, 3, 5, __sync_val_compare_and_swap(&num, 1, 2));
	printf("num = %d\n", num);

	return 0;
}

 

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
绝对想你所想,超乎想象!够详细,够给力! 目录 1. Jvm内存空间结构是什么样的? 1 程序计数器 1 Java栈 1 本地方法栈 2 堆 2 方法区 3 2. Jvm堆内存的划分结构和优化 3 2.1. 原理 6 2.1.1. 年轻代 6 2.1.2. 年老代 6 2.1.3. 持久代 7 2.2. 参数说明 8 2.3. 疑问解答 9 2.4. 垃圾回收器选择 10 2.4.1. 串行收集器 10 2.4.2. 并行收集器(吞吐量优先) 10 2.4.3. 并发收集器(响应时间优先) 11 2.4.4. 其它垃圾回收参数 11 2.5. 辅助信息参数设置 12 2.6. 关于参数名称等 13 3. JVM服务参数调优实战 14 3.1. 大型网站服务器案例 14 3.2. 内部集成构建服务器案例 15 4. 常量池在jvm的哪个空间里边? 17 5. jvm垃圾回收是什么时候触发的? 17 5.1. 那究竟GC为我们做了什么操作呢? 17 5.1.1. Jvm怎么判断对象可以回收了? 18 5.2. 下面我们来看一下几种垃圾收集算法 18 5.2.1. 在JDK1.2之前,使用的是引用计数器算法, 18 5.2.2. 根搜索算法 19 5.2.3. 引用的分类 19 5.3. 方法区也是会被回收的 20 5.4. 下面我们来逐一介绍一下每个垃圾回收器。 22 5.4.1. 1、 Serial收集器 22 5.4.2. 2、 ParNew收集器 23 5.4.3. 3、 ParallelScavenge 23 5.4.4. 4、 ParallelOld 24 5.4.5. 5、 SerialOld 24 5.4.6. 6、CMS 24 5.4.7. 7、 GarbageFirst(G1 ) 26 6. 常量池如何触发的垃圾回收? 28 7. 垃圾回收从哪里开始检测树进行回收?根节点是什么? 28 7.1. 目前java中可作为GC Root的对象有 29 8. Redis怎么判断对象可以清理了? 29 9. Redis怎么提高命中率? 29 10. Finalize中调用垃圾回收方法,再调用方法会回收么? 29 11. Int进行自增操作,如何保证线程安全? 30 12. Int a=1是原子方法,布尔类型进行赋值是原子方法么? 30 12.1. 多线程原子操作的安全性 31 13. Cas怎么实现unsafe? 32 13.1. Unsafe 32 13.2. CAS 32 13.3. 由CAS分析AtomicInteger原理 33 13.4. CAS的缺点 35 14. Map数据结构? 35 14.1. 一、定义 36 14.2. 二、构造函数 36 14.3. 三、数据结构 36 14.4. 四、存储实现:put(key,vlaue) 38 14.5. 五、读取实现:get(key) 41 15. 一百万数据放Arraylist数组,怎么放? 在哪个代? 42 15.1.1. 调整数组容量 42 16. Hashmap和 concurrentHashmap除了线程安全 还有什么区别,put的时候是怎么处理的。 43 17. 数据库组合索引,储存在一个叶子节点还是多个? 44 17.1. 索引的利弊与如何判定,是否需要索引: 44 17.1.1. 索引的好处 44 17.1.2. 索引的弊端 44 17.1.3. 如何判定是否须要创建索引 44 17.2. 复合索引优化 45 17.3. 下面是一些常见的索引限制问题 45 17.3.1. 使用不等于操作符(<>, !=) 45 17.3.2. 使用 is null 或 is not null 45 17.3.3. 使用函数 45 17.3.4. 比较不匹配的数据类型 46 17.4. 关于索引的排序 46 18. 数据库没有orderby默认如何排序。 46 19. 分布式事务如何实现。 46 19.1. 1 事务/分布式事务 47 19.1.1. 1.1 事务 47 19.1.2. 1.2 分布式事务与 XA 规范 47 19.2. 2 两阶段提交协议 47 19.2.1. 2.1 预提交阶段 48 19.2.2. 2.2 提交阶段 48 19.3. 3 分布式事务应用框架 48 19.3.1. 3.1 角色 48 19.3.2. 3.2 交互时序 49 19.3.3. 3.3 关键点 50 19.3.4. 参考 50 20. Jvm底层源码 51 21. 二叉树怎么实现的? 51 22. Executourservice 数据结构。哪种只能执行一个线程。 Cache怎么实现的 fixed线程如果中断,线程回自己销毁么? 51 23. 栅栏的原理实现。 51 23.1. 1. CyclicBarrier简介 51 23.2. 2. CyclicBarrier数据结构 52 23.3. 3. CyclicBarrier源码分析(基于JDK1.7.0_40) 52 23.3.1. 3.1 构造函数 52 23.3.2. 3.2 等待函数 53 23.4. 4. CyclicBarrier的使用示例 57 23.4.1. 示例1 57 23.4.2. 示例2 59 24. Blockingqueue有几种形式?各自的编码方式。 72 24.1. Queue接口 72 24.2. BlockingQueue接口 73 24.2.1. ArrayBlockingQueue 74 24.2.2. LinkedBlockingQueue 78 24.2.3. LinkedBlockingDeque(双向并发阻塞队列) 84 24.2.4. PriorityBlockingQueue(优先阻塞队列) 85 24.2.5. 总结一下阻塞队列 90 25. Tomcat 如何管理servlet? 90 26. Servlet生命周期 90 27. 没有缓存,如何实现领券并发操作? 92
第1讲:学习的要义 第2讲:Netty宏观理解 第3讲:Netty课程大纲深度解读 第4讲:项目环境搭建与Gradle配置 第5讲:Netty执行流程分析与重要组件介绍 第6讲:Netty回调与Channel执行流程分析 第7讲:Netty的Socket编程详解 第8讲:Netty多客户端连接与通信 第9讲:Netty读写检测机制与长连接要素 第10讲:Netty对WebSocket的支援 第11讲:Netty实现服务器端与客户端的长连接通信 第12讲:Google Protobuf详解 第13讲:定义Protobuf文件及消息详解 第14讲:Protobuf完整实例详解 第15讲:Protobuf集成Netty与多协议消息传递 第16讲:Protobuf多协议消息支援与工程最佳实践 第17讲:Protobuf使用最佳实践与Apache Thrift介绍 第18讲:Apache Thrift应用详解与实例剖析 第19讲:Apache Thrift原理与架构解析 第20讲:通过Apache Thrift实现Java与Python的RPC调用 第21讲:gRPC深入详解 第22讲:gRPC实践 第23讲:Gradle Wrapper在Gradle项目构建中的最佳实践 第24讲:gRPC整合Gradle与代码生成 第25讲:gRPC通信示例与JVM回调钩子 第26讲:gRPC服务器流式调用实现 第27讲:gRPC双向流式数据通信详解 第28讲:gRPC与Gradle流畅整合及问题解决的完整过程与思考 第29讲:Gradle插件问题解决方案与Nodejs环境搭建 第30讲:通过gRPC实现Java与Nodejs异构平台的RPC调用 第31讲:gRPC在Nodejs领域中的静态代码生成及与Java之间的RPC调用 第32讲:IO体系架构系统回顾与装饰模式的具体应用 第33讲:Java NIO深入详解与体系分析 第34讲:Buffer中各重要状态属性的含义与关系图解 第35讲:Java NIO核心类源码解读与分析 第36讲:文件通道用法详解 第37讲:Buffer深入详解 第38讲:NIO堆外内存与零拷贝深入讲解 第39讲:NIO中Scattering与Gathering深度解析 第40讲:Selector源码深入分析 第41讲:NIO网络访问模式分析 第42讲:NIO网络编程实例剖析 第43讲:NIO网络编程深度解析 第44讲:NIO网络客户端编写详解 第45讲:深入探索Java字符集编解码 第46讲:字符集编解码全方位解析 第47讲:Netty服务器与客户端编码模式回顾及源码分析准备 第48讲:Netty与NIO系统总结及NIO与Netty之间的关联关系分析 第49讲:零拷贝深入剖析及用户空间与内核空间切换方式 第50讲:零拷贝实例深度剖析 第51讲:NIO零拷贝彻底分析与Gather操作在零拷贝中的作用详解 第52讲:NioEventLoopGroup源码分析与线程数设定 第53讲:Netty对Executor的实现机制源码分析 第54讲:Netty服务端初始化过程与反射在其中的应用分析 第55讲:Netty提供的Future与ChannelFuture优势分析与源码讲解 第56讲:Netty服务器地址绑定底层源码分析 第57讲:Reactor模式透彻理解及其在Netty中的应用 第58讲:Reactor模式与Netty之间的关系详解 第59讲:Acceptor与Dispatcher角色分析 第60讲:Netty的自适应缓冲区分配策略与堆外内存创建方式 第61讲:Reactor模式5大角色彻底分析 第62讲:Reactor模式组件调用关系全景分析 第63讲:Reactor模式与Netty组件对比及Acceptor组件的作用分析 第64讲:Channel与ChannelPipeline关联关系及模式运用 第65讲:ChannelPipeline创建时机与高级拦截过滤器模式的运用 第66讲:Netty常量池实现及ChannelOption与Attribute作用分析 第67讲:Channel与ChannelHandler及ChannelHandlerContext之间的关系分析 第68讲:Netty核心四大组件关系与构建方式深度解读 第69讲:Netty初始化流程总结及Channel与ChannelHandlerContext作用域分析 第70讲:Channel注册流程深度解读 第71讲:Channel选择器工厂与轮询算法及注册底层实现 第72讲:Netty线程模型深度解读与架构设计原则 第73讲:Netty底层架构系统总结与应用实践 第74讲:Netty对于异步读写操作的架构思想与观察者模式的重要应用 第75讲:适配器模式与模板方法模式在入站处理器中的应用 第76讲:Netty项目开发过程中常见且重要事项分析 第77讲:Java NIO Buffer总结回顾与难点拓展 第78讲:Netty数据容器ByteBuf底层数据结构深度剖析 第79讲:Netty的ByteBuf底层实现大揭秘 第80讲:Netty复合缓冲区详解与3种缓冲区适用场景分析 第81讲:Netty引用计数的实现机制与自旋锁的使用技巧 第82讲:Netty引用计数原子更新揭秘与AtomicIntegerFieldUpdater深度剖析 第83讲:AtomicIntegerFieldUpdater实例演练与volatile关键字分析 第84讲:Netty引用计数注意事项与内存泄露检测方式 第85讲:Netty编解码器剖析与入站出站处理器详解 第86讲:Netty自定义编解码器与TCP粘包拆包问题 第87讲:Netty编解码器执行流程深入分析 第88讲:ReplayingDecoder源码分析与特性解读 第89讲:Netty常见且重要编解码器详解 第90讲:TCP粘包与拆包实例演示及分析 第91讲:Netty自定义协议与TCP粘包拆包问题解决之道 第92讲:精通并发与Netty课程总结与展望
精通并发与 netty 视频教程(2018)视频教程。 精通并发与netty视频教程(2018)视频教程 netty视频教程 Java视频教程目录: 1_学习的要义 2_Netty宏观理解 3_Netty课程大纲深度解读 4_项目环境搭建与Gradle配置 5_Netty执行流程分析与重要组件介绍 6_Netty回调与Channel执行流程分析 7_Netty的Socket编程详解 8_Netty多客户端连接与通信 9_Netty读写检测机制与长连接要素 10_Netty对WebSocket的支援 11_Netty实现服务器端与客户端的长连接通信 12_Google Protobuf详解 13_定义Protobuf文件及消息详解 14_Protobuf完整实例详解 15_Protobuf集成Netty与多协议消息传 递 16_Protobuf多协议消息支援与工程最佳实践 17_Protobuf使用最佳实践与Apache Thrift介绍 18_Apache Thrift应用详解与实例剖析 19_Apache Thrift原理与架构解析 20_通过Apache Thrift实现Java与Python的RPC调用 21_gRPC深入详解 22_gRPC实践 23_Gradle Wrapper在Gradle项目构建中的最佳实践 24_gRPC整合Gradle与代码生成 25_gRPC通信示例与JVM回调钩子 26_gRPC服务器流式调用实现 27_gRPC双向流式数据通信详解 28_gRPC与Gradle流畅整合及问题解决的完整过程与思考 29_Gradle插件问题解决方案与Nodejs环境搭建 30_通过gRPC实现Java与Nodejs异构平台的RPC调用 31_gRPC在Nodejs领域中的静态代码生成及与Java之间的RPC通信 32_IO体系架构系统回顾与装饰模式的具体应用 33_Java NIO深入详解与体系分析 34_Buffer中各重要状态属性的含义与关系图解 35_Java NIO核心类源码解读与分析 36_文件通道用法详解 37_Buffer深入详解 38_NIO堆外内存与零拷贝深入讲解 39_NIO中Scattering与Gathering深度解析 40_Selector源码深入分析 41_NIO网络访问模式分析 42_NIO网络编程实例剖析 43_NIO网络编程深度解析 44_NIO网络客户端编写详解 45_深入探索Java字符集编解码 46_字符集编解码全方位解析 47_Netty服务器与客户端编码模式回顾及源码分析准备 48_Netty与NIO系统总结及NIO与Netty之间的关联关系分析 49_零拷贝深入剖析及用户空间与内核空间切换方式 50_零拷贝实例深度剖析 51_NIO零拷贝彻底分析与Gather操作在零拷贝中的作用详解 52_NioEventLoopGroup源码分析与线程数设定 53_Netty对Executor的实现机制源码分析 54_Netty服务端初始化过程与反射在其中的应用分析 55_Netty提供的Future与ChannelFuture优势分析与源码讲解 56_Netty服务器地址绑定底层源码分析 57_Reactor模式透彻理解及其在Netty中的应用 58_Reactor模式与Netty之间的关系详解 59_Acceptor与Dispatcher角色分析 60_Netty的自适应缓冲区分配策略与堆外内存创建方式 61_Reactor模式5大角色彻底分析 62_Reactor模式组件调用关系全景分析 63_Reactor模式与Netty组件对比及Acceptor组件的作用分析 64_Channel与ChannelPipeline关联关系及模式运用 65_ChannelPipeline创建时机与高级拦截过滤器模式的运用 66_Netty常量池实现及ChannelOption与Attribute作用分析 67_Channel与ChannelHandler及ChannelHandlerContext之间的关系分析 68_Netty核心四大组件关系与构建方式深度解读 69_Netty初始化流程总结及Channel与ChannelHandlerContext作用域分析 70_Channel注册流程深度解读 71_Channel选择器工厂与轮询算法及注册底层实现 72_Netty线程模型深度解读与架构设计原则 73_Netty底层架构系统总结与应用实践 74_Netty对于异步读写操作的架构思想与观察者模式的重要应用 75_适配器模式与模板方法模式在入站处理器中的应用 76_Netty项目开发过程中常见且重要事项分析 77_Java NIO Buffer总结回顾与难点拓展 78_Netty数
操作系统中锁的底层实现原理是通过操作系统提供的锁机制来保证临界区资源的安全。在多线程编程中,为了保证数据的一致性,操作系统引入了锁机制。锁的本质是通过操作系统底层的机制来实现的。在硬件层面,CPU提供了原子操作、锁内存总线等机制,操作系统根据这些CPU硬件机制来实现锁。操作系统利用这些机制,可以保证在某一个时间点上,只能有一个线程进入临界区,从而保证临界区中操作数据的一致性。不同的高级语言可能会提供不同的锁接口,但底层调用的都是操作系统提供的锁。因此,要真正理解锁的底层实现原理,需要了解操作系统是如何实现锁的。 #### 引用[.reference_title] - *1* [java基础(十一)操作系统中锁机制](https://blog.csdn.net/xqhadoop/article/details/79592132)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [操作系统中锁的原理](https://blog.csdn.net/qq_40860852/article/details/102662308)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值