【iOS】锁

线程安全

当一个线程访问数据的时候,其他的线程不能对其进行访问,直到该线程访问完毕。简单来讲就是在同一时刻,对同一个数据操作的线程只有一个。而线程不安全,则是在同一时刻可以有多个线程对该数据进行访问,从而得不到预期的结果。 在iOS中, UIKit是绝对线程安全的,因为UIKit都是在主线程操作的,单线程没有线程当然没有线程安全问题,但除此之外,其他都要考虑线程安全问题

iOS解决线程安全的途径其原理大同小异,都是通过锁来使关键代码保证同步执行,从而确保线程安全性,这一点和多线程的异步执行任务是不冲突的。

注: 不要将过多的其他操作代码放到锁里面,否则一个线程执行的时候另一个线程就一直在等待,就无法发挥多线程的作用了

下方我们就详细讲解iOS相关锁,本博客采用一个经典的售票例子:

此处展示的是不加锁(即不考虑线程安全)的情况:

// .h 中
#import <Cocoa/Cocoa.h>@interface ViewController : NSViewController
@property (nonatomic, assign) NSInteger ticketSurplusCount;@end// .m中
// 记录共出售多少票的全局变量
int cnt = 0;- (void)startSell {// 一共有50张票self.ticketSurplusCount = 50;__weak typeof (self) weakSelf = self;// 一号售卖口dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{[weakSelf saleTick];});// 二号售卖口dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{[weakSelf saleTick];});
}- (void)saleTick {while(1) {if (self.ticketSurplusCount > 0) {self.ticketSurplusCount--;cnt++;NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);[NSThread sleepForTimeInterval:0.2];} else {NSLog(@"所有火车票均已售完,共售出%d张票", cnt);break;}}
}

运行结果:
在这里插入图片描述

我们看到运行结果显示整个卖票的过程是错乱的,居然一共卖出了51张票,接下来我们就在讲解锁的过程中对卖票操作加锁,来修正现在的错乱结果。

锁的种类

iOS中的锁有两大类:自旋锁、互斥锁

自旋锁

与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环尝试,直到该自旋锁的保持者已经释放了锁(忙等待)。因为不会引起调用者睡眠,所以效率高于互斥锁。

自旋锁原理

线程一直是running(加锁——>解锁),死循环检测锁的标志位,机制不复杂。

自旋锁缺点

  • 调用者在未获得锁的情况下,一直运行--自旋,所以占用着CPU资源,如果不能在很短的时间内获得锁,会使CPU效率降低。所以自旋锁就主要用在临界区持锁时间非常短且CPU资源不紧张的情况下。
  • 在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁。

OSSpinLock(自旋锁)

OSSpinLock是在libkern库中,使用之前需要引入头文件<libkern/OSAtomic.h>,使用时会出现警告⚠️。
在这里插入图片描述

这是因为OSSpinLock存在缺陷,从iOS10开始已经不建议使用了。官方建议使用os_unfair_lock来替代。

// 初始化
spinLock = OS_SPINKLOCK_INIT;
// 加锁
OSSpinLockLock(&spinLock);
// 解锁
OSSpinLockUnlock(&spinLock);

实际使用(在卖票例子中):

