iOS--锁的学习

news2024/10/7 6:46:55

iOS--锁的学习

  • 锁的介绍
    • 线程安全
  • 锁的分类
    • 自旋锁和互斥锁
    • OSSpinLock
    • os_unfair_lock
    • pthread_mutex
      • pthread_mutex的属性
    • NSLock
    • NSRecursiveLock
    • NSCondition
    • NSConditionLock
    • dispatch_semaphore
    • dispatch_queue
    • @synchronized
    • atomic
    • pthread_rwlock:读写锁
    • dispatch_barrier_async
  • 锁的性能比较

锁的介绍

主要参考了iOS多线程安全-13种线程锁🔒
在iOS开发中,锁是一种用于管理并发访问共享资源的机制。多个线程可以同时访问共享资源,但在某一时刻只允许一个线程对资源进行读取或修改,以避免数据竞争和不一致性。
锁的主要目的是确保在给定时间内只有一个线程可以进入被锁定的代码块或临界区。当一个线程获得了锁并进入临界区时,其他尝试获取锁的线程会被阻塞,直到该线程释放锁。这样可以保证在临界区内的代码只会被一个线程执行,从而避免竞态条件和数据不一致性的问题。

在iOS中,有多种锁机制可供选择,每种锁机制都有其特定的适用场景和性能特征。一些常见的锁机制包括:
互斥锁(Mutex):例如NSLock、pthread_mutex等。互斥锁提供了基本的线程同步机制,通过对临界区加锁和解锁来确保同一时间只有一个线程可以访问。
递归锁(Recursive Lock):例如NSRecursiveLock、pthread_mutex_recursive等。递归锁允许同一线程多次获取锁,避免了死锁情况。
条件锁(Condition Lock):例如NSConditionLock、pthread_cond等。条件锁在某些特定条件下才允许线程继续执行,否则线程会被阻塞。
信号量(Semaphore):例如dispatch_semaphore。信号量是一种更为通用的同步机制,可以用于控制多个线程的并发数量。
自旋锁(Spin Lock):例如os_unfair_lock、OSSpinLock(已被弃用)等。自旋锁在等待锁时不会将线程阻塞,而是通过循环忙等的方式尝试获取锁,适用于短时间内锁的竞争概率较低的情况。

线程安全

线程安全是指在多线程环境下,程序能够正确地处理共享数据并保证数据的一致性和正确性。
在多线程编程中,多个线程可以同时访问和修改共享的数据。如果没有适当的同步机制和线程安全的设计,可能会出现以下问题:

  • 竞态条件(Race Condition):多个线程同时对同一数据进行读写操作,导致结果依赖于线程执行的顺序,从而得到不确定的结果。
  • 数据竞争(Data Race):多个线程同时访问和修改共享数据,其中至少一个是写操作,导致未定义的行为。

这里用一个比较经典的例子,具体参考iOS多线程安全-13种线程锁🔒

//卖票演示
- (void)ticketTest{
self.ticketsCount = 50;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

for (NSInteger i = 0; i < 5; i++) {
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self sellingTickets];
}
});
}

}
//卖票
- (void)sellingTickets{
int oldMoney = self.ticketsCount;
sleep(.2);
oldMoney -= 1;
self.ticketsCount = oldMoney;

NSLog(@"当前剩余票数-> %d", oldMoney);
}

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

这里是一个典型的数据竞争问题,当多个线程同时对一个共享数据进行读写操作是很有可能会发生数据竞争,为了避免这种情况,我们往往通过线程同步来保证线程安全。实现线程同步的方法很多,锁就是一种比较常见的用法 ;

锁的分类

