当前位置: 首页 > news >正文

iOS——GCD再学习

GCD

使用GCD好处,具体如下:

  • GCD 可用于多核的并行运算;
  • GCD 会自动利用更多的 CPU 内核(比如双核、四核);
  • GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
  • 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。

任务与队列

概念

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

  • 同步执行(sync):
    同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
    只能在当前线程中执行任务,不具备开启新线程的能力。

  • 异步执行(async):
    异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
    可以在新的线程中执行任务,具备开启新线程的能力。

注意:异步执行(async)虽然具有开启新线程的能力,但是并不一定开启新线程。这跟任务所指定的队列类型有关。
队列(Dispatch Queue):这里的队列指执行任务的等待队列,即用来存放任务的队列。
队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。

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

串行队列和并发队列

在 GCD 中有两种队列:串行队列 和 并发队列。
两者都符合 FIFO(先进先出)的原则。
两者的主要区别是:执行顺序不同,以及开启线程数不同。

  • 串行队列(Serial Dispatch Queue):
    每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)

  • 并发队列(Concurrent Dispatch Queue):
    可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)

DISPATCH_QUEUE_SERIAL 表示串行队列。
DISPATCH_QUEUE_CONCURRENT 表示并发队列。

并发队列 的并发功能只有在异步(dispatch_async)方法下才有效。

主队列

主队列是一种特殊的 串行队列,在libdispatch_init 初始化时就创建了主队列,并且完成了与主线程的绑定。这些都是在程序main()函数之前就已完成的。
也就是说程序完成启动之时就已经有了主队列,并且所有放在主队列中的任务都是在主线程中执行的。==不管是同步还是异步都不会开辟新线程,任务只会在主线程执行。==这也是通常在主线程刷新UI时会将任务放到主队列的原因。
可通过dispatch_get_main_queue()获取主队列。

全局并发队列

全局并发队列 本质上是一个并发队列,由系统提供,方便编程,不用创建就可使用。
可通过dispatch_get_global_queue(long indentifier.unsigned long flags)获取全局并发队列。
该函数提供了两个参数,第一个参数表示队列优先级,通常写0,也就是默认优先级。可以通过服务质量类值来获取不同优先级的全局并发队列。

六种组合方式

请添加图片描述

同步执行+并发队列

有如下代码:

- (void)syncConcurrent {//打印syncConcurrent最开始执行的线程NSLog(@"%@", [NSThread currentThread]);//使用CONCURRENT并发队列dispatch_queue_t queue = dispatch_queue_create("elevenTest.queue", DISPATCH_QUEUE_CONCURRENT);dispatch_sync(queue, ^{[NSThread sleepForTimeInterval:2]; // 模拟耗时操作NSLog(@"1--%@", [NSThread currentThread]);});dispatch_sync(queue, ^{[NSThread sleepForTimeInterval:2]; // 模拟耗时操作NSLog(@"2--%@", [NSThread currentThread]);});dispatch_sync(queue, ^{[NSThread sleepForTimeInterval:2]; // 模拟耗时操作NSLog(@"3--%@", [NSThread currentThread]);});
}

执行结果:
请添加图片描述
所有任务都是在当前线程(主线程)中执行的,没有开启新的线程(同步执行不具备开启新线程的能力)。
按顺序执行的原因:虽然 并发队列 可以开启多个线程,并且同时执行多个任务。
但是因为本身不能创建新线程,只有当前线程这一个线程(同步任务 不具备开启新线程的能力),所以也就不存在并发。而且当前线程只有等待当前队列中正在执行的任务执行完毕之后,才能继续接着执行下面的操作(同步任务 需要等待队列的任务执行结束)。
所以任务只能一个接一个按顺序执行,不能同时被执行。

同步执行+串行队列