- (void)saleTick {while(1) {// 加锁OSSpinLockLock(&_spinLock);if (self.ticketSurplusCount > 0) {self.ticketSurplusCount--;cnt++;NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);[NSThread sleepForTimeInterval:0.2];} else {NSLog(@"所有火车票均已售完,共售出%d张票", cnt);// 解锁OSSpinLockUnlock(&_spinLock);break;}// 解锁OSSpinLockUnlock(&_spinLock);}
}

运行结果:
在这里插入图片描述

结果就是按照顺序非常规范地卖出了这50张票

刚才提到了OSSpinLock存在缺陷,其实它的缺陷主要存在两点:

  • OSSpinLock不会记录持有它的线程信息,当发生优先级反转的时候,系统找不到低优先级的线程,导致系统可能无法通过提高优先级解决优先级反转问题
  • 高优先级线程使用自旋锁忙等待的时候一直在占用CPU时间片,导致低优先级线程拿到时间片的概率降低。

值得注意的是: 自旋锁和优先级反转没有关系,但是正因为有上面两点,所以自旋锁会导致优先级反转问题更难解决,甚至造成更为严重的线程等待问题,所以苹果就废除了OSSpinLock,转而推荐人们使用os_unfair_lock来替代,由于os_unfair_lock是一个互斥锁,所以我们将对其的讲解放到互斥锁中去。

互斥锁

保证在任何时候,都只有一个线程访问对象。当获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒。

互斥锁原理

线程会从sleep(加锁)——> running(解锁),过程中有上下文的切换,cpu的抢占,信号的发送等开销,所以效率是要低于自旋锁的。

互斥锁分为两种: 递归锁、非递归锁

  • 递归锁:可重入锁,同一个线程在锁释放前可再次获取锁,即可以递归调用。
  • 非递归锁:不可重入,必须等锁释放后才能再次获取锁。

os_unfair_lock

上面讲过现在苹果采用os_unfair_lock来代替不安全的OSSpinLock,且由于os_unfair_lock会休眠而不是忙等,所以属于 互斥锁 ,且是非递归互斥锁,下面来看一下它的用法:

os_unfair_lockos库中,使用之前需要导入头文件<os/lock.h>

//创建一个锁
os_unfair_lock unfairLock;
//初始化
unfairLock = OS_UNFAIR_LOCK_INIT;
//加锁
os_unfair_lock_lock(&unfairLock);
//解锁
os_unfair_lock_unlock(&unfairLock);

实际使用(在卖票例子中):

- (void)saleTickWithOsUnfairLock {while(1) {// 加锁os_unfair_lock_lock(&unfairLock);if (self.ticketSurplusCount > 0) {self.ticketSurplusCount--;cnt++;NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);[NSThread sleepForTimeInterval:0.2];} else {NSLog(@"所有火车票均已售完,共售出%d张票", cnt);// 解锁os_unfair_lock_unlock(&unfairLock);break;}// 解锁os_unfair_lock_unlock(&unfairLock);}
}

运行结果:
在这里插入图片描述

关于它的定义:
在这里插入图片描述

可以看到这里的解释是,不是旋转(忙等),而是休眠,等待被唤醒,所以os_unfair_lock理应是互斥锁。

pthread_mutex

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);

运行结果如下:
在这里插入图片描述

结果就是按照顺序非常规范地卖出了这50张票。

NSLock

我们的Foundation框架内部也是有一把NSLock锁的,使用起来非常方便,基于互斥锁pthroad_mutex封装而来,是一把互斥非递归锁。
使用如下:

//初始化NSLock
NSLock *lock = [[NSLock alloc] init];
//加锁
[lock lock];
...
//线程安全执行的代码
...
//解锁
[lock unlock];

实际使用(在卖票例子中):

- (void)saleTickWithNSLock {while(1) {// 加锁[lock lock];if (self.ticketSurplusCount > 0) {  // 如果还有票,继续售卖self.ticketSurplusCount--;cnt++;NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);[NSThread sleepForTimeInterval:0.2];} else { // 如果已卖完,关闭售票窗口NSLog(@"所有火车票均已售完,共售出%d张票", cnt);// 解锁[lock unlock];break;}// 解锁[lock unlock];}
}

运行结果如下:

结果就是按照顺序非常规范地卖出了这50张票。

如果对非递归锁强行使用递归调用,就会在调用时发生线程阻塞,而并非是死锁,第一次加锁之后还没出锁就进行递归调用,第二次加锁就堵塞了线程。

苹果官方文档的描述如下:
在这里插入图片描述

可以看到在同一线程上调用两次NSLocklock方法将会永久锁定线程。同时也重点提醒向NSLock对象发生解锁消息时,必须确保消息时从发送初始锁定消息的同一个线程发送的,否则就会产生未知问题。

非递归互斥锁导致线程阻塞的例子:

- (void)saleTickWithNSLock {while(1) {// 加锁[lock lock];if (self.ticketSurplusCount > 0) {  // 如果还有票,继续售卖self.ticketSurplusCount--;cnt++;NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);[NSThread sleepForTimeInterval:0.2];} else { // 如果已卖完,关闭售票窗口NSLog(@"所有火车票均已售完,共售出%d张票", cnt);// 解锁break;}// 解锁}
}

运行结果如下:
在这里插入图片描述

可以看到,因为我们对当前这个线程在执行lock操作后还未unlock的情况下,又进行了NSLock的重复lock加锁操作,所以当前线程发生了阻塞,只进行了一次卖票操作就再不执行其他操作了。

NSRecusiveLock

NSRecursiveLock使用和NSLock类似,不过NSRecursiveLock是递归互斥锁。

//初始化NSLock
NSRecusiveLock *recusiveLock = [[NSRecusiveLock alloc] init];
//加锁
[recusiveLock lock];
...
//线程安全执行的代码
...
//解锁
[recusiveLock unlock];

下面我们举一个NSRecursiveLock递归使用的例子:

@interface ViewController ()
@property (nonatomic, assign) NSInteger ticketSurplusCount;
@property (nonatomic, strong) NSRecursiveLock *recursiveLock;@end//卖票窗口(此处我们循环创建十个窗口,但是都是在同一线程中执行)
- (void)threadBlock {   //一共50张票self.ticketSurplusCount = 50;//初始化NSRecursiveLock递归锁_recursiveLock = [[NSRecursiveLock alloc] init];__weak typeof (self) weakSelf = self;for (int i = 0; i < 10; ++i) {dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{[weakSelf saleTicket];});}
}//卖票的函数
- (void)saleTicket {//加锁[_recursiveLock lock];if (self.ticketSurplusCount > 0) {  // 如果还有票,继续售卖self.ticketSurplusCount--;cnt++;NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);[NSThread sleepForTimeInterval:0.2];//递归调用卖票函数[self saleTicket];} else { // 如果已卖完,关闭售票窗口NSLog(@"所有火车票均已售完,共售出%d张票", cnt);}//解锁[_recursiveLock unlock];
}

运行结果如下:
在这里插入图片描述

可以看到向同一个线程多次获取递归锁NSRecusiveLock并不会导致程序死锁,而是正常的线程安全地加锁执行。

苹果官方文档的描述如下:
在这里插入图片描述

同一线程可以多次获取而不会导致死锁的锁,重点是在同一线程。

举一个不同线程获取锁导致死锁的例子:

- (void) recursiveDeadlocksWithValue:(int)value {[recursiveLock lock];NSLog(@"%d---%@", value, [NSThread currentThread]);dispatch_group_t group = dispatch_group_create();dispatch_group_enter(group);dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{if (value > 0) {[self recursiveDeadlocksWithValue:value - 1];}dispatch_group_leave(group);});dispatch_group_wait(group, DISPATCH_TIME_FOREVER);[recursiveLock unlock];
}

可以看到里面的线程想要获取锁就必须等待外面的线程释放锁,而外面的线程释放锁需要等待里面的线程完成任务,导致互相等待死锁。

在这里插入图片描述

NSCondition

NSCondition是一个条件锁,同时其实也是一个非递归互斥锁,可能平时用的不多,但与GCD信号量相似:线程1需要等到条件1满足才会往下走,否则就会堵塞等待,直至条件满足,一旦获得了锁并执行了代码的关键部分,线程就可以放弃该锁并将关联条件设置为新的条件。条件本身是任意的:可以根据应用程序的需要定义它们。

Objective-C代码并不能看到NSCondition的具体实现,只能看到该类的接口部分,实现部分需要使用swift源码进行查看:

OC接口部分:
@interface NSCondition : NSObject <NSLocking> {
@privatevoid *_priv;
}- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));@end
swift实现部分:
open class NSCondition: NSObject, NSLocking {internal var mutex = _MutexPointer.allocate(capacity: 1)internal var cond = _ConditionVariablePointer.allocate(capacity: 1)public override init() {pthread_mutex_init(mutex, nil)pthread_cond_init(cond, nil)}deinit {pthread_mutex_destroy(mutex)pthread_cond_destroy(cond)mutex.deinitialize(count: 1)cond.deinitialize(count: 1)mutex.deallocate()cond.deallocate()}// 一般用于多线程同时访问、修改同一个数据源,保证在同一 时间内数据源只被访问、修改一次,// 其他线程的命令需要在lock 外等待,只到 unlock ,才可访问open func lock() {pthread_mutex_lock(mutex)}// 释放锁,与lock成对出现open func unlock() {pthread_mutex_unlock(mutex)}// 让当前线程处于等待状态,阻塞open func wait() {pthread_cond_wait(cond, mutex)}// 让当前线程等待到某个时间,阻塞open func wait(until limit: Date) -> Bool {guard var timeout = timeSpecFrom(date: limit) else {return false}return pthread_cond_timedwait(cond, mutex, &timeout) == 0}// 发信号告诉线程可以继续执行,唤醒线程open func signal() {pthread_cond_signal(cond)}//唤醒所有正在等待的线程open func broadcast() {pthread_cond_broadcast(cond) // wait  signal}open var name: String?
}

