【Objective -- C】—— GCD(1)(Grand Central Dispatch)

【Objective -- C】—— GCD(1)(Grand Central Dispatch)

  • GCD
    • 1. 什么是GCD
    • 2.多线程编程
      • 线程
      • 多线程的理解
      • 多线程的优点
    • 3.任务和队列
      • 任务
          • 同步执行和异步执行
      • 队列
        • 串行队列和并发队列
  • GCD的API
    • 1.Dispatch Queue
      • Dispatch Queue的分类
      • 2. dispatch_queue_create 方法
        • 2.1 serialQueue的并行
        • 2.2dispatch_release方法
    • 3.Main Dispatch Queue & Global Dispatch Queue
    • 4.dispatch_set_target_queue函数
      • 变更优先级
      • 添加执行层次
    • 5.dispatch_after GCD延迟执行方法
    • 6. Dispatch Group—GCD队列组
    • 7. GCD 栅栏方法:dispatch_barrier_async
    • 8. dispatch_sync 同步线程
      • **dispatch_sync & dispatch_async**
    • 9. dispatch_apply-GCD 快速迭代
    • 10. dispatch_suspend & dispatch_resume
    • 11.dispatch_semaphore-GCD 信号量
    • 12.dispatch_once

GCD

1. 什么是GCD

GCD的全称是Grand Central Dispatch,是异步执行任务的技术之一。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。为了开发者的方便,我们只需要定义想执行的任务并追加到适当的dispatch Queue中GCD就能自动生成必要的线程并计划执行任务,提升了效率。

为什么使用GCD

  • GCD 可用于多核的并行运算;
  • GCD 会自动利用更多的 CPU 内核(比如双核、四核);
  • GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
  • 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。
    接下来详细了解并学习GCD,书上简单的代码介绍了他的强大之处:
    在这里插入图片描述
    在上述GCD中的例子是经过了长时间的处理结束的时候,主线程使用该处理结果的源代码。仅用了一行代码让处理在后台线程中进行。
dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"主线程");
        // 主线程操作
    });

2.多线程编程

线程

一段从上到下顺序执行的代码是先通过编译器转换为CPU命令行二进制代码。一段从上到下顺序执行的代码是先通过编译器转换为CPU命令行二进制代码。
在这里插入图片描述
之后编译器汇集CPU命令列和数据将其安装到手机或电脑上。对于OS X和iOS的处理顺序是从CPU应用程序指定的地址开始一个一个的执行CPU命令列循环下去。

对于OC机制来说,由于if 和for循环的存在执行命令列的地址会是进行位置迁移,但是CPU一次只能执行一个命令不能执行某处分开的并列命令,因此CPU执行的命令列好比一条无分差的道路,执行不会出现分歧。
在这里插入图片描述
一次CPU执行的CPU命令列为一条无分叉路经-线程,多线程就是下图的多条无分叉路,此时多线程里一个CPU执行多条不同路径上的不同命令。
在这里插入图片描述

多线程的理解

OS X的核心XNU内核在发生操作系统时间的时候会切换执行路经。例如CPU的寄存器信息保存在各自路经专用的内存块里,从切换目标路经专用的内存块里复原CPU寄存器的信息,继续执行切换路经的CPU命令列叫上下文下切换
多线程编程就是多个CPU核反复进行某个线程和其他线程的上下文切换,叫多线程编程技术。
多线程编程存在:死锁,过于消耗内存,数据竞争等,但多线程编程可以保证程序的响应性能,利大于弊。
在这里插入图片描述

多线程的优点

多线程编程可以保证把一些费时间的处理放在主线程之外,这是提高系统性能的关键,也就是刚开始说的长时间的处理不在主线程而在其他线程的原因。

3.任务和队列

任务

就是执行操作的意思,在线程中执行的那段代码。在 GCD 中是放在 block 中的。执行任务有两种方式:『同步执行』 和 『异步执行』。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。