锁是实现线程同步和保证数据访问的一种机制,在多线程编程中起着重要的作用。根据实现方式和特性,锁可以分为几个不同的分类:

  • 互斥锁(Mutex
    Lock):互斥锁是最常见和基本的锁类型,用于实现互斥访问共享资源。它提供了两个状态:锁定(被一个线程持有)和解锁(无线程持有)。只有一个线程可以持有互斥锁,其他线程在获取锁之前会被阻塞。互斥锁可以防止多个线程同时访问临界区,从而保证数据的一致性。
  • 读写锁(Read-Write
    Lock):读写锁也称为共享-独占锁,它允许多个线程同时读取共享资源,但只允许一个线程进行写操作。读操作之间不会互斥,而读操作与写操作之间是互斥的。读写锁适用于读多写少的场景,可以提高并发性能。
  • 自旋锁(Spin
    Lock):自旋锁是一种忙等待锁,它使用循环来反复检查锁是否被释放,而不是将线程阻塞。当一个线程发现自旋锁被其他线程持有时,它会一直循环等待,直到自旋锁可用。自旋锁适用于临界区很小且短时间内会释放的情况。
  • 条件变量(Condition
    Variable):条件变量用于线程之间的等待和通知机制。它与互斥锁结合使用,允许一个线程等待某个条件成立并在条件满足时通知其他线程。等待条件的线程会释放互斥锁,以便其他线程可以访问临界区。条件变量常用于生产者-消费者问题等场景。
  • 屏障(Barrier):屏障用于多个线程在某个点上等待,直到所有线程都到达该点后才继续执行。它可以用于同步多个线程的操作,确保线程在达到某个阶段之前都等待其他线程。

自旋锁和互斥锁

自旋锁(Spin Lock):
自旋锁是一种忙等待锁,它使用循环来反复检查锁的状态,直到获取到锁为止。自旋锁的特点如下:

  • 自旋锁适用于临界区很小且短时间内会释放的情况,因为它需要持续占用CPU资源来进行自旋等待。
  • 自旋锁不会导致线程的阻塞和切换,所以对于短时间内能够获取到锁的情况,自旋锁的性能较好。
  • 自旋锁不会改变线程的调度顺序,因此在高优先级线程和低优先级线程之间,可能会导致优先级反转的问题。

互斥锁是一种常见的线程同步机制,用于实现互斥访问共享资源。在iOS中,常用的互斥锁有NSLock和NSRecursiveLock。互斥锁的特点如下:

  • 互斥锁允许一个线程持有锁,其他线程在获取锁之前会被阻塞,从而实现对临界区的互斥访问。
  • 互斥锁会导致线程的阻塞和切换,所以对于长时间占用锁或等待时间不确定的情况,互斥锁的性能可能较差。
  • 互斥锁可以解决优先级反转的问题,因为它可以改变线程的调度顺序,让优先级高的线程优先获取锁。

互斥锁:线程会从sleep(加锁)——>running(解锁),过程中有上下文的切换,cpu的抢占,信号的发送等开销。
自旋锁:线程一直是running(加锁——>解锁),死循环检测锁的标志位,机制不复杂。
对比
互斥锁的起始原始开销要高于自旋锁,但是基本是一劳永逸,临界区持锁时间的大小并不会对互斥锁的开销造成影响,而自旋锁是死循环检测,加锁全程消耗cpu,起始开销虽然低于互斥锁,但是随着持锁时间,加锁的开销是线性增长。

OSSpinLock

OSSpinLock是iOS旧版本中提供的一种自旋锁(Spin Lock)实现。它通过忙等待的方式来获取锁,并且不会导致线程的阻塞和切换。不过需要注意的是,自旋锁在iOS 10之后已被标记为废弃,因为它存在优先级反转和性能问题。推荐使用更现代的互斥锁实现,如os_unfair_lock和pthread_mutex等。
使用时需要导入头文件#import <libkern/OSAtomic.h>
在这里插入图片描述

os_unfair_lock

os_unfair_lock是iOS 10及以上版本引入的一种互斥锁(Mutex Lock)实现,用于实现线程同步和保护共享资源的访问。相比于旧版本中的自旋锁(OSSpinLock),os_unfair_lock采用了更高级的算法来解决优先级反转和性能问题。
os_unfair_lock的特点如下:

  • os_unfair_lock是一种互斥锁,只能由一个线程持有,其他线程在获取锁之前会被阻塞。
  • 它可以解决优先级反转问题,确保高优先级线程在等待锁时能够优先获取。
  • os_unfair_lock的实现采用了更高级的算法,避免了自旋等待,减少了不必要的CPU开销。
  • os_unfair_lock的性能通常比旧版自旋锁(OSSpinLock)更好,尤其在高并发情况下。
    需要导入头文件#import <os/lock.h>