可以看到,该对象还是对pthread_mutex的一层封装,NSCondition也是一种互斥锁。当我们需要等待某个条件的时候,也就是条件不满足的时候,就可以使用wait方法来阻塞线程,当条件满足了,使用signal方法发送信号唤醒线程。

再浅浅总结一下:

  • NSCondition是对mutexcond的一种封装(cond就是用于访问和操作特定类型数据的指针)。
  • wait操作会阻塞线程,使其进入休眠状态,直至超时。
  • signal操作是唤醒一个正在休眠等待的线程。
  • broadcast会唤醒所有正在等待的线程。

实际使用(在卖票例子中):

//售票的方法
- (void)saleTicketSafeWithConditionLock {while (1) {// 加锁[_condition lock];if (self.ticketSurplusCount > 0) {  // 如果还有票,继续售卖self.ticketSurplusCount--;cnt++;NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);[NSThread sleepForTimeInterval:0.2];} else { // 如果已卖完,关闭售票窗口NSLog(@"所有火车票均已售完,共售出%d张票", cnt);// 解锁[_condition unlock];break;}// 解锁[_condition unlock];}
}

运行结果:
在这里插入图片描述

结果就是按照顺序非常规范地卖出了这50张票。

NSConditionLock

NSConditionLockNSCondition又做了一层封装,自带条件探测,能够更简单灵活的使用,所以它也属于非递归互斥锁。
然后我们来看一看NSConditionLock的相关源码:

NSCondition的源码一样,在OC中看接口,在swift中看实现:

OC的接口部分:
@interface NSConditionLock : NSObject <NSLocking> {
@privatevoid *_priv;
}- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));@end
swift中实现:
internal var _cond = NSCondition()
internal var _value: Int
internal var _thread: _swift_CFThreadRef?public convenience override init() {self.init(condition: 0)
}public init(condition: Int) {_value = condition
}// 表示 xxx 期待获得锁,
// 如果没有其他线程获得锁(不需要判断内部的 condition) 那它能执行此行以下代码,
// 如果已经有其他线程获得锁(可能是条件锁,或者无条件 锁),则等待,直至其他线程解锁
open func lock() {let _ = lock(before: Date.distantFuture)
}open func unlock() {_cond.lock()_thread = nil_cond.broadcast()_cond.unlock()
}open var condition: Int {return _value
}// 表示如果没有其他线程获得该锁,但是该锁内部的 condition不等于A条件,它依然不能获得锁,仍然等待。
// 如果内部的condition等于A条件,并且没有其他线程获得该锁,则执行任务,同时设置它获得该锁
// 其他任何线程都将等待它代码的完成,直至它解锁。
open func lock(whenCondition condition: Int) {let _ = lock(whenCondition: condition, before: Date.distantFuture)
}open func `try`() -> Bool {return lock(before: Date.distantPast)
}open func tryLock(whenCondition condition: Int) -> Bool {return lock(whenCondition: condition, before: Date.distantPast)
}// 表示释放锁,同时把内部的condition设置为A条件
open func unlock(withCondition condition: Int) {_cond.lock()_thread = nil_value = condition_cond.broadcast()_cond.unlock()
}open func lock(before limit: Date) -> Bool {_cond.lock()while _thread != nil {if !_cond.wait(until: limit) {_cond.unlock()return false}}_thread = pthread_self()_cond.unlock()return true
}// 表示如果被锁定(没获得 锁),并超过该时间则不再阻塞线程。
// 需要注意的是:返回的值是NO,它没有改变锁的状态,这个函数的目的在于可以实现两种状态下的处理
open func lock(whenCondition condition: Int, before limit: Date) -> Bool {_cond.lock()while _thread != nil || _value != condition {if !_cond.wait(until: limit) {_cond.unlock()return false}}_thread = pthread_self()_cond.unlock()return true
}open var name: String?