同步执行和异步执行
  • 同步执行sync
    • 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
    • 只能在当前线程中执行任务,不具备开启新线程的能力。
  • 异步执行async
    • 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
    • 可以在新的线程中执行任务,具备开启新线程的能力
    • 异步执行(async)虽然具有开启新线程的能力,但是并不一定开启新线程。这跟任务所指定的队列类型有关。

队列

队列(Dispatch Queue):这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。
在这里插入图片描述

串行队列和并发队列
  • 串行队列
    • 每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务 )
      在这里插入图片描述
  • 并发队列
    • 可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
    • 并发队列只有在异步执行dispatch_async方法下有效。
      在这里插入图片描述

GCD的API

GCD大大简化偏于复杂的多线程编程的源代码,封存在一个个API里。

1.Dispatch Queue

GCD:开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中。

 dispatch_async(queue, ^{
        // 想执行的任务
    });

使用block语法“定义想执行的任务”,通过dispatch_async函数追加赋值在变量queue的Dispatch Queue中,即可使指定的block在另一个线程执行。

Dispatch Queue是执行处理的等待队列,和上面讲的一样的,FIFO先进先出的原则执行。
在这里插入图片描述

Dispatch Queue的分类

Dispatch Queue在执行处理的时候分为 等待现在执行中处理的 Serial Dispatch Queue,不等待现在之中处理的 Concurrent Dispatch Queue

Dispatch Queue说明
Serial Dispatch Queue等待现在执行处理结束
Concurrent Dispatch Queue不等待现在执行处理结束

在这里插入图片描述
比较不同的Queue,追加多个处理的时候。

	dispatch_async(queue, blk0);
    dispatch_async(queue, blk1);
    dispatch_async(queue, blk2);
    dispatch_async(queue, blk3);
    dispatch_async(queue, blk4);
    dispatch_async(queue, blk5);

当变量为Serial Dispatch Queue,变量一定是遵循等待现在执行结束的原则,也就是按照blk0-blk5执行顺序。
当变量为Concurrent Dispatch Queue,变量遵循无需等待现在执行结束,执行bk0之后紧接着执行blk1,blk0是否结束不影响blk1的执行,这就是并行执行。

并行执行也就是多个线程同时进行多个处理,一个图理解一下并发执行,单个线程仍然是遵循一个线程一个任务。
在这里插入图片描述

2. dispatch_queue_create 方法

创建串行并行队列
在上一条未说明如何获取dispatch_queue的queue,这里是dispatch_queue_create 方法可以生成queue。
dispatch_queue_create 方法,该方法需要传入两个参数

dispatch_queue_create(const char *_Nullable DISPATCH_UNSAFE_INDEXABLE label,
		dispatch_queue_attr_t _Nullable attr);

  • 第一个参数表示队列的唯一标识符,用于 DEBUG,可为空。队列的名称推荐使用应用程序 ID 这种逆序全程域名。
  • 第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT 表示并发队列。
// 串行队列的创建方法
    dispatch_queue_t serialQueue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_SERIAL);
    // 并发队列的创建方法
    dispatch_queue_t concurrentQueue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);

2.1 serialQueue的并行

在这里插入图片描述
dispatch_queue_t 方法可以生成任意多的 Dispatch_queue,当生成多个 serialQueue的时候各个 serialQueu将并行执行。这么做会大大降低响应性能。
在这里插入图片描述
还会产生数据竞争
在这里插入图片描述
所以还是建议Serial Queue使用一个线程。如果想不发生竞争问题还是使用并行队列 Concureent Queue系统XNU内核会合理的管理线程。

在Queue里执行指定的block:

 dispatch_queue_t concurrentQueue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(concurrentQueue, ^{
        NSLog(@"blk on ConCurrentQueue");
    });

2.2dispatch_release方法

书上的意思是尽管在ARC下我们还需要手动释放DispatchQueue dispatch_release。

3.Main Dispatch Queue & Global Dispatch Queue