@implementation ViewController {
    os_unfair_lock _lock ;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    [self ticketTest] ;


}

//卖票演示
- (void)ticketTest{
self.ticketsCount = 50;
//os_unfair_lock lock = OS_UNFAIR_LOCK_INIT ;
    _lock = OS_UNFAIR_LOCK_INIT ;

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

for (NSInteger i = 0; i < 5; i++) {
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {

            [self sellingTickets];

            }
        });
    }

}
//卖票
- (void)sellingTickets{
    os_unfair_lock_lock(&_lock) ;
    int oldMoney = self.ticketsCount;
    sleep(.2);
    oldMoney -= 1;
    self.ticketsCount = oldMoney;
    NSLog(@"当前剩余票数-> %d", oldMoney);
    os_unfair_lock_unlock(&_lock) ;
}



@end

在这里插入图片描述

要注意一下,通常不建议将 os_unfair_lock 直接作为属性来使用。os_unfair_lock 是一个结构体,不具备 Objective-C 对象的特性,因此不适合直接作为属性。

pthread_mutex

pthread_mutex 是 POSIX 线程库中提供的一种互斥锁(mutex)类型,用于实现线程同步和互斥访问共享资源。
主要的 pthread_mutex 函数如下:

  • pthread_mutex_init:用于初始化互斥锁,并指定互斥锁的属性。可以使用 NULL 作为第二个参数来使用默认属性。
  • pthread_mutex_destroy:用于销毁互斥锁。
  • pthread_mutex_lock:加锁操作,将互斥锁加锁,如果互斥锁已经被其他线程锁定,则当前线程被阻塞直到互斥锁可用。
  • pthread_mutex_trylock:尝试加锁操作,如果互斥锁已经被其他线程锁定,则该操作会立即返回,而不会阻塞当前线程。
  • pthread_mutex_unlock:解锁操作,释放互斥锁,允许其他线程获得锁。
@implementation ViewController {
//    os_unfair_lock _lock ;
    pthread_mutex_t _lock ;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    [self ticketTest] ;


}

//卖票演示
- (void)ticketTest{
self.ticketsCount = 50;
//os_unfair_lock lock = OS_UNFAIR_LOCK_INIT ;
//初始化锁属性
    pthread_mutexattr_t attr ;
    pthread_mutexattr_init(&attr) ;
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) ;
    pthread_mutex_init(&_lock, &attr) ;//第二个参数可以为NULL,表示使用默认属性PTHREAD_MUTEX_NORMAL

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

for (NSInteger i = 0; i < 5; i++) {
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {

            [self sellingTickets];

            }
        });
    }

}
//卖票
- (void)sellingTickets{
//    os_unfair_lock_lock(&_lock) ;
    pthread_mutex_lock(&_lock) ;
    int oldMoney = self.ticketsCount;
    sleep(.2);
    oldMoney -= 1;
    self.ticketsCount = oldMoney;
    NSLog(@"当前剩余票数-> %d", oldMoney);
//    os_unfair_lock_unlock(&_lock) ;
    pthread_mutex_unlock(&_lock) ;
}



@end

在这里插入图片描述

pthread_mutex的属性

pthread_mutexattr_settype:设置互斥锁的类型。常见选项有:

  • PTHREAD_MUTEX_NORMAL:普通互斥锁,不支持递归。
  • PTHREAD_MUTEX_RECURSIVE:递归互斥锁,允许同一线程多次获得锁。
  • PTHREAD_MUTEX_ERRORCHECK:错误检查互斥锁,会检测多次锁定同一互斥锁的错误。
  • PTHREAD_MUTEX_DEFAULT:根据实现的默认类型设置互斥锁。

NSLock

**NSLock是对mutex普通锁的封装。**pthread_mutex_init(mutex, NULL);
NSLock 遵循 NSLocking 协议。Lock 方法是加锁,unlock 是解锁,tryLock 是尝试加锁,如果失败的话返回 NO,lockBeforeDate: 是在指定Date之前尝试加锁,如果在指定时间之前都不能加锁,则返回NO