可以看出,触发的唤醒线程的条件是传入的condition取值,和我们创建锁的时候值要相同,我们可以在释放当前线程锁的时候重新设置其他线程传入的condition值,这样也就达到了唤醒其他线程的目的。如果创建锁的值和传入的值都不能匹配,则会进入阻塞状态

也就是说NSConditionLockinit lockunlock中都可以传入value。
例:

- (void)conditionLockTest {for (int i = 0; i < 5; ++i) {//调用测试函数[self test];//修改Condition参数值为3[self.conditionLock lockWhenCondition:0];[self.conditionLock unlockWithCondition:3];}return;
}//测试函数
- (void)test {self.conditionLock = [[NSConditionLock alloc] initWithCondition:3];dispatch_queue_t globalQ = dispatch_get_global_queue(0, 0);dispatch_async(globalQ, ^{[self.conditionLock lockWhenCondition:3];NSLog(@"任务1");[self.conditionLock unlockWithCondition:2];});dispatch_async(globalQ, ^{[self.conditionLock lockWhenCondition:2];NSLog(@"任务2");[self.conditionLock unlockWithCondition:1];});dispatch_async(globalQ, ^{[self.conditionLock lockWhenCondition:1];NSLog(@"任务3");[self.conditionLock unlockWithCondition:0];});
}

运行结果:
在这里插入图片描述

我们看到每次打印的结果都是严格按照任务1、任务2、任务3执行的。

总结NSConditionLockNSCondition

相同点:

  • 都是互斥锁。
  • 通过条件变量来控制加锁、释放锁,从而达到阻塞线程、唤醒线程的目的。

不同点:

  • NSCondition是基于对pthread_mutex的封装,而NSConditionLock是对NSCondition做了一层封装。
  • NSCondition是需要手动让线程进入等待状态阻塞线程、释放信号唤醒线程,NSConditionLock则只需要外部传入一个值,就会依据这个值进行自动判断是阻塞线程还是唤醒线程。

Semaphore信号量

Semaphore信号量也可以解决线程安全问题,GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号。类似于过高速路收费站的栏杆。可以通过时,打开栏杆,不可以通过时,关闭栏杆。在 Dispatch Semaphore 中,使用计数来完成这个功能,计数小于 0 时需要等待,不可通过。计数为 0 或大于 0 时,不用等待可通过。计数大于 0 且计数减 1 时不用等待,可通过。

Dispatch Semaphore 提供了三个方法:

dispatch_semaphore_create://创建一个 Semaphore 并初始化信号的总量
dispatch_semaphore_signal://发送一个信号,让信号总量加 1
dispatch_semaphore_wait://可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。

注意: 信号量的使用前提是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后使用信号量

Dispatch Semaphore 在实际开发中主要用于:

  • 保持线程同步,将异步执行任务转换为同步执行任务。
  • 保证线程安全,为线程加锁。

@synchronized

@synchronized可能是日常开发中用的比较多的一种递归互斥锁,因为它的使用比较简单,但并不是在任意场景下都能使用@synchronized,且它的性能较低。

使用方法如下:

@synchronized (obj) {}

下面我们来探索一下@synchronized的源码:

  • 通过汇编能发现@synchronized就是实现了objc_sync_enterobjc_sync_exit两个方法。
  • 通过符号断点能知道这两个方法都是在objc源码中的。
  • 通过clang也能得到一些信息。
#pragma clang assume_nonnull endint main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; { id _rethrow = 0; id _sync_obj = (id)__null; 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_6p_mn3hwpz14_7dg_gr79rtm4n80000gn_T_main_59328a_mi_0);} 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);}
}}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/20515.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