对于串行队列,GCD 默认提供了:主队列(Main Dispatch Queue)追加到Main Dispatch Queue的处理在主线程的RunLoop中执行。
在这里插入图片描述
所有放在主队列中的任务,都会放到主线程中执行。
可使用 dispatch_get_main_queue() 方法获得主队列。

// 主队列的获取方法
    dispatch_queue_t mainQueue = dispatch_get_main_queue();

主队列其实并不特殊。 主队列的实质上就是一个普通的串行队列,只是因为默认情况下,当前代码是放在主队列中的,然后主队列中的代码,有都会放到主线程中去执行,所以才造成了主队列特殊的现象

对于并行队列 GCD 默认提供了全局并发队列(Global Dispatch Queue)
使用 dispatch_get_global_queue 方法

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

Global Dispatch Queue 有四个执行优先级。XNU内核管理的线程,将各自使用的Global Dispatch Queue的执行优先级作为线程的优先级使用。在Global Dispatch Queue里追加处理的时候应选择与处理内容对应的执行优先级的Global Dispatch Queue。
在这里插入图片描述
不同优先级Queue获取方法:

// global 高优先级
    dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    
    // 默认优先级
    dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 低优先级
    dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
    // 后台优先级
    dispatch_queue_t globalDispatchQueueBackGround = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

4.dispatch_set_target_queue函数

变更优先级

dispatch_queue_create 方法生成的Queue不论是串行还是并行队列,都是和globalQueue的默认优先级相同执行优先级的线程,对于变更优先级我们需要使用dispatch_set_target_queue函数

  • 第一个参数是需要变更优先级的参数,第二个参数指定了与那个优先级相同的队列。
 dispatch_set_target_queue(serialQueue, globalDispatchQueueBackGround);

添加执行层次

除此之外,dispatch_set_target_queue函数还可以做成Dispatch Queue的执行阶层。
在这里插入图片描述
也就是说在必须将不可并行执行的处理追加到多个串行队列的时候可以使用,dispatch_set_target_queue函数将目标变为某一个串行队列防止处理并发执行,我理解为防止消耗太多内存,毕竟串行队列还是一个线程最好。

5.dispatch_after GCD延迟执行方法

dispatch_after函数是作为我们设定在多少秒之后执行处理的函数。

dispatch_after(dispatch_time_t when, dispatch_queue_t  _Nonnull queue, ^(void)block);
  • dispatch_after第一个参数是指定时间用的dispatch_time_t类型的值。

dispatch_time_t 值使用如下函数:

dispatch_time_t
dispatch_time(dispatch_time_t when, int64_t delta);

dispatch_after提供了更高的延时精度。它使用的是系统的底层时间管理机制,可以实现相对准确的延时。而NSTimer的精度受到运行循环(run loop)的影响,可能会受到其他事件的干扰,导致延时不够准确。

6. Dispatch Group—GCD队列组

当出现多个处理全部执行结束的时候回到主队列执行结束处理,这种情况经常出现,这个时候GCD队列组的出现能帮助我们实现。

  • 调用队列组的 dispatch_group_async 先把任务放到队列中,然后将队列放入队列组中。或者使用队列组的 dispatch_group_enter、dispatch_group_leave 组合来实现 dispatch_group_async。
  • 调用队列组的 dispatch_group_notify 回到指定线程执行任务。或者使用 dispatch_group_wait 回到当前线程继续向下执行(会阻塞当前线程。

使用队列组的原因
无论向如何类型的Queue追加处理,Dispatch Group都可以监视这些处理直到执行结束,一旦执行结束,就可以将结束处理追加到DispatchQueue中。

dispatch_group_notify
监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。
参数:队列组,回到的线程,执行的block

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk0");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk1");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk2");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk3");
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"task all Done");
    });

dispatch_group_wait
暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。

  • 参数:队列组,第二参数指定为等待的时间(超时),DISPATCH_TIME_FOREVER意味永久等待,也就是队列组尚未结束就不会取消等待。
- (void)groupWait {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk0");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk1");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk2");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk3");
    });
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"group end");
}

