.net core 线程锁,互斥锁,自旋锁,混合锁
线程锁、互斥锁、自旋锁和混合锁是多线程编程中的重要概念,它们用于控制对共享资源的访问,避免数据竞争和不一致性。每种锁有其特定的适用场景和特点。我们来逐一解释它们,并进行比较。
1. 线程锁(Thread Lock)
线程锁的概念泛指任何用于同步多线程访问共享资源的机制。它的目的是确保在同一时刻只有一个线程可以访问资源,从而避免多个线程并发访问时发生数据竞争(race condition)或资源不一致。
线程锁通常是通过以下几种锁机制来实现的:
- 互斥锁(Mutex)
- 自旋锁(SpinLock)
- 读写锁(ReadWriteLock)
- 信号量(Semaphore)
- 临界区(CriticalSection)
不同类型的锁有不同的实现方式和适用场景。
2. 互斥锁(Mutex)
互斥锁(Mutex)是一种最常见的同步原语,用于控制对共享资源的访问。它的基本思想是:如果一个线程已经获得了锁,其他线程必须等待,直到锁被释放,才能继续执行。
特点
- 线程阻塞:当一个线程尝试获取互斥锁时,如果锁已被其他线程持有,线程会被挂起,直到锁可用为止。
- 适用于长时间持有锁的情况:如果临界区代码较长,或线程会执行大量计算时,使用互斥锁能有效避免 CPU 资源的浪费。
- 系统开销较高:挂起和恢复线程的操作比自旋等待更消耗系统资源。
示例:C# 中的 lock
(实际上是基于 Monitor
的实现)
class Program
{private static readonly object lockObj = new object();private static int counter = 0;static void Main(){Thread thread1 = new Thread(IncrementCounter);Thread thread2 = new Thread(IncrementCounter);thread1.Start();thread2.Start();thread1.Join();thread2.Join();Console.WriteLine("Final counter value: " + counter);}static void IncrementCounter(){lock (lockObj) // 获取锁{counter++; // 临界区Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} incremented counter to {counter}");}}
}
3. 自旋锁(SpinLock)
自旋锁是一种非常轻量级的同步机制,线程在尝试获取锁时,不会被挂起,而是会在一个循环中不断检查锁是否已经释放。线程会不断“自旋”并消耗 CPU 时间,直到获得锁。
特点
- 忙等待:当一个线程请求自旋锁时,如果锁已经被其他线程持有,它会不断地检查锁是否已被释放,这种行为被称为“自旋”。
- 适用于锁持有时间短的场景:当临界区代码执行时间非常短时,自旋锁可以避免线程挂起和恢复的高开销。
- CPU 资源消耗较高:如果锁持有时间较长,多个线程可能会造成大量 CPU 资源的浪费。
示例:C# 中的 SpinLock
using System;
using System.Threading;class Program
{private static SpinLock spinLock = new SpinLock();private static int counter = 0;static void Main(){Thread thread1 = new Thread(IncrementCounter);Thread thread2 = new Thread(IncrementCounter);thread1.Start();thread2.Start();thread1.Join();thread2.Join();Console.WriteLine("Final counter value: " + counter);}static void IncrementCounter(){bool lockTaken = false;try{spinLock.Enter(ref lockTaken); // 获取锁counter++; // 临界区Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} incremented counter to {counter}");}finally{if (lockTaken)spinLock.Exit(); // 释放锁}}
}
4. 混合锁(Hybrid Lock)
混合锁是一种结合了互斥锁和自旋锁的锁机制,它通常用于试图在自旋锁和互斥锁之间根据具体情况进行切换,旨在提高多线程程序的效率。
混合锁的思想是:
- 自旋锁:在锁争用轻微、临界区代码执行时间短的情况下,使用自旋锁来减少线程挂起带来的性能开销。
- 互斥锁:如果自旋锁的时间过长,系统会自动切换为互斥锁,这样线程会被挂起,避免浪费过多 CPU 时间。
特点
- 适应性强:混合锁通过平衡自旋和线程挂起的开销,避免在锁争用过于严重时造成资源浪费。
- 自动调整:当争用变得严重时,混合锁会自动切换为互斥锁,而在争用轻微时,它会使用自旋来避免不必要的开销。
示例:C# 中没有直接的混合锁类,但可以通过自定义逻辑来实现类似功能。
using System;
using System.Threading;class Program
{private static SpinLock spinLock = new SpinLock();private static object mutex = new object();private static int counter = 0;static void Main(){Thread thread1 = new Thread(IncrementCounter);Thread thread2 = new Thread(IncrementCounter);thread1.Start();thread2.Start();thread1.Join();thread2.Join();Console.WriteLine("Final counter value: " + counter);}static void IncrementCounter(){bool lockTaken = false;try{// 尝试自旋锁if (!spinLock.TryEnter(100)) // 如果锁在 100ms 内未被获取{// 自旋失败,使用互斥锁lock (mutex){counter++;Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} incremented counter to {counter}");}}else{// 获取自旋锁counter++;Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} incremented counter to {counter}");}}finally{if (lockTaken)spinLock.Exit();}}
}
自旋锁、互斥锁和混合锁的比较
特性/锁类型 | 互斥锁(Mutex) | 自旋锁(SpinLock) | 混合锁(Hybrid Lock) |
---|---|---|---|
锁获取方式 | 阻塞,线程被挂起 | 自旋,线程忙等待锁 | 根据锁的争用情况自旋或阻塞 |
适用场景 | 锁持有时间长、锁竞争激烈的情况 | 锁持有时间短、锁竞争轻的情况 | 锁持有时间变化,既有自旋又有阻塞 |
性能开销 | 较高,线程挂起与恢复开销较大 | 较低,但如果竞争严重会浪费 CPU 资源 | 较低,可以根据情况自动调整 |
适用性 | 多线程竞争较高的场景 | 低竞争、锁持有时间短的场景 | 高竞争情况下动态选择锁类型 |
总结
- 互斥锁 适用于锁持有时间较长、竞争激烈的场景,能有效避免资源争用,但可能会导致性能瓶颈。
- 自旋锁 适用于锁持有时间非常短的场景,能够避免线程上下文切换的开销,但如果锁争用严重,可能会浪费大量 CPU 资源。
- 混合锁 结合了自旋锁和互斥锁的优点,能根据锁争用情况动态选择自旋或挂起,从而提供更好的性能和适应性。
选择哪种锁取决于具体的应用场景和性能需求。在高并发、高竞争的环境中,混合锁可能是最优选择,而在低竞争或快速临界区的情况下,自旋锁也许是最合适的。
5.信号量
信号量(Semaphore) 是一种用于多线程编程中的同步机制,用于控制对共享资源的访问,特别是在资源数量有限时,它能够限制并发访问的线程数目。信号量通过维护一个计数器来管理线程的访问。线程在进入临界区之前,需要检查信号量的计数值,只有计数值大于零时,线程才能进入;当线程完成工作后,信号量的计数值会增加,允许其他线程进入。
信号量的基本概念
- 计数器:信号量内部有一个整数计数器,表示可用的资源数量或允许并发执行的线程数。
- P操作(或称为 Wait 或 Acquire):线程尝试减少信号量的计数器。如果信号量的计数器大于零,线程会成功进入临界区,计数器减一。如果计数器为零,线程会被阻塞,直到计数器大于零。
- V操作(或称为 Signal 或 Release):线程在完成工作后,增加信号量的计数器,允许其他被阻塞的线程继续执行。
信号量的类型
-
计数信号量(Counting Semaphore):计数信号量的计数器值可以是任意非负整数,表示允许访问的资源数量或线程数。例如,如果有 5 个资源或 5 个线程可以并发执行,信号量的初始值为 5。每当一个线程获得资源时,计数器减一,释放资源时计数器加一。
-
二值信号量(Binary Semaphore):二值信号量是计数信号量的一种特殊情况,计数器值仅为 0 或 1。它常常用于控制一个线程的互斥访问,类似于互斥锁(Mutex)。二值信号量也被称为 互斥信号量,因为它的行为与互斥锁非常相似。
信号量的应用场景
-
控制并发访问:信号量通常用于控制某些资源的并发访问,限制同时访问某些共享资源的线程数。例如,数据库连接池中的数据库连接数有限,信号量可以用来确保不超过最大连接数。
-
限制资源数量:例如,线程池中只允许一定数量的线程同时运行任务,超出限制的线程会被阻塞,直到其他线程完成任务并释放资源。
-
线程同步:在一些需要线程同步的场景中,信号量可以用来控制线程的执行顺序或协调多个线程之间的操作。
示例:C# 中使用信号量
假设我们有一个共享的数据库连接池,最多只允许 3 个线程同时访问数据库。我们可以使用信号量来限制并发访问。
using System;
using System.Threading;class Program
{// 初始化信号量,最多允许 3 个线程并发访问private static Semaphore semaphore = new Semaphore(3, 3); static void Main(){// 创建并启动 5 个线程for (int i = 0; i < 5; i++){int threadId = i;Thread thread = new Thread(() => AccessDatabase(threadId));thread.Start();}}static void AccessDatabase(int threadId){Console.WriteLine($"Thread {threadId} trying to access database...");// 尝试获取信号量semaphore.WaitOne(); // 如果信号量计数器大于 0,则进入临界区,计数器减 1try{Console.WriteLine($"Thread {threadId} is accessing the database.");Thread.Sleep(2000); // 模拟数据库访问操作Console.WriteLine($"Thread {threadId} is done with the database.");}finally{// 释放信号量semaphore.Release(); // 释放资源,信号量计数器加 1}}
}
代码解释
-
信号量初始化:我们使用
Semaphore(3, 3)
来创建一个信号量,初始值为 3,表示最多允许 3 个线程同时访问共享资源(这里是模拟的数据库连接)。信号量的最大值也是 3,意味着最多只能有 3 个线程持有信号量。 -
线程尝试访问资源:每个线程在访问数据库之前调用
semaphore.WaitOne()
来尝试获取信号量。如果信号量的计数器大于 0,线程就能成功获得信号量并进入临界区,计数器减 1;如果计数器为 0,线程会被阻塞,直到其他线程释放信号量。 -
线程完成后释放信号量:在
finally
块中,线程完成工作后调用semaphore.Release()
来释放信号量,允许其他线程访问共享资源。此时,信号量计数器加 1。
信号量与其他同步机制的比较
特性/机制 | 信号量(Semaphore) | 互斥锁(Mutex) | 读写锁(ReadWriteLock) | 自旋锁(SpinLock) |
---|---|---|---|---|
锁粒度 | 用于控制资源数量 | 用于单个资源的互斥访问 | 分别对读和写操作加锁 | 轻量级的锁,用于短时间临界区 |
适用场景 | 控制资源数量,限流,多线程并发访问 | 防止多线程同时访问共享资源 | 允许多个读者同时访问,写者互斥 | 高并发且锁持有时间短的场景 |
阻塞方式 | 阻塞线程或继续执行 | 阻塞线程 | 阻塞线程 | 自旋,直到获得锁 |
优点 | 控制并发数量,灵活高效 | 确保资源的独占访问 | 提高读取性能,允许并发读取 | 轻量级,减少上下文切换的开销 |
总结
信号量是一种用于控制并发访问共享资源的同步工具,特别适用于资源数量有限的场景。它通过计数器来控制允许访问的线程数量,支持灵活的线程同步与调度。根据资源需求,信号量能够控制多个线程的并发执行,避免资源争用和冲突。
6.读写锁
读写锁是一种特殊类型的锁,它允许多个线程同时读取共享数据,但在写操作时,只能有一个线程进行写操作,而且在写操作时,其他线程不能进行读操作或写操作。读写锁旨在提高读操作多、写操作少的场景下的性能,尤其是在数据读取频繁而修改较少的情况下。
读写锁的工作原理
- 读锁:多个线程可以同时持有读锁,只要没有线程持有写锁。读锁不会阻止其他线程获取读锁。
- 写锁:写锁是排他性的,只有一个线程可以持有写锁。并且在持有写锁时,所有其他线程(无论是读锁还是写锁)都不能访问共享资源。
- 读写锁的基本设计思想是:在没有写操作的情况下,允许多个线程并发读取;但是一旦有写操作开始,必须保证其他线程都无法访问资源。
C# 中的 ReaderWriterLockSlim
在 C# 中,ReaderWriterLockSlim
类提供了类似的功能,用于处理并发读写操作。
EnterReadLock()
:获取读锁,允许多个线程并发读取。EnterWriteLock()
:获取写锁,排他性锁定,阻塞所有读写操作。
using System;
using System.Threading;class Program
{static ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();static int sharedResource = 0;static void Main(){// 创建并发读取的线程Thread readThread1 = new Thread(() =>{rwLock.EnterReadLock(); // 获取读锁try{Console.WriteLine("Read Thread 1: " + sharedResource);}finally{rwLock.ExitReadLock(); // 释放读锁}});Thread readThread2 = new Thread(() =>{rwLock.EnterReadLock(); // 获取读锁try{Console.WriteLine("Read Thread 2: " + sharedResource);}finally{rwLock.ExitReadLock(); // 释放读锁}});// 创建写线程Thread writeThread = new Thread(() =>{rwLock.EnterWriteLock(); // 获取写锁try{sharedResource++;Console.WriteLine("Write Thread: " + sharedResource);}finally{rwLock.ExitWriteLock(); // 释放写锁}});// 启动线程readThread1.Start();readThread2.Start();writeThread.Start();}
}
读写锁的优势和适用场景
优势:
- 提高并发性能:当读操作频繁而写操作较少时,使用读写锁可以显著提高系统的并发性能。多个线程可以同时进行读操作,而无需等待锁的释放。
- 减少锁竞争:由于读操作不互斥,可以避免频繁的锁竞争,尤其在读操作占主导的场景中。
- 提供更细粒度的控制:相比传统的互斥锁(如
ReentrantLock
),读写锁提供了更细粒度的锁机制,让读写操作更加高效。
适用场景:
- 读多写少的场景:比如缓存、日志读取、数据库查询等,系统中的大多数操作是读操作,少量写操作。
- 高并发读取:需要多个线程频繁读取共享资源,但写操作较少的应用(例如 Web 应用中的数据查询)。
- 低并发写操作:确保在写操作发生时,不会有其他线程同时执行读操作,保持数据一致性。
需要注意的问题:
- 写操作可能会阻塞读操作:如果有大量的读操作而只有少数的写操作,写操作会造成较长时间的阻塞,导致性能下降。
- 死锁风险:在设计并发系统时,如果不小心使用了写锁嵌套或读锁嵌套,可能会导致死锁。
总结
- 读写锁的设计旨在提高系统的并发性,特别是在读多写少的场景下。通过区分读锁和写锁,读写锁允许多个线程并行读操作,但写操作则是排他性的。
- 它适用于需要大量读取操作且写操作相对较少的场景,可以有效减少线程之间的锁竞争,提高系统的性能。
相关文章:

.net core 线程锁,互斥锁,自旋锁,混合锁
线程锁、互斥锁、自旋锁和混合锁是多线程编程中的重要概念,它们用于控制对共享资源的访问,避免数据竞争和不一致性。每种锁有其特定的适用场景和特点。我们来逐一解释它们,并进行比较。 1. 线程锁(Thread Lock) 线程…...

【DevOps】Jenkins项目发布
Jenkins项目发布 文章目录 Jenkins项目发布前言资源列表基础环境一、Jenkins发布静态网站1.1、项目介绍1.2、部署Web1.3、准备gitlab1.4、配置gitlab1.5、创建项目1.6、推送代码 二、Jenkins中创建gitlab凭据2.1、创建凭据2.2、在Jenkins中添加远程主机2.3、获取gitlab项目的UR…...

C# OpenCV机器视觉:霍夫变换
在一个阳光灿烂得近乎放肆的午后,阿强的实验室就像被施了魔法的科学城堡,到处闪耀着神秘的科技光芒。阿强呢,像个即将踏上惊险征程的探险家,一屁股坐在那堆满奇奇怪怪设备的桌前,眼神中透露出按捺不住的兴奋劲儿&#…...

Kraft模式安装Kafka(含常规、容器两种安装方式)
一、#创作灵感# 公司使用Kafka的软件项目较多,故写技术笔记巩固知识要点 二、软件环境 - Kafka 3.9.0 官方下载地址:Kafka 3.9.0 - Docker Desktop 4.37 容器图形化工具 官方下载地址:Docker Desktop 4.37 特别说明 - Docker Desktop…...

Linux驱动开发(16):输入子系统–电容触摸驱动实验
有关电容触摸的基础知识内容可以参考野火STM32相关教程,这里只介绍电容触摸驱动的相关内容。 本章配套源码、设备树以及更新固件位于“~/embed_linux_driver_tutorial_imx6_code/linux_driver/touch_scream_GTxxx”目录下。 触摸面板通过双面胶粘在显示屏上&#…...

《深入浅出HTTPS》读书笔记(24):椭圆曲线密码学
《深入浅出HTTPS》读书笔记(24):椭圆曲线密码学 为了保证DH的密钥对不被破解,提升安全性的主要手段就是增加密钥对的长度,但是长度越长,性能越低。 为了解决性能问题,需要…...

现代光学基础5
总结自老师的讲义 yt5 开卷考试复习资料:光探测器与光伏技术 目录 光探测器(Photodetector) 工作原理二极管电路连接方式响应度(Responsivity)微弱光检测超导纳米线单光子探测光电二极管噪声 太阳能电池࿰…...

力扣hot100——贪心
121. 买卖股票的最佳时机 class Solution { public:int maxProfit(vector<int>& a) {if (a.size() 1) return 0;int ans 0;int mi a[0];for (int i 1; i < a.size(); i) {ans max(ans, a[i] - mi);mi min(mi, a[i]);}return ans;} };55. 跳跃游戏 class S…...

vue3如何实现防抖?
第一 防抖就是我们设置一个调用时间,点击后设置时间开始倒计时,如果再次点击会重新倒计时 npm或yarn安装: npm install lodash <template><div click"debouncedInputHandler"><button>打印</button>…...

西安电子科技大学初/复试笔试、面试、机试成绩占比
西安电子科技大学初/复试笔试、面试、机试成绩占比 01通信工程学院 02电子工程学院 03计算机科学与技术学院 04机电工程学院 06经济与管理学院 07数学与统计学院 08人文学院 09外国语学院 12生命科学与技术学院 13空间科学与技术学院 14先进材料与纳米科技学院 15网络与信息安…...

spring mvc源码学习笔记之六
pom.xml 内容如下 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/P…...

树莓派4b如何连接ov7670摄像头
在树莓派4B上连接和使用OV7670摄像头是一项具有一定技术挑战的任务。这是因为OV7670摄像头是一个原始的CMOS摄像头模块,它通过并行接口与主机通信,而树莓派的GPIO接口通常用于串行接口(如I2C、SPI、UART)通信,不直接支持并行摄像头接口。因此,需要一些额外的硬件和软件工…...

[微服务]分布式搜索Java客户端
快速入门 使用RestClient客户端进行数据搜索可以分为两步 构建并发起请求 代码解读: 第一步,创建SearchRequest对象,指定索引库名第二步,利用request.source()构建DSL,DSL中可以包含查询、分页、排序、高亮等 query…...

如何使用 `uiautomator2` 控制 Android 设备并模拟应用操作_VIVO手机
在 Android 自动化测试中,uiautomator2 是一个非常强大的工具,能够帮助我们通过 Python 控制 Android 设备执行各种操作。今天,我将通过一个简单的示例,介绍如何使用 uiautomator2 控制 Android 设备,执行特定的应用启动、广告跳过以及其他 UI 操作。此示例的目标是自动化…...

在Ubuntu 18.04.6 LTS安装OpenFace流程
一、修改配置:将gcc8,g8作为默认选项 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 100 sudo update-alternatives --config gcc 选择版本,再查看gcc --version sudo update-alternatives --install /usr/bin/g g /usr/bin/g-…...

C 语言的整型提升问题
目录 引言 一、什么是整型提升 二、为什么会有整型提升 三、整型提升的规则 四、整型提升的影响 五、如何避免整型提升带来的问题 六、总结 引言 在 C 语言中,整型提升(Integer Promotion)是一个常常被忽视但却非常重要的概念。理解整…...

第0章 机器人及自动驾驶SLAM定位方法全解析及入门进阶学习建议
嗨,各位同学大家好!笔者自985硕士毕业后,在机器人算法领域已经深耕 7 年多啦。这段时间里,我积累了不少宝贵经验。本专栏《机器人工程师带你从零入门SLAM》将结合下面的SLAM知识体系思维导图及多年的工作实战总结,将逐…...

video.js视频播放上手
html案例 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>videojs视频播放</title> </head> <link href"https://cdnjs.cloudflare.com/ajax/libs/video.js/7.3.0/video-js.min.cs…...

【LLM-Agent】Building effective agents和典型workflows
note Anthropic的工程经验: 大道至简,尽量维护系统的简洁;尽量让过程更加透明(因为你依赖的是LLM的决策,如果只看输出不看过程,很容易陷入难以debug的情况);对LLM需要调用的工具&am…...

《量子比特大阅兵:不同类型量子比特在人工智能领域的优劣势剖析》
在科技的前沿,量子比特与人工智能的融合正开启一扇全新的大门。不同类型的量子比特,如超导、离子阱、光量子等,在与人工智能结合时展现出独特的优势与劣势。 超导量子比特 超导量子比特是目前应用较为广泛且研究相对成熟的量子比特类型。它…...

《探秘开源大模型:AI 世界的“超级引擎”》
《探秘开源大模型:AI 世界的“超级引擎”》 一、开源大模型崛起之路二、开源大模型发展历程回顾(一)早期奠基:理论突破与初步实践(二)快速发展:百花齐放的模型格局(三)当下态势:走向成熟与多元融合三、开源大模型核心技术剖析(一)Transformer 架构:基石之稳(二)…...

el-table行列转换简单版,仅限单行数据
原始数据格式如下,如果不是此格式,请转换成以下格式在进行以下操作 [{ label: name, value: Tom },{ label: age, value: 25 },{ label: country, value: UK } ]代码如下 <template><el-table :data"tableData" style"width: …...

2025年1月4日蜻蜓q旗舰版st完整开源·包含前后端所有源文件·开源可商用可二开·优雅草科技·优雅草kir|优雅草星星|优雅草银满|优雅草undefined
2025年1月4日蜻蜓q旗舰版st完整开源包含前后端所有源文件开源可商用可二开优雅草科技优雅草kir|优雅草星星|优雅草银满|优雅草undefined 产品介绍: 本产品主要贡献者优雅草科技优雅草kir|优雅草星星|优雅草银满|优雅草undefined-青史留名,时光如川浪淘…...

SQL把字符串按逗号分割成记录
在 SQL 中,可以通过以下方法将字符串按逗号分割,并将每个分割的值作为单独的记录插入到结果集中。以下是针对不同数据库系统的实现方法: 1. 使用 STRING_SPLIT(SQL Server 2016) STRING_SPLIT 是 SQL Server 提供的内置…...

C#设计模式(行为型模式):观察者模式
C#设计模式:观察者模式,让对象间通信更优雅 在软件开发中,我们经常会遇到一个对象的状态发生改变,其他对象需要自动更新或做出相应反应的场景。例如: GUI事件处理: 当用户点击按钮时,按钮需要…...

pytorch镜像源
我以为的 pip install torch2.3.1cu118 torchvision0.18.1cu118 torchaudio2.3.1cu118 -f https://download.pytorch.org/whl/torch_stable.html实际上,有很多加速方案 为提高下载速度可以使用国内的镜像源来安装与 CUDA 11.8 兼容的 PyTorch。 方法 1:…...

Verilog语法之常用行为级语法
摘要:本文主要介绍了一些在verilog中的行为级语法,并且提供了大量的运行实际例子,可以通过这些例子感受行为级语法在仿真中的巨大作用。 概述:行为级语法是RTL级的上一层,或者说是比RTL级更高级的语法,其语…...

PADS Logic原理图中有很多页原理图,如何(怎样)删除其中一页或者多页
我们在进行PADS Logic进行原理图设计的时候,有时候可能遇到一次性设计了很多页的原理图,比如说十几页的原理图。那么我们在进行PADS Layout的时候,可能将这些原理图绘制两块板或者多块PCB板,那么这时候我们需要将其中的一张原理图…...

蓝色简洁引导页网站源码
一款蓝色的简洁引导页,适合资源分发和网站备用引导。 1.源码上传至虚拟机或者服务器 2.绑定域名和目录 3.访问域名安装 4.安装完成后就行了 https://pan.quark.cn/s/b2d8b9c5dc7f https://pan.baidu.com/s/17h1bssUNhhR9DMyNTc-i9Q?pwd84sf https://caiyun.139.com…...

Apache PDFBox添加maven依赖,pdf转成图片
要使用Apache PDFBox将PDF文件转换为图片,并将其添加到Maven项目中,您可以按照以下步骤操作: 1. 添加Maven依赖 在您的pom.xml文件中添加Apache PDFBox的依赖。请确保使用最新版本的PDFBox库。截至2025年,以下是推荐的配置&…...