【iOS】—— iOS中的相关锁
文章目录
- 自旋锁
- 1.OSSpinLock
- 2.os_unfair_lock
- 3.atomic
- 互斥锁
- pthread_mutex
- @synchronized
- objc_sync_enter
- objc_sync_exit
- 注意事项
- NSLock
- NSRecursiveLock
- 信号量
- 条件锁
- NSCondition
- NSConditionLock
- 读写锁
- 总结
锁作为一种非强制的机制,被用来保证线程安全。每一个线程在访问数据或者资源前,要先获取(Acquire)锁,并在访问结束之后释放(Release)锁。如果锁已经被占用,其它试图获取锁的线程会等待,直到锁重新可用
注:不要将过多的其他操作代码放到锁里面,否则一个线程执行的时候另一个线程就一直在等待,就无法发挥多线程的作用了
iOS中锁的基本种类只有三种:互斥锁、自旋锁、读写锁,其他的可能比如:条件锁、递归锁、信号量都是上层的封装和实现
在之前学习中了解过一些锁的知识,很多地方也用到过,在上周看AFNetworking源码的时候也看到了NSLock
和@synchronized
这两种锁用了好多次。
自旋锁
我们在weak的实现原理中有学习过自旋锁,对于每个SideTable中间都有自旋锁,同时也使用了分离锁给单个SideTable上锁。
自旋锁:线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直到显式释放自旋锁。
1.OSSpinLock
自从OSSpinLock
出现了安全问题之后就废弃了。自旋锁之所以不安全,是因为自旋锁由于获取锁时,线程会一直处于忙则等待的死锁状态,造成了任务优先级的反转
OSSpinLock
忙等的机制就可能造成高优先级一直running等待
,占用CPU时间片,而低优先级任务无法抢占时间片,变成迟迟完不成,不释放锁的情况。
// 初始化
spinLock = OS_SPINKLOCK_INIT;
// 加锁
OSSpinLockLock(&spinLock);
// 解锁
OSSpinLockUnlock(&spinLock);
2.os_unfair_lock
weak实现部分的自旋锁就使用的是这个,自旋锁已经不安全了,苹果推出了os_unfair_lock
,这个锁解决了优先级反转的问题
//创建一个锁os_unfair_lock_t unfairLock;
//初始化unfairLock = &(OS_UNFAIR_LOCK_INIT);//加锁os_unfair_lock_lock(unfairLock);//解锁os_unfair_lock_unlock(unfairLock);
3.atomic
自旋锁的实际应用,自动生成的setter
方法会根据修饰符不同调用不同方法,最后统一调用reallySetProperty
方法,其中就有一段关于atomic
修饰词的代码。
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{if (offset == 0) {object_setClass(self, newValue);return;}id oldValue;id *slot = (id*) ((char*)self + offset);if (copy) {newValue = [newValue copyWithZone:nil];} else if (mutableCopy) {newValue = [newValue mutableCopyWithZone:nil];} else {if (*slot == newValue) return;newValue = objc_retain(newValue);}if (!atomic) {oldValue = *slot;*slot = newValue;} else {spinlock_t& slotlock = PropertyLocks[slot];slotlock.lock();oldValue = *slot;*slot = newValue; slotlock.unlock();}objc_release(oldValue);
}
比对一下atomic的逻辑分支:
- 原子性修饰的属性进行了
spinlock
加锁处理 - 非原子性的属性除了没加锁,其他逻辑与
atomic
一般无二
前面提到了os_unfair_lock
替代了OSSpinLock
,所以在上面还用到了OSSpinLock。
using spinlock_t = mutex_tt<LOCKDEBUG>;
class mutex_tt : nocopy_t {os_unfair_lock mLock;...
}
getter
方法也是如此:atomic
修饰的属性进行加锁处理。
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {if (offset == 0) {return object_getClass(self);}// Retain release worldid *slot = (id*) ((char*)self + offset);if (!atomic) return *slot;// Atomic retain release worldspinlock_t& slotlock = PropertyLocks[slot];slotlock.lock();id value = objc_retain(*slot);slotlock.unlock();// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.return objc_autoreleaseReturnValue(value);
}
atomic只能保证setter、getter方法的线程安全,并不能保证数据安全。
如果有多个线程同时调用setter的话,不会出现某一个线程执行setter全部语句之前,另一个线程开始执行setter情况,相当于函数头尾加了锁一样。 nonatomic不保证setter/getter的原语行,所以你可能会取到不完整的东西。 比如setter函数里面改变两个成员变量,如果你用nonatomic的话,getter可能会取到只更改了其中一个变量时候的状态。 atomic是线程安全的,nonatomic是线程不安全的。如果只是单线程操作的话用nonatomic最好,因为后者效率高一些。
互斥锁
进行互斥操作的锁,防止两条线程同时对同一公共资源(比如全局变量)进行读写操作。
- 互斥锁:如果共享数据已经有其他线程加锁了,线程会进入休眠状态等待锁。一旦被访问的资源被解锁,则等待资源的线程会被唤醒。
- 自旋锁:如果共享数据已经有其他线程加锁了,线程会以死循环的方式等待,一旦被访问的资源被解锁,则等待资源的线程会立即执行
- 自旋锁的效率高于互斥锁。但是我们要注意由于自旋时不释放CPU,因而持有自旋锁的线程应尽快释放自旋锁,否则等待该自旋锁的线程会一直在那里自旋,浪费CPU时间。
互斥锁又分为:
- 递归锁:可重入锁,同一个线程在锁匙放钱可再次获取锁,即可以递归调用
- 非递归锁:不可重入,必须等锁释放后才能再次获取锁
对于递归锁我们要注意使用时死锁问题,前后代码相互等待就会死锁
对于非递归锁,我们强行使用递归就会造成堵塞而非死锁。
pthread_mutex
pthread_mutex就是互斥锁本身——当锁被占用,而其他线程申请锁时,不是使用忙等,而是阻塞线程并睡眠
// 导入头文件
#import <pthread.h>
// 全局声明互斥锁
pthread_mutex_t _lock;
// 初始化互斥锁
pthread_mutex_init(&_lock, NULL);
// 加锁
pthread_mutex_lock(&_lock);
// 这里做需要线程安全操作
// ...
// 解锁
pthread_mutex_unlock(&_lock);
// 释放锁
pthread_mutex_destroy(&_lock);
@synchronized
@synchronized可能是日常开发中用的比较多的一种互斥锁,因为它的使用比较简单,但并不是在任意场景下都能使用@synchronized,且它的性能较低。
@synchronized需要一个参数,这个参数相当于信号量
// 初始化
@synchronized(锁对象){}
/*
底层封装的pthread_mutex的PTHREAD_MUTEX_RECURSIVE 模式,
锁对象来表示是否为同一把锁
*/
我们来浅看一下它的底层实现:
static void _I_MyPerson_run(MyPerson * self, SEL _cmd) {{ id _rethrow = 0; id _sync_obj = (id)self; objc_sync_enter(_sync_obj);try {struct _SYNC_EXIT { _SYNC_EXIT(id arg) : sync_exit(arg) {}~_SYNC_EXIT() {objc_sync_exit(sync_exit);}id sync_exit;} _sync_exit(_sync_obj);NSLog((NSString *)&__NSConstantStringImpl__var_folders_rx_h53wjns9787gpxxz8tg94y6r0000gn_T_MyPerson_9b8773_mi_3);} catch (id e) {_rethrow = e;}{ struct _FIN { _FIN(id reth) : rethrow(reth) {}~_FIN() { if (rethrow) objc_exception_throw(rethrow); }id rethrow;} _fin_force_rethow(_rethrow);}}
}
synchronized
调用了try catch
,内部调用了objc_sync_enter
和objc_sync_exit
。
objc_sync_enter
// Begin synchronizing on 'obj'.
// Allocates recursive mutex associated with 'obj' if needed.
// Returns OBJC_SYNC_SUCCESS once lock is acquired.
//开始同步'obj'。
//如果需要,分配与'obj'关联的递归互斥。
//获取锁后返回OBJC_SYNC_SUCCESS。
int objc_sync_enter(id obj)
{int result = OBJC_SYNC_SUCCESS;if (obj) {SyncData* data = id2data(obj, ACQUIRE);ASSERT(data);data->mutex.lock();} else {// @synchronized(nil) does nothingif (DebugNilSync) {_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");}objc_sync_nil();}return result;
}
BREAKPOINT_FUNCTION(void objc_sync_nil(void)
);
- 首先从它的注释中
recursive mutex
可以得出@synchronized
是递归锁 - 如果加锁的对象
obj
不存在时分别会走objc_sync_nil()
和不做任何操作。这也是@synchronized
作为递归锁但能防止死锁的原因所在:在不断递归的过程中如果对象不存在了就会停止递归从而防止死锁。 - 正常情况下(obj存在)会通过id2data方法生成一个SyncData对象
typedef struct alignas(CacheLineSize) SyncData {struct SyncData* nextData;DisguisedPtr<objc_object> object;int32_t threadCount; // number of THREADS using this blockrecursive_mutex_t mutex;
} SyncData;
-
- nextData指的是链表中下一个SyncData
-
- object指的是当前加锁的对象
-
- threadCount表示使用该对象进行加锁的线程数
-
- mutex即对象所关联的锁
objc_sync_exit
// End synchronizing on 'obj'.
// Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
int objc_sync_exit(id obj)
{int result = OBJC_SYNC_SUCCESS;if (obj) {SyncData* data = id2data(obj, RELEASE); if (!data) {result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;} else {bool okay = data->mutex.tryUnlock();if (!okay) {result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;}}} else {// @synchronized(nil) does nothing}return result;
}
注意事项
- 不能使用非OC对象作为加锁条件——id2data中接收参数为id类型
- 多次锁同一个对象会有什么后果吗——会从高速缓存中拿到data,所以只会锁一次对象
- 都说@synchronized性能低——是因为在底层增删改查消耗了大量性能
- 加锁对象不能为nil,否则加锁无效,不能保证线程安全
NSLock
NSLock是非递归锁;NSLock是对互斥锁的简单封装.
NSLock在AFNetworking的AFURLSessionManager中有使用到
如果对非递归锁强行使用递归调用,就会在调用时发生堵塞,并非死锁,第一次加锁之后还没出锁就进行递归调用,第二次加锁就堵塞了线程。(因为不会查询缓存)
- (void)test {self.testArray = [NSMutableArray array];NSLock *lock = [[NSLock alloc] init];for (int i = 0; i < 200000; i++) {dispatch_async(dispatch_get_global_queue(0, 0), ^{[lock lock];self.testArray = [NSMutableArray array];[lock unlock];});}
}
从官方文档的解释里看的更清楚,在同一线程上调用NSLock的两次lock方法将永久锁定线程。同时官方文档重点提醒向NSLock对象发送解锁消息时,必须确保该消息是从发送初始锁定消息的同一线程发送的。
NSRecursiveLock
NSRecursiveLock是递归锁
- (void)test {NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];dispatch_async(dispatch_get_global_queue(0, 0), ^{static void (^block)(int);block = ^(int value) {[lock lock];if (value > 0) {NSLog(@"value——%d", value);block(value - 1);}[lock unlock];};block(10);});
}
如果我们在外层添加for循环
- (void)test {NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];for (int i = 0; i < 10; i++) {dispatch_async(dispatch_get_global_queue(0, 0), ^{static void (^block)(int);block = ^(int value) {[lock lock];if (value > 0) {NSLog(@"value——%d", value);block(value - 1);}[lock unlock];};block(10);});}
}
程序就崩了。
因为for循环在block内部对同一个对象进行了多次锁操作,直到这个资源身上挂着N把锁,最后大家都无法一次性解锁,也就是找不到解锁的出口。
即线程1中加锁1、同时线程2中加锁2-> 解锁1等待解锁2 -> 解锁2等待解锁1 -> 无法结束解锁——形成死锁
此时我们可以通过@synchronized对对象进行锁操作,会先从缓存查找是否有锁syncData存在。如果有,直接返回而不加锁,保证锁的唯一性。
同一线程可以多次获取而不会导致死锁的锁。
信号量
信号量(semaphore):是一种更高级的同步机制,互斥锁可以说是 semaphore 在仅取值 0/1 时的特例。信号量可以有更多的取值空间,用来实现更加复杂的同步,而不单单是线程间互斥。
// 初始化
dispatch_semaphore_t semaphore_t = dispatch_semaphore_create(1);
// 加锁
dispatch_semaphore_wait(semaphore_t,DISPATCH_TIME_FOREVER);
// 解锁
dispatch_semaphore_signal(semaphore_t);
/*
注: dispatch_semaphore 其他两个功能
1.还可以起到阻塞线程的作用.
2.可以实现定时器功能,这里不做过多介绍.
*/
条件锁
就是条件变量,当进程的某些资源要求不满足时就进入休眠,也就是锁住了。当资源被分配到了,条件锁打开,进程继续运行。
在一定条件下,让其等待休眠,并放开锁,等接收到信号或者广播,会从新唤起线程,并重新加锁,像NSCondition
封装了pthread_mutex
的以上几个函数,NSConditionLock
封装了NSCondition
。
NSCondition
NSCondition
是一个条件锁,可能平时用的不多,但与信号量相似:线程1需要等到条件1满足才会往下走,否则就会堵塞等待,直至条件满足。
// 初始化
NSCondition *_condition= [[NSCondition alloc]init];
// 加锁
[_condition lock];
// 解锁
[_condition unlock];
/*
其他功能接口
wait 进入等待状态
waitUntilDate:让一个线程等待一定的时间
signal 唤醒一个等待的线程
broadcast 唤醒所有等待的线程
*/
我们可以看出:
- NSCondition是对mutex和cond的一种封装(cond就是用于访问和操作特定类型数据的指针)
- wait操作会阻塞线程,使其进入休眠状态,直至超时
- signal操作是唤醒一个正在休眠等待的线程
- broadcast会唤醒所有正在等待的线程
NSConditionLock
// 初始化
NSConditionLock *_conditionLock = [[NSConditionLock alloc]init];
// 加锁
[_conditionLock lock];
// 解锁
[_conditionLock unlock];
// 尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO
[_conditionLock tryLock];
/*
其他功能接口
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER; //初始化传入条件
- (void)lockWhenCondition:(NSInteger)condition;//条件成立触发锁
- (BOOL)tryLockWhenCondition:(NSInteger)condition;//尝试条件成立触发锁
- (void)unlockWithCondition:(NSInteger)condition;//条件成立解锁
- (BOOL)lockBeforeDate:(NSDate *)limit;//触发锁 在等待时间之内
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;//触发锁 条件成立 并且在等待时间之内
*/
- NSConditionLock是NSCondition加线程数的封装
- NSConditionLock可以设置锁条件,而NSCondition只是通知信号
读写锁
读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的CPU数
写者是排他性的,⼀个读写锁同时只能有⼀个写者或多个读者(与CPU数相关),但不能同时既有读者⼜有写者。在读写锁保持期间也是抢占失效的
如果读写锁当前没有读者,也没有写者,那么写者可以⽴刻获得读写锁,否则它必须⾃旋在那⾥,直到没有任何写者或读者。如果读写锁没有写者,那么读者可以⽴即获得该读写锁,否则读者必须⾃旋在那⾥,直到写者释放该读写锁。
// 导入头文件
#import <pthread.h>
//普通初始化
// 全局声明读写锁
pthread_rwlock_t lock;
// 初始化读写锁
pthread_rwlock_init(&lock, NULL);
//宏定义初始化
pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER;// 读操作-加锁
pthread_rwlock_rdlock(&lock);
// 读操作-尝试加锁
pthread_rwlock_tryrdlock(&lock);
// 写操作-加锁
pthread_rwlock_wrlock(&lock);
// 写操作-尝试加锁
pthread_rwlock_trywrlock(&lock);
// 解锁
pthread_rwlock_unlock(&lock);
// 释放锁
pthread_rwlock_destroy(&lock);
总结
- OSSpinLock不再安全,底层用os_unfair_lock替代
- atomic只能保证setter、getter时线程安全,所以更多的使用nonatomic来修饰
- 读写锁更多使用栅栏函数来实现
- @synchronized在底层维护了一个哈希链表进行data的存储,使用recursive_mutex_t进行加锁
- NSLock、NSRecursiveLock、NSCondition和NSConditionLock底层都是对pthread_mutex的封装
- NSCondition和NSConditionLock是条件锁,当满足某一个条件时才能进行操作,和信号量dispatch_semaphore类似
- 普通场景下涉及到线程安全,可以用NSLock
- 循环调用时用NSRecursiveLock
- 循环调用且有线程影响时,请注意死锁,如果有死锁问题请使用@synchronized
相关文章:

【iOS】—— iOS中的相关锁
文章目录 自旋锁1.OSSpinLock2.os_unfair_lock3.atomic 互斥锁pthread_mutexsynchronizedobjc_sync_enterobjc_sync_exit注意事项 NSLockNSRecursiveLock信号量条件锁NSConditionNSConditionLock 读写锁总结 锁作为一种非强制的机制,被用来保证线程安全。每一个线程…...

表单重复提交:
1. 表单重复提交原因 当用户提交完请求,浏览器会记录最后一次请求的全部信息。用户按下功能键F5,就会发起浏览器记录的最后一次请求。如果最后一次请求为添加操作,那么此时刷新按钮就会再次提交数据,造成表单重复提交。 2. 表单…...
【0197】共享内存管理结构(shmem)之创建共享内存分配机制(Shared Memory Allocation)(2 - 2)
文章目录 1. 概述2. 初始化事务管理器 ShmemVariableCache2.1 从共享内存分配 VariableCacheData 大小内存空间2.1.1 分配对齐块2.2 内存空间清零相关文章: 【0195】共享内存管理结构(shmem)之概念篇(1) 【0196】共享内存管理结构(shmem)之创建共享内存分配机制(Shared…...

ChatGPT国内免费使用方法有哪些?
目录 ChatGPT介绍:一、ChatGPT是什么?二、ChatGPT发展:三、ChatGPT 优点:四、国内使用ChatGPT方法五、结语: ChatGPT介绍: 一、ChatGPT是什么? ChatGPT 是一个基于语言模型 GPT-3.5 的聊天机器人,ChatGPT模型是Instruct GPT的姊妹模型(siblingmodel&a…...

【CloudCompare教程】012:基于点云数据的测量功能
本文讲解CloudCompare基于点云数据的测量功能,主要有:点云索引、坐标、距离、角度、面积、标签等。 文章目录 一、加载地形点云数据二、基于点云数据的测量功能1. 选择单点并显示信息2. 选择两点并显示分割信息3. 选择三点并显示相关三角形信息4. 定义矩形2D标签5. 保存当前标…...

一体化医学影像平台PACS源码,影像存档与传输系统源码
PACS影像存档与传输系统源码 PACS即影像存档与传输系统,是医学影像、数字化图像技术、计算机技术和网络通讯技术相结合的产物,是处理各种医学影像信息的采集、存储、报告、输出、管理、查询的计算机应用程序。 是基于DICOM标准的医学影像管理系统&…...

一篇文章打好SQL基础,熟悉数据库的基础操作和方法,以及安装MySQL软件包和Python操作MySQL基础使用
1.SQL的概述 SQL的全称:Structured Query Language,结构化查询语言,用于访问和处理数据库的标准计算机语言。 SQL语言1974年有Boyce和Chamberlin提出的,并且首先在IBM公司研制的关系数据库系统SystemR上实现。 经过多年发展&am…...

C4D R26 渲染学习笔记 建模篇(3):生成器
文章目录 前文回顾介绍篇建模篇 生成器介绍生成器变形器搭配举例 生成器详细介绍细分曲面布料曲面 未完待续 前文回顾 介绍篇 C4D R26 渲染学习笔记(1):C4D版本选择和初始UI框介绍 C4D R26 渲染学习笔记(2)ÿ…...

智慧梁场3D建模
智慧梁场3D建模:数字化革命下的新起点 随着科技的迅猛发展,数字化已经成为了现代工业生产的必然趋势。作为传统工业的核心产业,建筑行业也在不断地探索数字化变革的新路径。而“智慧梁场3D建模”便是其中的一项杰出实践。 梁场是建筑…...

《程序员面试金典(第6版)》面试题 02.08. 环路检测(哈希法,双指针,检测链表是否有环)
题目描述 给定一个链表,如果它是有环链表,实现一个算法返回环路的开头节点。若环不存在,请返回 null。 题目传送门:面试题 02.08. 环路检测 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链…...

软考A计划-试题模拟含答案解析-卷六
点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 👉关于作者 专注于Android/Unity和各种游戏开发技巧,以及各种资源分享&am…...

Linux 上的 .NET 崩溃了怎么抓 Dump
一:背景 1. 讲故事 训练营中有朋友问在 Linux 上如何抓 crash dump,在我的系列文章中演示的大多是在 Windows 平台上,这也没办法要跟着市场走,谁让 .NET 的主战场在工控 和 医疗 呢,上一张在 合肥 分享时的一个统计图…...

QT桌面项目(状态栏和导航栏设置)
文章目录 前言一、状态栏二、导航栏三、同时添加状态栏和导航栏总结 前言 为了和我们这个项目做的更加真实,这里为我们的项目添加上状态栏和导航栏让他变成更加接近手机的桌面效果。 一、状态栏 这个状态栏就是显示时间和wifi状态,电池电量的…...

数据链路层:点对点协议PPP
数据链路层:点对点协议PPP 笔记来源: 湖科大教书匠:点对点协议PPP 声明:该学习笔记来自湖科大教书匠,笔记仅做学习参考 数据链路层只负责直接相连的两个结点之间的通信 PPP是点对点数据链路层协议 用户通过ISP接入因特…...
C/C++读取txt文件中的float数据并用指针存储
C语言中读取txt文件中的数据 以下是一个简单的示例代码,演示如何在C语言中读取txt文件中的数据: #include <stdio.h>int main() {FILE *fp;char buffer[100];// 打开文件fp fopen("example.txt", "r");// 如果文件打开失败…...

对KMP算法的一点碎碎念——上篇
对KMP算法的一点碎碎念——上篇 文章目录 对KMP算法的一点碎碎念——上篇1. KMP 算法 Next数组 求解问题1.1 前置知识-最长公共前后缀LCP1.1.1 前缀与后缀1.1.2 最长公共前后缀LCP 1.2 手算法求解 Next数组值(3种常见情况)1.2.1 情况1: next数组 正常存放匹配字符的长度情况1的…...
算法---边界着色
题目 给你一个大小为 m x n 的整数矩阵 grid ,表示一个网格。另给你三个整数 row、col 和 color 。网格中的每个值表示该位置处的网格块的颜色。 两个网格块属于同一 连通分量 需满足下述全部条件: 两个网格块颜色相同 在上、下、左、右任意一个方向上…...
二叉排序树的查找、插入、删除
目录 二叉排序树的定义 二叉排序树的查找 二叉排序树的插入 二叉排序树的定义 二叉排序树的定义 二叉排序树(Binary Sort Tree, BST),也称二叉查找树。 二叉排序树或者是一棵空树,或者是一棵具有下列特性的非空二叉…...

《Opencv3编程入门》学习笔记—第三章
《Opencv3编程入门》学习笔记 记录一下在学习《Opencv3编程入门》这本书时遇到的问题或重要的知识点。 第三章 HighGUI图形用户界面初步 一、图像的载入、显示和输出到文件 (一)OpenCV的命名空间 简单的OpenCV程序标配: #include <o…...

如何从Ubuntu Linux中删除Firefox Snap?
Ubuntu Linux是一款广受欢迎的开源操作系统,拥有强大的功能和广泛的应用程序选择。默认情况下,Ubuntu提供了一种称为Snap的软件打包格式,用于安装和管理应用程序。Firefox是一款流行的开源网络浏览器,而Firefox Snap是Firefox的Sn…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...

19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...

Matlab实现任意伪彩色图像可视化显示
Matlab实现任意伪彩色图像可视化显示 1、灰度原始图像2、RGB彩色原始图像 在科研研究中,如何展示好看的实验结果图像非常重要!!! 1、灰度原始图像 灰度图像每个像素点只有一个数值,代表该点的亮度(或…...

WebRTC调研
WebRTC是什么,为什么,如何使用 WebRTC有什么优势 WebRTC Architecture Amazon KVS WebRTC 其它厂商WebRTC 海康门禁WebRTC 海康门禁其他界面整理 威视通WebRTC 局域网 Google浏览器 Microsoft Edge 公网 RTSP RTMP NVR ONVIF SIP SRT WebRTC协…...
用鸿蒙HarmonyOS5实现国际象棋小游戏的过程
下面是一个基于鸿蒙OS (HarmonyOS) 的国际象棋小游戏的完整实现代码,使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├── …...