当所有任务执行完成之后,才执行 dispatch_group_wait 之后的操作。但是,使用dispatch_group_wait 会阻塞当前线程。
dispatch_group_enter、dispatch_group_leave

  • dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1。
  • dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1。
  • 当 group 中未执行完毕任务数为0的时候,才会使 dispatch_group_wait 解除阻塞,以及执行追加到 dispatch_group_notify 中的任务。

7. GCD 栅栏方法:dispatch_barrier_async

在访问数据库或者文件的时候serial Queue可以避免数据竞争,其实这个场景就是我们有时需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。
我们就需要一个相当于栅栏一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务 GCD栅栏方法 dispatch_barrier_async函数。多用于异步操作。

  • 该函数同queue creat生成的Concurrent Dispatch Queue一起使用
/**
 * 栅栏方法 dispatch_barrier_async
 */
- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_async(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_barrier_async(queue, ^{
        // 追加任务 barrier
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
    });
    
    dispatch_async(queue, ^{
        // 追加任务 3
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_async(queue, ^{
        // 追加任务 4
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"4---%@",[NSThread currentThread]);      // 打印当前线程
    });
}

dispatch_barrier_async 方法原理

dispatch_barrier_async 方法会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。然后在 dispatch_barrier_async 方法追加的任务执行完毕之后,异步队列才恢复为一般动作,接着追加任务到该异步队列并开始执行。
在这里插入图片描述

8. dispatch_sync 同步线程

GCD 提供了同步执行任务的创建方法 dispatch_sync 和异步执行任务创建方法 dispatch_async。

// 同步执行任务创建方法
dispatch_sync(queue, ^{
    // 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
    // 这里放异步执行任务代码
});

dispatch_sync & dispatch_async

dispatch_async意味着非同步,就是将制定的block非同步的追加到queue中, dispatch_async不会进行任何等待
在这里插入图片描述
dispatch_sync意味着同步,就是将制定的block同步的追加到queue中, dispatch_sync会在追加结束之前一直等待
在这里插入图片描述
死锁

  • 一旦使用sync函数,那么在指定处理结束之前函数不会返回,但它容易引起死锁。
- (void)syncLockForever {
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue,^ {
        NSLog(@"hello");
    });
}

该代码在主线程执行打印blk,并等待执行结束,而其实在主线程中还是在执行这一段代码,所以无法执行结束,也就是无法追加到主线程就会造成死锁,从而造成了无限的套娃行为。

9. dispatch_apply-GCD 快速迭代

dispatch_apply函数使dispatch_sync函数和Dispatch Group关联的API,它可以按照指定的次数将指定的blk追加到指定的Queue中,也可以理解成for循环的作用,并等待全部执行结束

  • dispatch_apply函数一般多用于并行队列,若是在串行队列使用其实for循环原理相似。
  • 参数:第一个是重复次数,第二个参数是追加对象的QUeue,第三个参数就是对追加参数的处理,整体结构和for循环很类似。
/**
 * 快速迭代方法 dispatch_apply
 */
- (void)apply {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    NSLog(@"apply---begin");
    dispatch_apply(6, queue, ^(size_t index) {
        NSLog(@"%zd---%@",index, [NSThread currentThread]);
    });
    NSLog(@"apply---end");
}

dispatch_apply 都会等待全部任务执行完毕,这点就像是同步操作与dispatch_sync函数相同,也像是队列组中的 dispatch_group_wait方法。
由此推断dispatch_async里是非同步的执行了dispatch_apply函数。

10. dispatch_suspend & dispatch_resume

这两个函数可以随时挂起某个队列,等需要执行的时候恢复即可。

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_suspend(queue);
    dispatch_resume(queue);

11.dispatch_semaphore-GCD 信号量

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

信号量的使用时机是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后使用信号量
Dispatch Semaphore 在实际开发中主要用于:

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

dispatch_semaphore方法

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

Dispatch Semaphore 使线程同步

/**
 * semaphore 线程同步
 */