openCV C++环境配置

文章目录 一、openCV 安装二、新建项目三、配置环境变量四、测试使用 编译器:vs2017 OpenCV:4.5.4 一、openCV 安装 将openCV安装到一个路径下&#xff0c;我安装到了D盘根目录下 二、新建项目 在vs2017新建控制台空项目&#xff0c;打开项目属性 在VC目录 -> 包含目录下…

智慧林业~经典开源项目数字孪生智慧林业——开源工程及源码

东北林业局的工程和源码免费赠送&#xff0c;帮您实现深林防火的智慧林业。 项目介绍 东北林业局作为东北地区林业管理的重要机构&#xff0c;致力于森林资源保护和防火工作。他们的项目通过先进的技术手段&#xff0c;为林业管理提供可靠的解决方案。 本项目使用数字孪生技术&…

【实操教程】如何开始用Qt Widgets编程?(一)

Qt 是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写&#xff0c;所有平台无差别运行&#xff0c;更提供了几乎所有开发过程中需要用到的工具。如今&#xff0c;Qt已被运用于超过70个行业、数千家企业&#xff0c;支持数百万设备及应用。 在本文中&#xff0…

轮足机器人硬件总结

简介 本文主要根据“轮腿机器人Hyun”总结的硬件部分。 轮腿机器人Hyun开源地址&#xff1a;https://github.com/HuGuoXuang/Hyun 1 电源部分 1.1 78M05 78M05是一款三端稳压器芯片&#xff0c;它可以将输入电压稳定输出为5V直流电压. 1.2 AMS1117-3.3 AMS1117-3.3是一种输…