@protocol NSLocking
- (void)lock;
- (void)unlock;
@end

@interface NSLock : NSObject <NSLocking> {
@private
void *_priv;
}

- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name
@end

@implementation ViewController {
//    os_unfair_lock _lock ;
//    pthread_mutex_t _lock ;
    
}

- (void)viewDidLoad {
    [super viewDidLoad];
    [self ticketTest] ;


}

//卖票演示
- (void)ticketTest{
self.ticketsCount = 50;
//os_unfair_lock lock = OS_UNFAIR_LOCK_INIT ;
//初始化锁属性
//    pthread_mutexattr_t attr ;
//    pthread_mutexattr_init(&attr) ;
//    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) ;
//    pthread_mutex_init(&_lock, &attr) ;//第二个参数可以为NULL,表示使用默认属性PTHREAD_MUTEX_NORMAL

    self.lock = [[NSLock alloc] init] ;
    
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

for (NSInteger i = 0; i < 5; i++) {
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {

            [self sellingTickets];

            }
        });
    }

}
//卖票
- (void)sellingTickets{
//    os_unfair_lock_lock(&_lock) ;
//    pthread_mutex_lock(&_lock) ;
    [self.lock lock] ;
    int oldMoney = self.ticketsCount;
    sleep(.2);
    oldMoney -= 1;
    self.ticketsCount = oldMoney;
    NSLog(@"当前剩余票数-> %d", oldMoney);
//    os_unfair_lock_unlock(&_lock) ;
//    pthread_mutex_unlock(&_lock) ;
    [self.lock unlock] ;
}

//- (instancetype)init {
//    self = [super init];
//    if (self) {
//        _lock = [[NSLock alloc] init];
//    }
//    return self;
//}

//- (void)lock {
//    [_lock lock];
//}
//
//- (void)unlock {
//    [_lock unlock];
//}

@end

在这里插入图片描述

NSRecursiveLock

NSRecursiveLock是对mutex递归锁的封装,API跟NSLock基本一致

- (void)ticketTest{
self.ticketsCount = 50;
//os_unfair_lock lock = OS_UNFAIR_LOCK_INIT ;
//初始化锁属性
//    pthread_mutexattr_t attr ;
//    pthread_mutexattr_init(&attr) ;
//    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) ;
//    pthread_mutex_init(&_lock, &attr) ;//第二个参数可以为NULL,表示使用默认属性PTHREAD_MUTEX_NORMAL

    self.lock = [[NSRecursiveLock alloc] init] ;
    
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

for (NSInteger i = 0; i < 5; i++) {
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {

            [self sellingTickets];

            }
        });
    }

}
//卖票
- (void)sellingTickets{
//    os_unfair_lock_lock(&_lock) ;
//    pthread_mutex_lock(&_lock) ;
    [self.lock lock] ;
    int oldMoney = self.ticketsCount;
    sleep(.2);
    oldMoney -= 1;
    self.ticketsCount = oldMoney;
    NSLog(@"当前剩余票数-> %d", oldMoney);
//    os_unfair_lock_unlock(&_lock) ;
//    pthread_mutex_unlock(&_lock) ;
    [self.lock unlock] ;
}

NSCondition

NSCondition是对mutex和cond的封装,更加面向对象 ;

@interface NSCondition : NSObject <NSLocking>

- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;

@property (nullable, copy) NSString *name;

@end
  • NSCondition 类继承自 NSObject 并遵循 NSLocking 协议。
  • wait 方法用于使当前线程等待,直到接收到信号。 waitUntilDate: 方法使当前线程等待,直到接收到信号或者指定的日期超时。
  • signal 方法用于唤醒一个等待中的线程。
  • broadcast 方法用于唤醒所有等待中的线程。
  • name 属性是一个可选的字符串,用于标识 NSCondition 对象的名称。
// 线程1
// 删除数组中的元素
- (void)__remove
{
[self.condition lock];
if (self.data.count == 0) {
// 等待
[self.condition wait];
}
[self.data removeLastObject];
NSLog(@"删除了元素");
[self.condition unlock];
}