- (void)semaphoreSync {
    
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"semaphore---begin");
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    __block int number = 0;
    dispatch_async(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        
        number = 100;
        
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore---end,number = %d",number);
}

理解:首先async是异步执行,我们要把异步变同步是需要添加一个等待的,也就是编译器

  • 1.在执行dispatch_async的时候 dispatch_semaphore_wait同时执行 此时信号量是-1
  • 2.当前线程阻塞,接着 dispatch_semaphore_signal(semaphore)信号量为0,线程继续进行,-
  • 3.number= 100.最后打印 semaphore—end,number = 100。

12.dispatch_once

我们在创建单例或者有整个程序运行过程中只执行一次的代码时,我们就用到了 GCD 的 dispatch_once 方法。使用 dispatch_once 方法能保证某段代码在程序运行过程中只被执行 1 次,并且即使在多线程的环境下,dispatch_once 也可以保证线程安全。

如此创建的单例模式只会被初始化一次。

先声明静态全局变量,只能在当前文件访问 ,单例对象的条件
#import "Person.h"
static id instance;
static id SingleEg;
// 懒汉
@implementation Person
+(id) instance {
    static dispatch_once_t o;
    dispatch_once(&o, ^{
        if (instance == nil) {
            instance = [[super alloc] init];
        }
    });
    return  instance;
}
// 饿汉
+ (id)_singleEg {
    static dispatch_once_t o;
    dispatch_once(&o, ^{
            SingleEg = [[super alloc] init];
    });
    return  SingleEg;
}

并且即使在多线程的环境下,dispatch_once 也可以保证线程安全。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

客户管理系统功能大揭秘!助您了解其核心作用!

早在2019年,中国已经快速迈向服务经济时代 客户管理一直是各行业管理者关心的话题,在数字化时代,如何做好客户管理?很多人说可以尝试客户管理系统,那么今天大家就一窥客户管理系统的庐山真面目。 一、什么是客户管理系…

复试编程练习题(from 牛客网)

考试时候可能没有VS,但codeblocks是必备的。 下面总结一些常用的快捷键。 CtrlEnter:光标移至行尾ShiftEnter:光标跳到下一行CtrlD:直接复制粘贴当前行CtrlL:删除当前行F9:buildrun。CtrlShiftC:注释掉当…

网络安全实训Day9

写在前面 访问控制和防火墙桌面端安全检测与防御 网络安全实训-网络安全技术 网络安全概述 访问控制 定义:通过定义策略和规则来限制哪些流量能经过防火墙,哪些流量不能通过。本质是包过滤 可以匹配的元素 IP协议版本 源区域和目的区域 源IP地址和目…

【C++那些事儿】C++模板编程入门:构建可重用组件的利器

📷 江池俊:个人主页 🔥 个人专栏:✅C那些事儿 ✅Linux技术宝典 🌅 此去关山万里,定不负云起之望 文章目录 1. 泛型编程2. 函数模板2.1 函数模板概念2.1 函数模板格式2.3 函数模板的原理2.4 函数模板的实…

基于python+vue成都旅游网flask-django-php-nodejs

本篇论文对成都旅游网的需求分析、功能设计、系统设计进行了较为详尽的阐述,并对系统的整体设计进行了阐述,并对各功能的实现和主要功能进行了说明,并附上了相应的操作界面图。 语言:Python 框架:django/flask 软件版本…

select , poll, epoll思维导图

目录 1. 总的框架结构 2. select 3. poll 4. epoll 1. 总的框架结构 2. select

【C语言】自定义类型

1、内存对齐(必考) 1.1结构体内存对齐 结构体的对齐规则: 1. 第一个成员在与结构体变量偏移量为0的地址处。 2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 编译器默认的一个对齐数 与 该成员大…

为什么算法渐进复杂度中对数的底数总为2

在分析各种算法时,经常看到(O(\log_2n))或(O(n\log_2n))这样的渐进复杂度。不知有没有同学困惑过,为什么算法的渐进复杂度中的对数都是以2为底?为什么没有见过(O(n\log_3n))这样的渐进复杂度?本文解释这个问题。 三分式归并排序的…