Vue实现leafletMap自定义绘制线段 并且删除指定的已绘制的点位

效果&#xff1a;点击表格可实现选中地图点位&#xff0c;删除按钮点击可删除对应点位并且重新绘制线段&#xff0c;点击确定按钮 保存已经绘制的点位信息传给父组件 并且该组件已实现回显 完整的组件代码如下 文件名称为&#xff1a; leafletMakePointYt <!--* Descripti…

FPGA优质开源模块 - SRIO

本文介绍一个FPGA常用模块&#xff1a;SRIO&#xff08;Serial RapidIO&#xff09;。SRIO协议是一种高速串行通信协议&#xff0c;在我参与的项目中主要是用于FPGA和DSP之间的高速通信。有关SRIO协议的详细介绍网上有很多&#xff0c;本文主要简单介绍一下SRIO IP核的使用和本…

SIFT算法原理:SIFT算法详细介绍

前面我们介绍了Harris和Shi-Tomasi角点检测算法&#xff0c;这两种算法具有旋转不变性&#xff0c;但不具有尺度不变性&#xff0c;以下图为例&#xff0c;在左侧小图中可以检测到角点&#xff0c;但是图像被放大后&#xff0c;在使用同样的窗口&#xff0c;就检测不到角点了。…

Excel技巧 - 管理规则设置一行变色

如何设置某一列单元格的值大于一个值时&#xff0c;该单元格所在的一整行都变色呢&#xff1f; 1、先框选内容区域&#xff0c;点击开始&#xff0c;条件格式&#xff0c;新建规则 2、如果销量大于20&#xff0c;则该行都变为绿色 编辑格式选择&#xff1a;使用公式确定要设置…

【MySQL】存储过程(十一)

🚗MySQL学习第十一站~ 🚩本文已收录至专栏:MySQL通关路 ❤️文末附全文思维导图,感谢各位点赞收藏支持~ 一.引入 存储过程是事先经过编译并存储在数据库中的一段 SQL 语句的集合,调用存储过程可以简化应用开发人员的工作,可以减少数据在数据库和应用服务器之间的传输,…

大数据技术之Clickhouse---入门篇---SQL操作、副本