// 线程2
// 往数组中添加元素
- (void)__add
{
[self.condition lock];
sleep(1);
[self.data addObject:@"Test"];
NSLog(@"添加了元素");
// 信号
[self.condition signal];
[self.condition unlock];
}

我的想法是这类对象有两种线程等待的方式,一种是wait–signal方式,一种是lock–unlock方式 ;

NSConditionLock

NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值

@interface NSConditionLock : NSObject <NSLocking> {
 
- (instancetype)initWithCondition:(NSInteger)condition;

@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;
@end

dispatch_semaphore

ispatch_semaphore 是 Grand Central Dispatch (GCD) 中的一个信号量机制,用于控制并发访问共享资源的线程数量。这个之前GCD中讲过了 ;

dispatch_queue

使用GCD的串行队列也可以实现线程同步的

@synchronized

@synchronized 是 Objective-C 中用于实现线程安全的关键字之一。它提供了一种简单的方式来保护共享资源,以防止多个线程同时访问和修改该资源。
使用 @synchronized 的基本语法如下:

@synchronized (object) {
    // 同步的代码块
    // ...
}

@synchronized 关键字的工作原理是,在进入 @synchronized 代码块之前,它会获取 object 的互斥锁,以确保同一时间只有一个线程可以进入该代码块。当线程离开 @synchronized 代码块时,它会释放锁,允许其他线程继续进入。

关于@synchronized 的底层原理可以去深入看看,但平时使用只要知道它会在代码块开头加锁,代码块末尾解锁就行了

atomic

在Objective-C中,atomic是一种属性修饰符,用于指定属性的原子性。当一个属性被声明为atomic时,它意味着在对该属性进行读取和写入操作时,将会保证操作的原子性。
原子性是指一个操作要么完全执行,要么完全不执行,不存在执行过程中被中断的情况。在多线程环境下,使用atomic修饰符可以确保对属性的读取和写入操作是线程安全的。
当属性被声明为atomic时,编译器会自动生成相关的同步代码,以确保对属性的访问是原子操作。这样可以防止多个线程同时对同一个属性进行读写操作,避免出现数据竞争和不一致的情况。
需要注意的是,虽然atomic提供了一定程度的线程安全性,但它并不能完全保证线程安全。在高并发的多线程环境中,仍然需要额外的同步机制来确保数据的一致性和正确性。
相比之下,另一个属性修饰符nonatomic则表示属性是非原子的,它不会提供自动的线程安全保护。在多线程环境下,使用nonatomic修饰符可以提高性能,但需要开发人员自行处理线程安全问题。
总之,atomic属性修饰符用于指定属性的原子性,提供一定程度的线程安全性。在多线程环境中,可以使用atomic来确保对属性的读取和写入操作是原子的,但仍需要注意其他的线程安全问题。

pthread_rwlock:读写锁

//初始化锁
pthread_rwlock_t lock;
pthread_rwlock_init(&_lock, NULL);

//读加锁
pthread_rwlock_rdlock(&_lock);
//读尝试加锁
pthread_rwlock_trywrlock(&_lock)

//写加锁
pthread_rwlock_wrlock(&_lock);
//写尝试加锁
pthread_rwlock_trywrlock(&_lock)

//解锁
pthread_rwlock_unlock(&_lock);
//销毁
pthread_rwlock_destroy(&_lock);


pthread_rwlock 是 POSIX 线程库中的读写锁(read-write lock)机制。它提供了一种多读单写的并发访问控制机制,允许多个线程同时读取共享资源,但只允许一个线程进行写操作。
使用 pthread_rwlock 可以实现以下功能:

  • 多读单写控制:多个线程可以同时获取读锁(读取共享资源),但只有一个线程可以获取写锁(修改共享资源)。
  • 读写互斥:在写锁被持有期间,其他线程无法获取读锁,以确保数据一致性。

dispatch_barrier_async

GCD中也有 ;

锁的性能比较

性能从高到低排序

1、os_unfair_lock
2、OSSpinLock
3、dispatch_semaphore
4、pthread_mutex
5、dispatch_queue(DISPATCH_QUEUE_SERIAL)
6、NSLock
7、NSCondition
8、pthread_mutex(recursive)
9、NSRecursiveLock
10、NSConditionLock
11、@synchronized

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

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

相关文章

react【框架原理详解】JSX 的本质、SyntheticEvent 合成事件机制、组件渲染过程、组件更新过程

JSX 的本质 JSX 代码本身并不是 HTML&#xff0c;也不是 Javascript&#xff0c;在渲染页面前&#xff0c;需先通过解析工具&#xff08;如babel&#xff09;解析之后才能在浏览器中运行。 babel官网可查看 JSX 解析后的效果 更早之前&#xff0c;Babel 会把 JSX 转译成一个 R…

Linux 内核

查看内核的发行版 $ uname -r 5.4.0-150-genericcd /lib/modules/5.4.0-150-generic, 内核源码所在的位置&#xff1a;/usr/src 这里的内核源码路径&#xff08;–kernel-source-path&#xff09;即为&#xff1a; cd /usr/src/linux-headers-5.4.0-150-generic/ 临时生效: …

自建公式,VBA在Excel中轻松获取反义词

自建公式&#xff0c;VBA在Excel中轻松获取反义词 文章目录 前言一、爬取网站数据二、代码1.创建数据发送及返回方法2.汉字转UTF8编码2.获取反义词 三、运行效果截图 前言 小学语文中&#xff0c;近义词、反义词是必考内容之一。家长不能随时辅导怎么办&#xff1f;有VBA&…

dsPIC单片机buck-boost拓扑双向DC-DC电源变换器设计

为实现电池储能装置的双向DC-DC变换器&#xff0c;本系统以buck-boost拓扑电路为核心&#xff0c;通过DSPICFJ256GP710单片机最小系统控制拓扑的切换&#xff0c;从而进行buck恒流充电和boost恒压放电。充电时效率≥94%&#xff0c;放电时效率≥95.5%&#xff0c;具有过压保护及…

引流500+创业粉,抖音口播工具

在抖音平台运营一个专注于口播的工具号&#xff0c;旨在集结超过500位热衷于创业的粉丝&#xff0c;这需要精心筹划的内容策略和周到的运营计划。首先&#xff0c;明确你的口播工具号所专注的领域&#xff0c;无论是分享创业经验、财务管理技巧还是案例分析&#xff0c;确保你所…

springboot错误

错误总结 1、使用IDEA 的 initialalzer显示2、IDEA 新建文件 没有 java class3、java: 错误: 不支持发行版本 22解决方法4、IDEA-SpringBoot项目yml配置文件不自动提示解决办法 1、使用IDEA 的 initialalzer显示 IDEA创建SpringBoot项目时出现&#xff1a;Initialization fail…

秋招突击——算法——模板题——区间DP(1)——加分二叉树

文章目录 题目描述思路分析实现代码分析总结 题目描述 思路分析 实现代码 不过我的代码写的真的不够简洁&#xff0c;逻辑不够清晰&#xff0c;后续多练练吧。 // 组合数问题 #include <iostream> #include <algorithm>using namespace std;const int N 35; int…

JDBC使用QreryRunner简化SQL查询注意事项

QreryRunner是Dbutils的核心类之一&#xff0c;它显著的简化了SQL查询&#xff0c;并与ResultSetHandler协同工作将使编码量大为减少。 注意事项 1. 使用QreryRunner必须保证实体类的变量名&#xff0c;和sql语句中要查找的字段名必须相同&#xff0c;否则查询 不到数据,会出…

视频号小店去哪里找货源?最全货源渠道分享!

大家好&#xff0c;我是电商糖果 视频号小店因为是这两年电商行业新出来的黑马&#xff0c;吸引着不少商家入驻。 入驻了商家中很多都没有自己的货源渠道。 他们基本都是从无货源开始起步&#xff0c;后期通过积累资源&#xff0c;慢慢搭建属于自己的货源渠道。 可是渐渐的…

FreeRTOS中断中释放信号量

串口接收&#xff1a;中断程序中逆序打印字符串 串口接收&#xff1a;逆序回环实验思路 注&#xff1a;任务优先级较高会自动的切换上下文进行运行 FreeRTOS中的顶半操作和底半操作 顶半操作和底半操作“这种叫法源自与Linux”在嵌入式开发中&#xff0c;为了和Linux操作系统做…

leetcode 1631. 最小体力消耗路径 二分+BFS、并查集、Dijkstra算法

最小体力消耗路径 题目与水位上升的泳池中游泳类似 二分查找BFS 首先&#xff0c;采用二分查找&#xff0c;确定一个体力值&#xff0c;再从左上角&#xff0c;进行BFS&#xff0c;查看能否到达右下角&#xff0c;如果不行&#xff0c;二分查找就往大的数字进行查找&#xff…

终端安全管理系统、天锐DLP(数据泄露防护系统)| 数据透明加密保护,防止外泄!

终端作为企业员工日常办公、数据处理和信息交流的关键工具&#xff0c;承载着企业运营的核心信息资产。一旦终端安全受到威胁&#xff0c;企业的敏感数据将面临泄露风险&#xff0c;业务流程可能遭受中断&#xff0c;甚至整个企业的运营稳定性都会受到严重影响。 因此&#xff…

Java——认识Java

一、介绍 1、起源 Java 是由 Sun Microsystems 于 1995 年推出的一种面向对象的编程语言和计算平台。由詹姆斯高斯林&#xff08;James Gosling&#xff0c;后来被称为Java之父&#xff09;和他的同事们共同研发。后来&#xff0c;Sun 公司被 Oracle&#xff08;甲骨文&#…

【list】list库介绍 + 简化模拟实现

本节博客先对list进行用法介绍&#xff0c;再在库的基础上简化其内容和形式&#xff0c;简单进行模拟实现&#xff0c;有需要借鉴即可。 目录 1.list介绍1.1 list概述1.2相关接口的介绍 2.简化模拟实现3.各部分的细节详述3.1结点3.2迭代器细节1&#xff1a;迭代器用原生指针还是…

【动态规划】斐波那契数列模型(C++)

目录 1137.第N个泰波那契数 解法&#xff08;动态规划&#xff09; 算法流程 1. 状态表⽰&#xff1a; 2. 状态转移⽅程&#xff1a; 3. 初始化&#xff1a; 4. 填表顺序&#xff1a; 5. 返回值&#xff1a; C算法代码 优化&#xff1a; 滚动数组 测试&#xff1a; …

电脑提示请重新安装软件MSVCP140.dll的几种解决方法分享

在日常使用电脑的过程中&#xff0c;我们常常会遇到一些错误提示&#xff0c;其中之一就是找不到msvcp140.dll文件&#xff0c;导致软件无法正常启动运行。这个问题可能是由于缺少相应的依赖库或者版本不匹配引起的。下面我将介绍5种解决方法&#xff0c;帮助大家解决这个问题。…

0524_网络编程8

思维导图&#xff1a;

Java基础的语法---StringBuilder

StringBuilder 构造方法 StringBuilder()&#xff1a;创建一个空的StringBuilder实例。 StringBuilder(String str)&#xff1a;创建一个StringBuilder实例&#xff0c;并将其初始化为指定的字符串内容。 StringBuilder(int a): 创建一个StringBuilder实例…

数据结构--《二叉树》

二叉树 1、什么是二叉树 二叉树(Binar Tree)是n(n>0)个结点的优先集合&#xff0c;该集合或者为空集(称为空二叉树)&#xff0c;或者由一个根结点和两颗互不相交的、分别称为根结点的左子树和右子树的二叉树构成。 这里给张图&#xff0c;能更直观的感受二叉树&#xff1…

AJAX初级

AJAX的概念&#xff1a; 使用浏览器的 XMLHttpRequest 对象 与服务器通信 浏览器网页中&#xff0c;使用 AJAX技术&#xff08;XHR对象&#xff09;发起获取省份列表数据的请求&#xff0c;服务器代码响应准备好的省份列表数据给前端&#xff0c;前端拿到数据数组以后&#xf…