Obsidian+PicGo+Gitee搭建免费图床

之前使用PicGoGitee配合Typora,后来因为换电脑Typora管理笔记不方便,换到Obsidian笔记,此处记录重新搭建图床的坑与经验。 主要参考# picGogitee搭建Obsidian图床,实现高效写作! 1 下载安装PicGo 下载链接https://mo…

ASM四部曲之一:什么是ASM

文章目录 前言什么是.class文件什么是ASM概述作用域模型基于ASM的程序架构 ASM库结构 前言 本文翻译自ASM官方文档。 什么是.class文件 Java字节码文件(.class)是Java编译器编译Java源文件(.java)产生的目标文件。它是一种8位字…

【Linux】vim配置及安装方法

注 安装方法在文章最后 配置文件的位置 在目录 /etc/ 下面,有个名为vimrc的文件,这是系统中公共的vim配置文件,对所有用户都有效。而在每个用户的主目录下,都可以自己建立私有的配置文件,命名为“.vimrc”。例如&…

Docker搭建LNMP环境实战(03):VMware安装CentOS

Docker搭建LNMP环境实战(03):VMware安装CentOS 1、创建新的虚拟机,选择CentOS7镜像文件,并启动安装 启动VMware,创建新的虚拟机 图1 选择典型安装即可 选用最大最全的CentOS镜像文件:CentOS-7…

遥感原理与应用—绪论

一、关于基本概念与对应的英文 遥感:Remote Sensing 遥测:Telemetry,对被测物体某些运动参数和性质进行远距离测量的技术,分为接触测量与非接触测量,对于RS的概念,遥测探测的目标显得狭隘了一些&#xff…

Machine Learning - Logistic Regression

目录 一、Activation Function Why introduce activation functions? There are several commonly used activation functions: 二、Sigmoid: 三、Logistic Regression Model: 四、Implementation of logistic regression: 五、Decis…

GNU Radio创建Zadoff-Chu序列python OOT块

文章目录 前言一、ZC序列是什么?二、创建自定义的 OOT 块三、相关文件四、测试1、grc 图2、运行结果①、时域图②、时域幅值模图③、IQ 曲线 前言 本文实现在 GNU Radio 中创建 Zadoff-Chu 序列 python OOT 块,仅做代码调试记录。 一、ZC序列是什么&…

java 实现发送邮件功能

今天分享一篇 java 发送 QQ 邮件的功能 环境&#xff1a; jdk 1.8 springboot 2.6.3 maven 3.9.6 邮件功能依赖&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId>&…

C语言分支和循环

目录 一.分支 一.if 二.if else 三.if else嵌套 四.else if 五.switch语句 二.循环 一.while (do while&#xff09;break : 二.for函数&#xff1a; 三.goto语句: 四.猜数字: 一.分支 一.if if要条件为真才执行为假不执行而且if只能执行后面第一条如果要执行多条就…

Windows下同时安装多个版本的JDK并配置环境变量

说明&#xff1a;这里安装的JDK版本为1.8和17 JDK下载 官方地址: https://www.oracle.com/java/ 我这里下载的是exe安装包 安装这里就不阐述了&#xff0c;安装方法都是一样的。 系统环境变量配置 1、首先新建JDK1.8和17的JAVA_HOME&#xff0c;他们的变量名区分开&#xff…

MySQL进阶——索引

索引 索引概述 索引结构 索引分类 索引语法 SQL性能分析 索引使用 索引设计原则 概述 介绍 索引&#xff08;Index&#xff09;是帮助MySQL高效获取数据的数据结构&#xff08;有序&#xff09;。 在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结…

Redis 教程系列之缓存雪崩,击穿,穿透(十四)

一,缓存雪崩 当大量缓存数据在同一时间过期(失效)或者 Redis 故障宕机时,如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃,这就是缓存雪崩的问…
最新文章