星光下的赶路人star的个人主页 积一勺以成江河&#xff0c;累微尘以崇峻极 文章目录 1、SQL操作1.1 Insert1.2 Update 和 Delete1.3 查询操作1.4 alter操作1.5 导出数据 2、副本2.1 副本写入流程2.2 配置步骤 1、SQL操作 基本上来说传统关系型数据库&#xff08;以 MySQL 为例…

【雕爷学编程】MicroPython动手做(30)——物联网之Blynk 4

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

phpstudy 进行 composer 全局配置

背景 因为注意到&#xff0c;使用 phpStudy 进行环境搭建时&#xff0c;有时需要使用 composer 每次都需要查找资料进行配置&#xff0c; 在此进行记录笔记&#xff0c;方便有需要的道友借鉴 配置 版本&#xff1a;composer1.8.5&#xff08;phpStudy8 当前只能安装这一个版本…

Flink作业调度的9种状态

1.什么是作业调度 Flink 通过 Task Slots 来定义执行资源。每个 TaskManager 有一到多个 task slot&#xff0c;每个 task slot 可以运行一条由多个并行 task 组成的流水线。 这样一条流水线由多个连续的 task 组成&#xff0c;比如并行度为 n 的 MapFunction 和 并行度为 n 的…

省份数量(力扣)深度优先 JAVA

有 n 个城市&#xff0c;其中一些彼此相连&#xff0c;另一些没有相连。如果城市 a 与城市 b 直接相连&#xff0c;且城市 b 与城市 c 直接相连&#xff0c;那么城市 a 与城市c 间接相连。 省份 是一组直接或间接相连的城市&#xff0c;组内不含其他没有相连的城市。 给你一个 …

作为一个老程序员,想对新人说什么?

前言 最近知乎上&#xff0c;有一位大佬邀请我回答下面这个问题&#xff0c;看到这个问题我百感交集&#xff0c;感触颇多。 在我是新人时&#xff0c;如果有前辈能够指导方向一下&#xff0c;分享一些踩坑经历&#xff0c;或许会让我少走很多弯路&#xff0c;节省更多的学习的…

CNVD-2023-12632 泛微e-cology9 sql注入 附poc

目录 漏洞描述影响版本漏洞复现漏洞修复 众亦信安&#xff0c;中意你啊&#xff01; 微信搜索&#xff1a;众亦信安&#xff0c;回复关键字&#xff1a;230317 领批量检测脚本。 声明&#xff1a;文中涉及到的技术和工具&#xff0c;仅供学习使用&#xff0c;禁止从事任何非法…

汽车智能化再掀新热潮!「中央计算架构」进入规模量产周期

中央计算区域控制的新一代整车电子架构&#xff0c;已经成为车企继电动化、智能化&#xff08;功能上车&#xff09;之后&#xff0c;新一轮竞争的焦点。 如果说智能化的1.0阶段&#xff0c;是智能驾驶智能座舱的争夺战&#xff1b;那么&#xff0c;即将进入的2.0阶段&#xff…

黑马大数据学习笔记3-MapReduce配置和YARN部署以及基本命令

目录 部署说明MapReduce配置文件YARN配置文件分发配置文件集群启动命令开始启动YARN集群 查看YARN的WEB UI页面保存快照YARN集群的启停命令一键启动脚本单进程启停 提交MapReduce任务到YARN执行提交wordcount示例程序查看运行日志提交求圆周率示例程序 p41~43 https://www.bili…

IntelliJ IDEA快捷键大全 + 动图演示!

一、构建/编译 Ctrl F9&#xff1a;构建项目该快捷键&#xff0c;等同于菜单【Build】—>【Build Project】 执行该命令后&#xff0c;IntelliJ IDEA 会编译项目中所有类&#xff0c;并将编译结果输出到out目录中。IntelliJ IDEA 支持增量构建&#xff0c;会在上次构建的基…

ES6之Promise、Class类与模块化(Modules)

目录 PromiseClass类extendssuper Modules 模块系统export default 和对应importexport 和 import Promise Promise 是 ES6 引入的一种用于处理异步操作的对象。 它解决了传统回调函数&#xff08;callback&#xff09;模式中容易出现的回调地狱和代码可读性差的问题。 Promis…