- (void)syncConcurrent {//打印syncConcurrent最开始执行的线程NSLog(@"%@", [NSThread currentThread]);//使用SERIAL串行队列dispatch_queue_t queue = dispatch_queue_create("elevenTest.queue", DISPATCH_QUEUE_SERIAL);dispatch_sync(queue, ^{[NSThread sleepForTimeInterval:2]; // 模拟耗时操作NSLog(@"1--%@", [NSThread currentThread]);});dispatch_sync(queue, ^{[NSThread sleepForTimeInterval:2]; // 模拟耗时操作NSLog(@"2--%@", [NSThread currentThread]);});dispatch_sync(queue, ^{[NSThread sleepForTimeInterval:2]; // 模拟耗时操作NSLog(@"3--%@", [NSThread currentThread]);});
}

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

所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(同步执行 不具备开启新线程的能力)。

任务是按顺序执行的(串行队列 每次只有一个任务被执行,任务一个接一个按顺序执行)。

异步执行+串行队列

- (void)syncConcurrent {//打印syncConcurrent最开始执行的线程NSLog(@"%@", [NSThread currentThread]);NSLog(@"start");dispatch_queue_t queue = dispatch_queue_create("elevenTest.queue1", DISPATCH_QUEUE_SERIAL);dispatch_async(queue, ^{[NSThread sleepForTimeInterval:2]; // 模拟耗时操作NSLog(@"1--%@", [NSThread currentThread]);});dispatch_async(queue, ^{[NSThread sleepForTimeInterval:2]; // 模拟耗时操作NSLog(@"2--%@", [NSThread currentThread]);});dispatch_async(queue, ^{[NSThread sleepForTimeInterval:2]; // 模拟耗时操作NSLog(@"3--%@", [NSThread currentThread]);});NSLog(@"end");
}

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

开启了一条新线程(异步执行 具备开启新线程的能力,串行队列 只开启一个线程)。

任务是按顺序执行的(串行队列 每次只有一个任务被执行,任务一个接一个按顺序执行)。

异步执行+并发队列

- (void)syncConcurrent {//打印syncConcurrent最开始执行的线程NSLog(@"%@", [NSThread currentThread]);NSLog(@"start");dispatch_queue_t queue = dispatch_queue_create("elevenTest.queue1", DISPATCH_QUEUE_CONCURRENT);dispatch_async(queue, ^{[NSThread sleepForTimeInterval:2]; // 模拟耗时操作NSLog(@"1--%@", [NSThread currentThread]);});dispatch_async(queue, ^{[NSThread sleepForTimeInterval:2]; // 模拟耗时操作NSLog(@"2--%@", [NSThread currentThread]);});dispatch_async(queue, ^{[NSThread sleepForTimeInterval:2]; // 模拟耗时操作NSLog(@"3--%@", [NSThread currentThread]);});NSLog(@"end");
}

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

除了当前线程(主线程),系统又开启了 3 个线程,并且任务是交替/同时执行的。(异步执行 具备开启新线程的能力。且 并发队列 可开启多个线程,同时执行多个任务)。
说明当前线程没有等待,而是直接开启了新线程,在新线程中执行任务(异步执行 不做等待,可以继续执行任务)。

同步执行+主队列

- (void)syncConcurrent {//打印syncConcurrent最开始执行的线程NSLog(@"%@", [NSThread currentThread]);NSLog(@"start");dispatch_queue_t queue = dispatch_get_main_queue();dispatch_sync(queue, ^{[NSThread sleepForTimeInterval:2]; // 模拟耗时操作NSLog(@"1--%@", [NSThread currentThread]);});dispatch_sync(queue, ^{[NSThread sleepForTimeInterval:2]; // 模拟耗时操作NSLog(@"2--%@", [NSThread currentThread]);});dispatch_sync(queue, ^{[NSThread sleepForTimeInterval:2]; // 模拟耗时操作NSLog(@"3--%@", [NSThread currentThread]);});NSLog(@"end");
}

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

同步执行 + 主队列在主线程中调用会发生死锁问题,而在其他线程中调用则不会。
这是因为我们在主线程中执行 syncConcurrent 方法,相当于把 syncConcurrent 任务放到了主线程的队列中。而 同步执行 会等待当前队列中的任务执行完毕,才会接着执行。那么当我们把 任务 1 追加到主队列中,任务 1 就在等待主线程处理完 syncConcurrent 任务。而syncConcurrent 任务需要等待 任务 1 执行完毕,才能接着执行。
那么,现在的情况就是 syncConcurrent 任务和 任务 1 都在等对方执行完毕。这样大家互相等待,所以就卡住了,所以我们的任务执行不了,而且 send 也没有打印。

