【C++】互斥锁(Mutex)和原子操作(Atomics)
详细探讨 C++ 中的并发、多线程、互斥锁(Mutex)和原子操作(Atomics)的概念及其区别,并附带代码示例。
1. C++ 并发与多线程 (Concurrency vs. Multithreading)
- 并发 (Concurrency):指系统能够处理多个任务的能力。这些任务的执行可以重叠,但不一定需要同时进行。并发更侧重于逻辑结构,即如何设计程序来应对多个独立的执行流。例如,一个 Web 服务器需要并发地处理多个客户端请求。
- 多线程 (Multithreading):是实现并发的一种具体技术。它允许单个进程内存在多个执行线程,这些线程共享进程的内存空间(如代码段、数据段、堆),但拥有各自独立的栈、程序计数器和寄存器。操作系统可以在多个线程之间快速切换(时间分片),或者在多核处理器上并行 (Parallelism) 执行多个线程,从而实现真正的同时执行。
简单来说,并发是问题域(处理多个任务),多线程是解决方案域(用线程实现并发)。
在 C++11 及之后的标准中,通过 <thread>
, <mutex>
, <atomic>
, <future>
, <condition_variable>
等头文件提供了标准库级别的并发和多线程支持。
2. 共享数据的问题:竞态条件 (Race Condition)
当多个线程并发访问(至少一个是写入操作)共享数据,并且最终的结果取决于线程执行的相对顺序时,就会发生竞态条件。这通常会导致程序行为不可预测、数据损坏或崩溃。
示例:无保护的计数器
#include <iostream>
#include <thread>
#include <vector>
#include <chrono> // 用于演示潜在问题long long counter = 0; // 共享变量void worker(int increments) {for (int i = 0; i < increments; ++i) {// --- 竞态条件发生点 ---// 这一步不是原子操作,它至少包含三步:// 1. 读取 counter 的当前值到寄存器// 2. 寄存器的值加 1// 3. 将寄存器的新值写回 counter// 多个线程可能在这些步骤之间交错执行,导致更新丢失counter++;// --- 竞态条件结束点 ---// 稍微增加延迟,更容易观察到竞态条件(实际代码中不应随意加sleep)// std::this_thread::sleep_for(std::chrono::nanoseconds(1));}
}int main() {const int num_threads = 10;const int increments_per_thread = 100000;std::vector<std::thread> threads;for (int i = 0; i < num_threads; ++i) {threads.emplace_back(worker, increments_per_thread);}for (auto& t : threads) {t.join();}long long expected_value = (long long)num_threads * increments_per_thread;std::cout << "Expected counter value: " << expected_value << std::endl;std::cout << "Actual counter value: " << counter << std::endl; // 很可能小于预期值return 0;
}
运行上述代码,你会发现 Actual counter value
几乎总是小于 Expected counter value
。这就是因为 counter++
操作不是原子的,多个线程同时读写导致了更新丢失。
为了解决竞态条件,我们需要同步机制,其中最常用的就是互斥锁和原子操作。
3. 互斥锁 (Mutex - Mutual Exclusion)
- 概念:互斥锁是一种同步原语,用于保护临界区 (Critical Section)——即访问共享资源的代码段。同一时刻,只有一个线程能获得(锁定)互斥锁。其他试图获取该锁的线程会被阻塞,直到持有锁的线程释放(解锁)它。
- 工作方式:
- 线程在进入临界区之前尝试
lock()
互斥锁。 - 如果锁未被持有,该线程获得锁并进入临界区。
- 如果锁已被其他线程持有,该线程阻塞(挂起),等待锁被释放。
- 线程完成临界区操作后,必须
unlock()
互斥锁,以便其他等待的线程可以获取它。
- 线程在进入临界区之前尝试
- C++ 实现:
std::mutex
: 基本的互斥锁。std::recursive_mutex
: 允许同一线程多次锁定,但必须对应次数解锁。std::timed_mutex
: 允许尝试在一定时间内锁定 (try_lock_for
,try_lock_until
)。std::recursive_timed_mutex
: 结合了recursive
和timed
特性。
- RAII (Resource Acquisition Is Initialization) 包装器:直接使用
lock()
和unlock()
容易忘记解锁,尤其是在有异常抛出的情况下。C++ 提供了 RAII 风格的锁管理类,它们在构造时自动获取锁,在析构时自动释放锁,大大提高了安全性。std::lock_guard<Mutex>
: 构造时锁定,析构时解锁。简单直接,不支持手动解锁或转移所有权。std::unique_lock<Mutex>
: 功能更强大,支持延迟锁定、尝试锁定、定时锁定、手动解锁、移动所有权(可以用于条件变量)。
示例:使用 std::mutex
和 std::lock_guard
保护计数器
#include <iostream>
#include <thread>
#include <vector>
#include <mutex> // 包含互斥锁头文件long long counter_mutex = 0; // 共享变量
std::mutex mtx; // 定义一个互斥锁实例void worker_mutex(int increments) {for (int i = 0; i < increments; ++i) {// 使用 lock_guard 自动管理锁的生命周期// 当 lock 对象创建时,它会调用 mtx.lock()std::lock_guard<std::mutex> lock(mtx);// --- 临界区开始 ---// 现在只有一个线程能执行这里的代码counter_mutex++;// --- 临界区结束 ---// 当 lock 对象离开作用域(在此次循环迭代结束时),// 其析构函数会自动调用 mtx.unlock()}
}int main() {const int num_threads = 10;const int increments_per_thread = 100000;std::vector<std::thread> threads;for (int i = 0; i < num_threads; ++i) {threads.emplace_back(worker_mutex, increments_per_thread);}for (auto& t : threads) {t.join();}long long expected_value = (long long)num_threads * increments_per_thread;std::cout << "Mutex - Expected counter value: " << expected_value << std::endl;std::cout << "Mutex - Actual counter value: " << counter_mutex << std::endl; // 现在结果总是正确的return 0;
}
使用互斥锁后,Actual counter value
将始终等于 Expected counter value
,因为对 counter_mutex
的访问被正确地序列化了。
4. 原子操作 (Atomic Operations)
- 概念:原子操作是指不可中断的操作。从其他线程的角度看,原子操作要么完全执行完毕,要么根本没有执行,不存在中间状态。它们通常由特殊的 CPU 指令实现,比使用互斥锁的开销要小得多,尤其是在低争用情况下。
- 工作方式:原子操作直接作用于内存位置,硬件保证其执行的原子性。例如,一个原子的
fetch_add
(或operator++
) 指令可以在单个、不可分割的步骤中完成读取、增加和写回的操作。 - C++ 实现:
std::atomic<T>
: 模板类,可以用于封装任何可平凡复制 (Trivially Copyable) 的类型T
(如int
,bool
,float
, 指针等),并为其提供原子操作。- 提供了多种原子操作,如
load
,store
,exchange
,compare_exchange_weak
/compare_exchange_strong
,fetch_add
,fetch_sub
等。 std::atomic_flag
: 一个简单的原子布尔标志,保证是无锁 (lock-free) 的。
- 内存序 (Memory Order):原子操作还可以指定内存序 (
std::memory_order
),用于控制编译器和处理器如何重排指令以及内存操作对其他线程的可见性。这是原子操作中比较复杂的部分,默认的std::memory_order_seq_cst
(顺序一致性)提供了最强的保证(所有线程看到的原子操作顺序一致),但可能性能最低。其他内存序(如relaxed
,acquire
,release
,acq_rel
)允许更宽松的重排,可能提高性能,但需要非常小心地使用以避免引入微妙的错误。
示例:使用 std::atomic
保护计数器
#include <iostream>
#include <thread>
#include <vector>
#include <atomic> // 包含原子操作头文件// 使用 std::atomic 封装共享变量
std::atomic<long long> atomic_counter = 0;void worker_atomic(int increments) {for (int i = 0; i < increments; ++i) {// 对 std::atomic 类型的 ++ 操作是原子的// 它通常会被编译成一条特殊的原子指令 (如 x86 的 LOCK INC)// 或者使用 CAS (Compare-And-Swap) 循环实现atomic_counter++;// 或者显式调用 fetch_add:// atomic_counter.fetch_add(1, std::memory_order_seq_cst); // 默认内存序}
}int main() {const int num_threads = 10;const int increments_per_thread = 100000;std::vector<std::thread> threads;for (int i = 0; i < num_threads; ++i) {threads.emplace_back(worker_atomic, increments_per_thread);}for (auto& t : threads) {t.join();}long long expected_value = (long long)num_threads * increments_per_thread;std::cout << "Atomic - Expected counter value: " << expected_value << std::endl;std::cout << "Atomic - Actual counter value: " << atomic_counter.load() << std::endl; // 结果总是正确的// 读取原子变量也需要用 load() 或隐式转换// 检查原子类型是否无锁std::cout << "std::atomic<long long> is lock-free? "<< (atomic_counter.is_lock_free() ? "Yes" : "No") << std::endl;// 在大多数现代平台上,对于内建类型(如 int, long long, bool, 指针)的原子操作通常是无锁的return 0;
}
使用 std::atomic
后,Actual counter value
同样始终等于 Expected counter value
。
5. 互斥锁 vs. 原子操作:区别总结
特性 | 互斥锁 (Mutex) | 原子操作 (Atomics) |
---|---|---|
机制 | 基于操作系统的锁机制(通常涉及系统调用) | 通常基于硬件指令(CPU 特殊指令) |
保护对象 | 保护代码块(临界区),可以包含复杂逻辑和多个变量 | 保护对单个变量的访问操作 |
粒度 | 较粗 | 较细 |
阻塞行为 | 线程获取不到锁时会阻塞(挂起,让出 CPU) | 通常是非阻塞的(或短暂自旋),不让出 CPU(除非是争用激烈时的自旋锁实现或非无锁原子) |
性能开销 | 较高(涉及上下文切换、内核调用) | 通常较低(尤其是无锁原子操作),但高争用下 CAS 可能变慢 |
死锁风险 | 有可能发生死锁(如 A 等 B 的锁,B 等 A 的锁) | 原子操作本身不会导致死锁 |
适用场景 | 保护复杂操作、多个共享变量、I/O 操作等 | 简单计数器、标志位、指针更新、状态机转换等简单操作 |
复杂度 | 概念相对简单,但需小心死锁和锁的粒度 | 基本使用简单,但内存序 (memory_order ) 的概念复杂且易错 |
无锁性 | 总是有锁的 (Blocking) | 可能是无锁 (Lock-free) 的(不保证所有类型/平台) |
关键选择依据:
-
保护范围:
- 如果你需要保护一个涉及多条语句、多个变量或复杂逻辑的临界区,使用互斥锁。例如,从一个队列弹出元素,然后更新队列大小和状态。
- 如果你只需要对单个变量执行简单的、不可分割的操作(如增减、读取、写入、比较并交换),使用原子操作通常更高效。
-
性能考虑:
- 对于简单操作和低到中等程度的线程争用,原子操作通常性能更好,因为它们避免了阻塞和上下文切换的开销。
- 在高争用情况下,原子操作(尤其是基于 CAS 的)可能会因为不断重试而消耗大量 CPU 周期(称为“活锁”或“自旋”现象),此时性能可能不如互斥锁(因为互斥锁会让线程睡眠,释放 CPU)。但是,现代 CPU 的原子指令优化得越来越好。
- 互斥锁的开销相对固定,但比无争用的原子操作要高。
-
死锁:
- 使用多个互斥锁时,必须非常小心锁的获取顺序,否则容易导致死锁。
- 原子操作本身不会引起死锁。
总结:
互斥锁和原子操作都是 C++ 中处理并发访问共享数据的重要工具。互斥锁提供了基于阻塞的、通用的临界区保护机制,适用于保护复杂的代码段。原子操作则提供了基于硬件指令的、通常更高效的、针对单个变量的不可分割操作。理解它们的区别和适用场景,是编写正确、高效的并发程序的关键。在现代 C++ 中,应优先考虑使用 std::atomic
来处理简单的共享状态(如标志、计数器),而将 std::mutex
用于保护更复杂的临界区。
相关文章:
【C++】互斥锁(Mutex)和原子操作(Atomics)
详细探讨 C 中的并发、多线程、互斥锁(Mutex)和原子操作(Atomics)的概念及其区别,并附带代码示例。 1. C 并发与多线程 (Concurrency vs. Multithreading) 并发 (Concurrency):指系统能够处理多个任务的能…...
Linux--命令行操作
一、Linux的作用 1.简单的文件操作 2.编程 3.支持系统和网络 二、多账号管理 1、我们需要在root账号下进行,可以用whoami来查询账号身份 2、adduser 你要创建的账号名 就可以创建一个账号 3、ls /home可以查看账号是否创立 4、使用passwd 创建账号名字的来设…...
具身系列——Diffusion Policy算法实现CartPole游戏
代码原理分析 1. 核心思想 该代码实现了一个基于扩散模型(Diffusion Model)的强化学习策略网络。扩散模型通过逐步去噪过程生成动作,核心思想是: • 前向过程:通过T步逐渐将专家动作添加高斯噪声,最终变成…...
4.用 Excel 录入数据
一 用 Excel 录入数据的两种方式 用鼠标键盘录入数据和从网上爬取数据。 二 用鼠标键盘录入数据 1.录入数据的规范 横着录入数据(横着一条条录入数据)。 2.使用快捷键进行数据录入 tab 键和 enter 键。 tab 键:向右移动一个单元格。 tab 键…...
nginx配置跳转设置Host有误导致报404问题
我们有个项目,前端调用了第三方接口。为了避免跨域,所以使用nginx进行转发。一直正常工作,相安无事。近日第三方调整了安全策略,http转换成https,原本使用ip,现在也改成使用域名,所以nginx这里我…...
接口/UI自动化面试题
一、UI自动化 1.1、接口和UI自动化有多少用例? 回答策略:根据接口设定用例,100个接口,自动化case在1500-2000左右。结合自身的项目,回答覆盖的主功能流程。 示例: 接口自动化的测试case一般需要根据接口数…...
Java 中调用语言模型(如 OpenAI、阿里云通义千问、Hugging Face 等)API 的详细步骤和示例代码,涵盖常见场景及注意事项
以下是 Java 中调用语言模型(如 OpenAI、阿里云通义千问、Hugging Face 等)API 的详细步骤和示例代码,涵盖常见场景及注意事项: 1. 常见语言模型 API 选择 (1) OpenAI API 特点:支持 GPT-3、GPT-3.5、GPT-4 等模型&a…...
搜广推校招面经六十
soul推荐算法 一、word2vec原理 参考一篇文章入门Word2Vec 二、word2vec正负采样怎么做的、word2vec采用的loss和原理 见【搜广推校招面经四、搜广推校招面经五十二、搜广推校招面经五十七】 不太理解为啥问这么多word2vec,索性直接整理一遍。 三、多路召回融合…...
红宝书第十二讲:详解JavaScript中的工厂模式与原型模式等各种设计模式
红宝书第十二讲:详解JavaScript中的工厂模式与原型模式等各种设计模式 资料取自《JavaScript高级程序设计(第5版)》。 查看总目录:红宝书学习大纲 工厂模式和原型模式解析 一、工厂模式:像订外卖一样创建对象 工厂模…...
Flutter完整开发实战详解(一、Dart语言和Flutter基础)
前言 在如今的 Flutter 大潮下,本系列是让你看完会安心的文章。本系列将完整讲述:如何快速从0开发一个完整的 Flutter APP,配套高完成度 Flutter 开源项目 GSYGithubAppFlutter。同时也会提供一些 Flutter 的开发细节技巧,并针对…...
Kafka 偏移量
在 Apache Kafka 中,偏移量(Offset)是一个非常重要的概念。它不仅用于标识消息的位置,还在多种场景中发挥关键作用。本文将详细介绍 Kafka 偏移量的核心概念及其使用场景。 一、偏移量的核心概念 1. 定义 偏移量是一个非负整数…...
手撕LRU缓存Java版(带输入输出)
由于面试手撕lru没撕出来,导致心态炸裂,今天特地练习了lru输入输出 手撕版,在每个函数里手动加上输出 public class LC146 {static class LRUCache{class Node{int key, value;Node prev, next;Node(int key, int value){this.key key;thi…...
Android 12系统源码_系统启动(二)Zygote进程
前言 Zygote(意为“受精卵”)是 Android 系统中的一个核心进程,负责 孵化(fork)应用进程,以优化应用启动速度和内存占用。它是 Android 系统启动后第一个由 init 进程启动的 Java 进程,后续所有…...
python之并发编程
并发编程介绍 串行、并行与并发的区别 进程、线程、协程的区别 1. 进程 (Process) 定义:进程是操作系统为运行中的程序分配的基本单位。每个进程都有独立的地址空间和资源(如内存、文件句柄等)。特点: 进程是资源分配的基本单位…...
极速全场景 MPP数据库starrocks介绍
目录 一、引子 二、起源 (一)前身 (二)定位 三、特点 (一)高性能架构 (二)实时分析 (三)高并发与扩展性 (四)兼容性与生态 …...
MySQL 表连接(内连接与外连接)
🏝️专栏:Mysql_猫咪-9527的博客-CSDN博客 🌅主页:猫咪-9527-CSDN博客 “欲穷千里目,更上一层楼。会当凌绝顶,一览众山小。” 目录 1、表连接的核心概念 1.1 为什么需要表连接? 2、内连接&a…...
重学Java基础篇—什么是快速失败(fail-fast)和安全失败(fail-safe)?
快速失败(fail-fast) 和 安全失败(fail-safe) 是两种不同的迭代器设计策略,主要用于处理集合(如 List、Map)在遍历过程中被修改的场景。 它们的核心区别在于对并发修改的容忍度和实现机制。 1…...
Redis 集群配置
在币圈交易所,Redis 集群的节点数量和内存大小通常根据交易所的规模、访问量、并发需求等因素来决定。一般来说,可以按照以下标准配置: Redis 集群节点数量 小型交易所(日活 < 10万,QPS < 10k)&…...
容器C++
string容器 string构造函数 #include<iostream> using namespace std; #include<string.h> void test01() {string s1;//默认构造const char* str "hello world";string s2(str);//传入char*cout << "s2" << s2 << endl;s…...
Git 基础入门:从概念到实践的版本控制指南
一、Git 核心概念解析 1. 仓库(Repository) Git 的核心存储单元,包含项目所有文件及其完整历史记录。分为本地仓库(开发者本地副本)和远程仓库(如 GitHub、GitLab 等云端存储),支持…...
蓝桥杯真题_小蓝和小桥的讨论
小蓝和小桥的讨论 问题描述 小蓝和小桥是一所高中的好朋友,他们正在讨论下一次的课程。这节课需要讨论 nn 个主题,第 ii 个主题对老师来说有 aia**i 的趣味度,对学生来说有 bib**i 的趣味度。 小蓝认为,如果一个主题对老师来说…...
【C++游戏引擎开发】《线性代数》(2):矩阵加减法与SIMD集成
一、矩阵加减法数学原理 1.1 定义 逐元素操作:运算仅针对相同位置的元素,不涉及矩阵乘法或行列变换。交换律与结合律: 加法满足交换律(A + B = B + A)和结合律( ( A + B ) + C = A + ( B + C ) )。 减法不满足交换律(A − B ≠ B − A)。1.2 公式 C i j = …...
HTML应用指南:利用POST请求获取全国小鹏汽车的充电桩位置信息
在新能源汽车快速发展的背景下,充电桩的分布和可用性成为影响用户体验的关键因素之一。随着全球对环境保护意识的增强以及政府对新能源政策的支持,越来越多的消费者倾向于选择电动汽车作为日常出行工具。然而,充电设施是否完备、便捷直接影响…...
工具介绍《WireShark》
Wireshark 过滤命令中符号含义详解 一、比较运算符 Wireshark 支持两种比较运算符语法:英文缩写(如 eq)和 C语言风格符号(如 ),两者功能等价。 符号(英文缩写)C语言风格符号含义示…...
深入理解 Linux 中磁盘空间驱动的编写:从原理到实践
在编写 Linux 内核中的磁盘空间驱动时,理解不同类型的存储设备及其在内核中的工作模式至关重要。常见的存储设备主要分为两类:采用 MTD(Memory Technology Device)模式的原始闪存设备(如 NAND、NOR Flash)&…...
flutter android端抓包工具
flutter做的android app,使用fiddler抓不了包,现介绍一款能支持flutter的抓包工具Reqable,使用方法如下: 1、下载电脑端安装包 下载地址为【https://reqable.com/zh-CN/download/】 2、还是在上述地址下载 android 端apk…...
知识周汇 | 用 matplotlib 轻松绘制折线图、散点图、柱状图、直方图
目录 前言 折线图 散点图 柱状图 直方图 组合图:柱状图和折线图 1. 导入库 2. 定义组合图函数 3. 设置中文字体和样式 4. 创建画布和子图 5. 绘制柱状图 6. 绘制折线图 7. 美化图表 8. 保存和显示图表 9. 调用函数 总结 前言 matplotlib 是 Python…...
Ribbon负载均衡的深度解析与应用
在微服务架构中,服务之间的调用频繁且复杂,因此负载均衡显得尤为重要。Spring Cloud生态系统中,Ribbon作为一个客户端负载均衡器,扮演着关键的角色。它不仅能提高系统的响应速度,还能确保系统的稳定性和可用性。接下来…...
Neo4j GDS-06-neo4j GDS 库中社区检测算法介绍
neo4j apoc 系列 Neo4j APOC-01-图数据库 apoc 插件介绍 Neo4j APOC-01-图数据库 apoc 插件安装 neo4j on windows10 Neo4j APOC-03-图数据库 apoc 实战使用使用 Neo4j APOC-04-图数据库 apoc 实战使用使用 apoc.path.spanningTree 最小生成树 Neo4j APOC-05-图数据库 apo…...
Android 删除aar中的一个类 aar包冲突 aar类冲突 删除aar中的一个包
Duplicate class com.xxxa.naviauto.sdk.listener.OnChangeListener found in modules jetified-xxxa-sdk-v1.1.2-release-runtime (:xxx-sdk-v1.1.2-release:) and jetified-xxxb-sdk-1.1.3-runtime (:xxxb-sdk-1.1.3:) A.aar B.aar 有类冲突; 使用 exclude 排除本…...
【老电脑翻新】华硕A456U(换电池+换固态+光驱换机械+重装系统+重装系统后开始菜单失灵问题解决)
前言 电脑华硕A456U买来快10年了,倒是还能用,就是比较卡,cpu占比总是100%,之前已经加过内存条了。想要不换个固态看看。 省流:没太大效果。 记录一下拆机&换固态的过程 准备 西部数据固态硬盘480G WD Green S…...
Unity 简单使用Addressables加载SpriteAtlas图集资源
思路很简单,传入图集名和资源名,利用Addressables提供的异步加载方式从ab包中加载。加载完成后存储进缓存字典里,以供后续使用。 添加引用计数,防止多个地方使用同一图集时,不会提前释放 using UnityEngine; using U…...
stable diffusion本地安装
1. 基本环境准备 安装conda 环境 pytorch基础学习-CSDN博客 创建虚拟环境: conda create -n sd python3.10 一定要指定用3.10,过高的版本会提示错误: 激活启用环境: conda activate sd 设置pip国内镜像源: pip conf…...
MQ 如何保证数据一致性?
大家好,我是苏三,又跟大家见面了。 前言 上个月,我们有个电商系统出了个灵异事件:用户支付成功了,但订单状态死活不改成“已发货”。 折腾了半天才定位到问题:订单服务的MQ消息,像人间蒸发一…...
spring @Autowired对属性、set方法,构造器的分别使用,以及配合 @Autowired 和 @Qualifier避免歧义性的综合使用案例
代码结构 依赖注入 在Spring IoC容器的概念中,主要是使用依赖注入来实现Bean之间的依赖关系的 举例 例如,人类(Person)有时候会利用动物(Animal)来完成一些事情,狗(Dog࿰…...
Ubuntu 系统上完全卸载 Docker
以下是在 Ubuntu 系统上完全卸载 Docker 的分步指南 一.卸载验证 二.卸载步骤 1.停止 Docker 服务 sudo systemctl stop docker.socket sudo systemctl stop docker.service2.卸载 Docker 软件包 # 移除 Docker 核心组件 sudo apt-get purge -y \docker-ce \docker-ce-cli …...
国际机构Gartner发布2025年网络安全趋势
转自:中国新闻网 中新网北京3月14日电 国际机构高德纳(Gartner)14日发布的消息称,网络安全和风险管理在2025年“面临挑战与机遇并存的局面”,“实现转型和提高弹性”对确保企业在快速变化的数字世界中,实现安全且可持续的创新至关…...
设计秒杀系统(高并发的分布式系统)
学海无涯,志当存远。燃心砺志,奋进不辍。 愿诸君得此鸡汤,如沐春风,事业有成。 若觉此言甚善,烦请赐赞一枚,共励学途,同铸辉煌! 思路 处理高并发 流量削峰:限流…...
C# 打印模板设计-ACTIVEX打印控件-多模板加载
一、启动软件 using System; using System.Collections.Generic; using System.Windows.Forms; using System.Data;namespace Print {static class Program{/// <summary>/// 应用程序的主入口点。/// </summary>[STAThread]static void Main(){//使用模板前必须…...
华为HCIE方向那么多应该如何选择?
在华为认证体系里,HCIE作为最高等级的认证,是ICT领域专业实力的有力象征。HCIE设置了多个细分方向,这些方向宛如不同的专业赛道,为期望在ICT行业深入发展的人提供了丰富的选择。今天,咱们就来好好聊聊华为HCIE方向的相…...
五子棋游戏
五子棋 - deveco <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>五子棋 - deveco</title>…...
Vue3.5 企业级管理系统实战(十):面包屑导航组件
1 breadcrumb 组件 1.1 安装插件 path-to-regexp 首先,我们需要安装插件 path-to-regexp,以便在下面的面包屑组件中对路由地址进行解析。 path-to-regexp是一个 JavaScript 库,可将路径字符串转化为正则表达式,广泛用于 Web 开发…...
【python】OpenCV—Hand Detection
文章目录 1、功能描述2、代码实现3、效果展示4、完整代码5、参考6、其它手部检测和手势识别的方案 更多有趣的代码示例,可参考【Programming】 1、功能描述 基于 opencv-python 和 mediapipe 进行手部检测 2、代码实现 导入必要的库函数 import cv2 import media…...
[ComfyUI] SDXL Prompt Styler 自定义节点的作用解析
1. SDXL Prompt Styler 的位置与基本功能 在 ComfyUI 的 “新建节点” → “实用工具” 下,可以找到 Style 节点(SDXL Prompt Styler)。该节点的主要作用是对输入的描述进行结构化处理,并在转换为 Stable Diffusion XL (SDXL) 提示词时,自动补充风格相关的内容,使提示词…...
Oracle-rman restore遭遇RMAN-03002与ORA-19563
文章目录 在原DB上检查是否有重复的文件名:查看rman恢复的日志修正重名部分重新执行rman恢复结论: 在 RMAN 恢复过程中,遇到RMAN-03002连同ORA-19563:错误。 操作是将 Oracle 10.0.5的数据库备份从 RMAN備份恢复到另一台测试主机的同一个目录…...
FPGA中串行执行方式之使用时钟分频或延迟的方式
FPGA中串行执行方式之使用时钟分频或延迟的方式 在FPGA设计中,时钟分频和延迟是两种常用的技术,用于控制信号的时序或调整信号的频率。它们可以用来实现简单的串行逻辑、状态转移或其他需要时间控制的场景。 时钟分频(Clock Division) 基本原理:时钟分频是通过将输入…...
Dubbo 全面解析:从 RPC 核心到服务治理实践
一、分布式系统与 RPC 框架概述 在当今互联网时代,随着业务规模的不断扩大,单体架构已经无法满足高并发、高可用的需求,分布式系统架构成为主流选择。而在分布式系统中,远程服务调用(Remote Procedure Call࿰…...
JavaScript 调试入门指南
JavaScript 调试入门指南 一、调试准备阶段 1. 必备工具配置 浏览器套件:安装最新Chrome102+,开启实验性功能(地址栏输入chrome://flags/#enable-devtools-experiments)编辑器集成:VS Code安装以下扩展: JavaScript Debugger:支持浏览器与Node.js双端调试Error Lens:实…...
不能将下载行为传输到IDM
目录预览 一、问题描述二、原因分析三、解决方案四、参考链接 一、问题描述 安装IDM后,调用IDM下载软件显示:不能将下载行为传输到IDM,Error 0x80029C4A 二、原因分析 可能是识别浏览器插件不到,或者本地的插件版本不对导致的 三…...
spring security 认证流程分析
Spring Security 认证流程分析 Spring Security 的认证流程是一个模块化且可扩展的过程,核心围绕 过滤器链 和 认证组件 协作实现。以下是详细流程分析: 1. 请求拦截与过滤器链 • 入口:所有 HTTP 请求经过 Spring Security 的过滤器链。 •…...