为什么放到其他线程中就不会卡住了呢?
因为当任务放到了其他线程里,而 任务 1、任务 2、任务3 都在追加到主队列中,这三个任务都会在主线程中执行。
syncConcurrent任务在其他线程中执行到追加 任务 1 到主队列中,因为主队列现在没有正在执行的任务,所以,会直接执行主队列的 任务1,等 任务1 执行完毕,再接着执行 任务 2、任务 3。所以这里不会卡住线程,也就不会造成死锁问题。

异步执行+主队列

/*** 异步执行 + 主队列* 特点:只在主线程中执行任务,执行完一个任务,再执行下一个任务*/
- (void)asyncMain {NSLog(@"currentThread---%@",[NSThread currentThread]);  NSLog(@"asyncMain---begin");dispatch_queue_t queue = dispatch_get_main_queue();dispatch_async(queue, ^{// 追加任务 1[NSThread sleepForTimeInterval:2]; // 模拟耗时操作NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程});dispatch_async(queue, ^{// 追加任务 2[NSThread sleepForTimeInterval:2];// 模拟耗时操作NSLog(@"2---%@",[NSThread currentThread]);// 打印当前线程});dispatch_async(queue, ^{// 追加任务 3[NSThread sleepForTimeInterval:2]; // 模拟耗时操作NSLog(@"3---%@",[NSThread currentThread]);// 打印当前线程});NSLog(@"asyncMain---end");
}打印:
[17521:4243690] currentThread—-{number = 1, name = main}
[17521:4243690] asyncMain—-begin
[17521:4243690] asyncMain—-end
[17521:4243690] 1-{number = 1, name = main}
[17521:4243690] 2-{number = 1, name = main}
[17521:4243690] 3-{number = 1, name = main}

所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(虽然 异步执行 具备开启线程的能力,但因为是主队列,所以所有任务都在主线程中)。

任务是按顺序执行的(因为主队列是 串行队列,每次只有一个任务被执行,任务一个接一个按顺序执行)。

为什么一定要在主线程更新UI呢?
首先,UIKit不是线程安全的,当多个线程同时操作UI时,抢夺资源,有可能导致崩溃,UI异常等问题。假如在两个线程中设置了同一张背景图片,很有可能就会由于背景图片被释放两次,使得程序崩溃。或者某一个线程中遍历找寻某个subView,然而在另一个线程中删除了该subView,那么就会造成错乱。

子线程到底能不能更新UI呢?
有时也可以,但是会有问题。在子线程能更新的UI是一个假象,其实是子线程代码执行完毕了,又自动进入到了主线程,执行了子线程中的UI更新的函数栈,这中间的时间非常的短,就让大家误以为分线程可以更新UI。如果子线程一直在运行,则子线程中的UI更新的函数栈,主线程就无法获知,那就无法更新直到子线程结束。

GCD的一些函数

栅栏函数:dispatch_barrier_async/sync

dispatch_barrier_async方法:

  1. 在调用 dispatch_barrier_async 之前,所有被添加到并发队列中的任务都会并发执行。
  2. dispatch_barrier_async添加的任务开始执行时,会等待之前所有添加到队列中的任务执行完毕。
  3. dispatch_barrier_async中添加的任务独占地执行,确保在执行任务时,没有其他任务在执行。
  4. 栅栏中的任务执行完毕后,队列恢复正常的并发行为,后续添加的任务可以并发执行。

有如下代码:

- (void)syncConcurrent {//打印syncConcurrent最开始执行的线程NSLog(@"%@", [NSThread currentThread]);NSLog(@"start");dispatch_queue_t queue = dispatch_queue_create("eleven", DISPATCH_QUEUE_CONCURRENT);dispatch_async(queue, ^{[NSThread sleepForTimeInterval:2]; // 模拟耗时操作NSLog(@"1--%@", [NSThread currentThread]);});dispatch_async(queue, ^{[NSThread sleepForTimeInterval:2]; // 模拟耗时操作NSLog(@"2--%@", [NSThread currentThread]);});dispatch_barrier_async(queue, ^{[NSThread sleepForTimeInterval:2];NSLog(@"barrier--%@", [NSThread currentThread]);NSLog(@"barrier start");sleep(5);NSLog(@"barrier end");});dispatch_async(queue, ^{[NSThread sleepForTimeInterval:2]; // 模拟耗时操作NSLog(@"3--%@", [NSThread currentThread]);});dispatch_async(queue, ^{[NSThread sleepForTimeInterval:2]; // 模拟耗时操作NSLog(@"4--%@", [NSThread currentThread]);});NSLog(@"end");
}

执行结果:
请添加图片描述

dispatch_barrier_async:不会阻塞调用它的线程,屏障任务在并发队列中独占执行,但调用它的线程可以继续执行其他任务。对于自定义的并发队列,会按照上面的顺序执行,但是如果是系统的全局并发队列dispatch_queue_t queue = dispatch_get_global_queue(0, 0);,就不是这个顺序了,他们四个任务的顺序是不确定的;

dispatch_barrier_sync:会阻塞调用它的线程,直到屏障任务完成,调用线程才会继续执行。

GCD 延时执行方法:dispatch_after

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{// 2.0 秒后异步追加任务代码到主队列,并开始执行NSLog(@"after---%@",[NSThread currentThread]);  });

GCD 一次性代码(只执行一次):dispatch_once

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

- (void)once {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{// 只执行 1 次的代码(这里面默认是线程安全的)});
}

GCD 快速迭代方法:dispatch_apply

通常我们会用 for 循环遍历,但是 GCD 给我们提供了快速迭代的方法 dispatch_apply
dispatch_apply 按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。
如果是在串行队列中使用 dispatch_apply,那么就和 for 循环一样,按顺序同步执行。但是这样就体现不出快速迭代的意义了。
我们可以利用并发队列进行异步执行。比如说遍历 0~5 这 6 个数字,for 循环的做法是每次取出一个元素,逐个遍历。dispatch_apply 可以 在多个线程中同时(异步)遍历多个数字。
还有一点,无论是在串行队列,还是并发队列中,dispatch_apply 都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的 dispatch_group_wait方法。

- (void)syncConcurrent {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");}

执行结果:
请添加图片描述

GCD 队列组:dispatch_group

会有这样的需求:分别异步执行2个耗时任务,然后当2个耗时任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组。

调用队列组的 dispatch_group_async先把任务放到队列中,然后将队列放入队列组中。或者使用队列组的dispatch_group_enterdispatch_group_leave组合来实现 dispatch_group_async

调用队列组的dispatch_group_notify回到指定线程执行任务。或者使用dispatch_group_wait回到当前线程继续向下执行(会阻塞当前线程)。

dispatch_group_notify

监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务

- (void)viewDidLoad {[super viewDidLoad];//打印syncConcurrent最开始执行的线程NSLog(@"%@", [NSThread currentThread]);NSLog(@"start");dispatch_group_t group = dispatch_group_create();dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{[NSThread sleepForTimeInterval:2];NSLog(@"1--%@", [NSThread currentThread]);});dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{[NSThread sleepForTimeInterval:2];NSLog(@"2--%@", [NSThread currentThread]);});dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{[NSThread sleepForTimeInterval:2];NSLog(@"3--%@", [NSThread currentThread]);});dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{[NSThread sleepForTimeInterval:2];NSLog(@"4--%@", [NSThread currentThread]);});dispatch_group_notify(group, dispatch_get_main_queue(), ^{[NSThread sleepForTimeInterval:2];NSLog(@"5--%@", [NSThread currentThread]);});dispatch_group_wait(group, DISPATCH_TIME_FOREVER);NSLog(@"end");
}

执行结果:
请添加图片描述

dispatch_group_wait

暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。
当所有任务执行完成之后,才执行 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 中的任务。

- (void)viewDidLoad {[super viewDidLoad];NSLog(@"currentThread---%@",[NSThread currentThread]);NSLog(@"group---begin");dispatch_group_t group = dispatch_group_create();dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);dispatch_group_enter(group);dispatch_async(queue, ^{// 追加任务 1[NSThread sleepForTimeInterval:2];  // 模拟耗时操作NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程dispatch_group_leave(group);});dispatch_group_enter(group);dispatch_async(queue, ^{// 追加任务 2[NSThread sleepForTimeInterval:2]; // 模拟耗时操作NSLog(@"2---%@",[NSThread currentThread]);// 打印当前线程dispatch_group_leave(group);});dispatch_group_notify(group, dispatch_get_main_queue(), ^{// 等前面的异步操作都执行完毕后,回到主线程.[NSThread sleepForTimeInterval:2];   // 模拟耗时操作NSLog(@"3---%@",[NSThread currentThread]);// 打印当前线程NSLog(@"group---end");});
}

这里的dispatch_group_enterdispatch_group_leave 组合,其实等同于dispatch_group_async

GCD 信号量:dispatch_semaphore

GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号。

在 Dispatch Semaphore 中,使用计数来完成这个功能,计数小于 0 时等待,不可通过。计数为 0 或大于 0 时,计数减 1 且不等待,可通过。

Dispatch Semaphore 提供了三个方法:

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

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

  • Dispatch Semaphore 在实际开发中主要用于:
  1. 保持线程同步,将异步执行任务转换为同步执行任务
  2. 保证线程安全,为线程加锁

但是使用信号量可能会造成线程优先级反转,且无法避免。

有如下代码:

- (void)viewDidLoad {[super viewDidLoad];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);}

执行结果:
请添加图片描述

执行顺序如下:

  1. semaphore 初始创建时计数为 0。
  2. 异步执行 将 任务 1 追加到队列之后,不做等待,接着执行 dispatch_semaphore_wait 方法,semaphore 减 1,此时 semaphore == -1,当前线程进入等待状态。
  3. 然后,异步任务 1 开始执行。任务 1 执行到 dispatch_semaphore_signal 之后,总信号量加 1,此时 semaphore == 0,正在被阻塞的线程(主线程)恢复继续执行。
  4. 最后打印 semaphore—end,number = 100。

线程同步:可理解为线程 A 和 线程 B 一块配合,A 执行到一定程度时要依靠线程 B 的某个结果,于是停下来,示意 B 运行;B 依言执行,再将结果给 A;A 再继续操作。

线程池

线程池流程图如下:

请添加图片描述

线程池(Thread Pool)是一种多线程管理机制,用于提高应用程序的性能和资源利用率。线程池通过预先创建一定数量的线程,并在需要执行任务时重复使用这些线程,而不是每次都创建和销毁线程,从而减少线程创建和销毁的开销。

通常使用GCD(Grand Central Dispatch)和NSOperationQueue来实现线程池的功能。

死锁问题

场景一:

- (void)viewDidLoad {[super viewDidLoad];[self test1];
}- (void)test1 {NSLog(@"任务1------");dispatch_queue_t queue = dispatch_get_main_queue();dispatch_sync(queue, ^{NSLog(@"任务2------");});NSLog(@"任务3--------");
}

主队列+同步执行:主队列是串行队列,任务test1在主队列中,在test1中同步执行了任务2。同步执行的队列中,当前任务执行完之前会阻塞线程。而主线程每次只能执行一个任务,因此test1任务要等待任务2执行完才能完成执行,而任务2要等待test1执行完才能执行,因此两个任务互相卡住了,发生了死锁。

场景二:

- (void)test2 {NSLog(@"任务1------");// 创建一个串行队列dispatch_queue_t queue = dispatch_queue_create("com.longchi", DISPATCH_QUEUE_SERIAL);dispatch_async(queue, ^{  // 异步任务NSLog(@"任务2------");dispatch_sync(queue, ^{NSLog(@"任务3------");});NSLog(@"任务4--------");});NSLog(@"任务5--------");
}

在这段代码中,queue是一个串行队列。向其中异步执行一个任务,因为异步不会阻塞当前线程,因此任务1和5都可以执行。在异步方法内又同步执行了一个方法,又因为同步执行中的方法执行完成前会阻塞当前线程,因此任务3执行完成前会一直阻塞线程,但是这个同步方法是处于串行队列的,因此要执行任务3要等待任务4执行完,但是任务4又要等任务3执行完才能执行,因此发生了死锁,跟那个主队列+同步执行差不多。

相关文章:

iOS——GCD再学习

GCD 使用GCD好处,具体如下: GCD 可用于多核的并行运算;GCD 会自动利用更多的 CPU 内核(比如双核、四核);GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)&#xff1b…...

SVD降维

文章目录 一、SVD降维的基本原理二、SVD降维的步骤三、SVD降维的优点四、SVD降维的应用五、代码应用六、SVD降维的局限性 一、SVD降维的基本原理 SVD是线性代数中的一种技术,它将一个矩阵A分解为三个矩阵的乘积:A UΣV^T。其中,U和V是正交矩…...

剖析Cookie的工作原理及其安全风险

Cookie的工作原理主要涉及到HTTP协议中的状态管理。HTTP协议本身是无状态的,这意味着每次请求都是独立的,服务器不会保留之前的请求信息。为了在无状态的HTTP协议上实现有状态的会话,引入了Cookie机制。 1. Cookie定义 Cookie,也…...

规控面试复盘

目录 前言 一、京东方 1、CPP和C的区别是什么? 2、讲一下的ROS的话题通信 二、Momenta(泊车部门实习面试) 1、MPC的预测时间步是多少? 2、MPC的代价函数考虑的是什么? 三、九识 1、智能指针有哪些优缺点? 优点: 缺点: 2、Protobuf的数据传输效率为什么更高…...

Elastic Stack--ES集群加密及Kibana的RBAC实战

前言:本博客仅作记录学习使用,部分图片出自网络,如有侵犯您的权益,请联系删除 学习B站博主教程笔记: 最新版适合自学的ElasticStack全套视频(Elk零基础入门到精通教程)Linux运维必备—Elastic…...

【开源免费】基于SpringBoot+Vue.JS图书个性化推荐系统(JAVA毕业设计)

本文项目编号 T 015 ,文末自助获取源码 \color{red}{T015,文末自助获取源码} T015,文末自助获取源码 目录 一、系统介绍1.1 业务分析1.2 用例设计1.3 时序设计 二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究…...

STM32(F103ZET6)第十九课:FreeRtos的移植和使用

目录 需求一、FreeRtos简介二、移植FreeRtos1.复制代码2.内存空间分配和内核相关接口3.FreeRtosConfig4.添加到工程中三、任务块操作1.任务四种状态2.创建任务过程 需求 1.将FreeRtos(嵌入式实时操作系统)移植到STM32中。 2.在该系统中实现任务的创建、…...

索尼的Web3蓝图:从技术创新到现实应用的全方位布局

近年来,随着区块链技术和加密资产的迅猛发展,全球科技巨头纷纷投入其中,力图在Web3浪潮中占据一席之地。作为传统科技行业的巨头,索尼(Sony)也不甘落后,积极推动其Web3战略布局,展现出其在新兴领域的强烈野…...

探索Java中的分布式消息队列与事件总线:架构、实现与最佳实践

引言 在现代分布式系统中,消息队列和事件总线已经成为实现松耦合、高扩展性和高可用性架构的关键组件。无论是微服务架构、事件驱动架构,还是实时数据处理,消息队列和事件总线都扮演着至关重要的角色。本文将深入探讨Java中的分布式消息队列…...

HTML零基础教程(超详细)

一、什么是HTML HTML,全称超文本标记语言(HyperText Markup Language),是一种用于创建网页的标准标记语言。它通过一系列标签来定义网页的结构、内容和格式。HTML文档是由HTML元素构成的文本文件,这些元素包括标题、段…...

011.Python爬虫系列_bs4解析

我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈 入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈 虚 拟 环 境 搭 建 :👉👉 Python项目虚拟环境(超详细讲解) 👈👈 PyQt5 系 列 教 程:👉👉 Python GUI(PyQt5)文章合集 👈👈 Oracle数…...

django摄影竞赛小程序论文源码调试讲解

2系统关键技术及工具简介 系统开发过程中设计的关键技术是系统的核心,而开发工具则会影响的项目开发的进程和效率。第二部分便描述了系统的设计与实现等相关开发工具。 2.1 Python简介 Python 属于一个高层次的脚本语言,以解释性,编译性&am…...

Unity-OpenCV-Imgproc函数概览

OpenCV-Imgproc函数概览 函数名功能描述createLineSegmentDetector创建一个智能指针到 LineSegmentDetector 对象并初始化它。此算法用于检测图像中的线段。getGaussianKernel返回高斯滤波器的系数。这些系数用于平滑图像或进行高斯模糊。getDerivKernels返回计算图像空间导数的…...

水晶连连看 - 无限版软件操作说明书

水晶连连看 – 无限版游戏软件使用说明书 文章目录 水晶连连看 – 无限版游戏软件使用说明书1 引言1.1 编写目的1.2 项目名称1.3 项目背景1.4 项目开发环境 2 概述2.1 目标2.2 功能2.3 性能 3 运行环境3.1 硬件3.2 软件 4 使用说明4.1 游戏开始界面4.2 游戏设定4.2.1 游戏帮助4…...

目标检测-YOLOv3

YOLOv3介绍 YOLOv3 (You Only Look Once, Version 3) 是 YOLO 系列目标检测模型的第三个版本,相较于 YOLOv2 有了显著的改进和增强,尤其在检测速度和精度上表现优异。YOLOv3 的设计目标是在保持高速的前提下提升检测的准确性和稳定性。下面是对 YOLOv3 …...

vscode好用的快捷键整理~

vscode好用的快捷键 将当前行复制并插入到上一行 shift alt ↑将当前行复制并插入到上一行 shift alt ↓将光标复制到上一行 ctrl alt ↑将光标复制到下一行 ctrl alt ↓删除当前行 ctrl x 本身是剪切当前行,也可以作为删除当前行来用选中下一个相同的片段…...

Docker in Docker 实践 on mac

在尝试tekton构建ci pipeline是,需要在k8 pod里build image,于是研究了如何docker in docker。 1. 编写自己的dind docker image FROM docker:20.10.16-dind ENV DOCKER_HOST unix:///var/run/docker.sock 2. docker build 自己的dind docker image并…...

Flask-Session扩展,使用Redis存储会话数据

深入理解Flask-session扩展Redis Flask 应用中使用 flask-session 扩展将 session 数据存储在 Redis 中是一种高效且可扩展的方法,特别是在需要处理大量用户或需要分布式部署的应用中。以下是如何在 Flask 应用中配置 flask-session 以使用 Redis 存储 session 的步…...

urdf ( xacro ) 的 collision碰撞参数设置

目录 写在前面的话整体流程1 URDF 文件结构2 查看原始碰撞形状描述3 加入简单碰撞形状描述方法一 Meshlab 自动测量方法二 人为测量 4 加入XACRO函数简化描述 最终结果展示侧视图正视图碰撞几何体中心点设置不对出现的结果 写在前面的话 本文使用的 URDF 文件是由 solidworks …...

iOS——方法交换Method Swizzing

什么是方法交换 Method Swizzing是发生在运行时的,主要用于在运行时将两个Method进行交换,我们可以将Method Swizzling代码写到任何地方,但是只有在这段Method Swilzzling代码执行完毕之后互换才起作用。 利用Objective-C Runtimee的动态绑定…...

在软件开发中正确使用MySQL日期时间类型的深度解析

在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...

<6>-MySQL表的增删查改

目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表&#xf…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》

在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中&#xff0…...

家政维修平台实战20:权限设计

目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色&#xf…...

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...

爬虫基础学习day2

# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容

目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...

Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?

在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...

使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度

文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...