同步和异步的区别
同步执行等待操作完成,而异步执行允许程序在操作完成前继续运行,提高了效率和响应性。这里的关键就是上一个操作需不需要等待当前操作的执行,如果需要就是同步,如果不需要就是异步。
异步有开启新线程的能力但不一定开启,同步没有开启新线程的能力。
两者的区别在于是否等待队列中的任务执行结束,是否具备开启新线程的能力
什么情况下使用串行队列,什么情况下使用并发队列
- 串行队列(Serial Dispatch Queue):等待现在执行中的处理,也就是每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
- 并发队列(Concurrent Dispatch Queue):不等待现在执行中的处理,也就是可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
并发功能只有在异步(dispatch_async)函数下才有效
串行队列用于确保任务按顺序执行,适用于任务间有依赖关系或需要独占访问共享资源的场景,而并发队列则允许多个任务同时执行,适合于能够并行处理的任务以提高性能和效率,如独立的计算或数据处理任务
GCD和NSOperation队列的区别
-
GCD中的队列有串行队列和并发队列,都是采用FIFO原则,也就是创建的新任务总是在队尾,执行的当前任务总是从队头开始。添加到队列中的任务不能取消或暂停。
-
NSOperationQueue有主队列和自定义队列。自定义队列有串行和并发两种,通过设置操作队列的最大并发操作数来实现。添加到队列中的操作可以取消或者暂停。
主队列是什么
主队列其实就是串行队列,只是在默认情况下当前代码放在主队列中,然后主队列中的代码,又都会放到主线程中去执行。
主队列的任务一定在主线程中进行,主线程中进行的任务不一定是主队列的任务还可能是其它队列的任务。
GCD的优先级
GCD中有四个优先级:分别是高优先级(High Priority)、默认优先级(Default Priority)、低优先级(Low Priority)和后台优先级(Background Priority)
如何理解GCD中的同步和异步操作
GCD中的同步操作:是指将block块中的任务添加到队列的队尾时需要等待队头的任务在线程上执行完并且移除后才能添加,这里边有一个等待队头任务完成的过程
GCD中的异步操作:是指将block块中的任务添加到队列的队尾时直接将所有任务全部依次添加到队尾,不用管队头的任务有没有在线程上执行完并移除,这里边就涉及到了不需要等待的意思。
如何理解GCD中的串行队列和并发队列
GCD中的串行队列和并发队列都遵循FIFO(先进先出)原则,也就是说先添加到队列中的任务先出去,后添加到队列中的任务后出去。
串行队列:
串行队列保证队列中的任务会按照它们被添加到队列中的顺序依次执行(先添加的任务会先开始执行,后添加的任务必须等待前面的任务完成才能开始)。
如何保证的呢?
当一个任务被添加到串行队列中时,无论是同步还是异步添加,GCD 都会将这个任务放入队列的末尾无非是要不要等待的过程。串行队列的调度器会从队列的头部取出任务放到线程上来执行,无论开启多少的新线程,它限制每次只执行一个任务,一旦当前任务完成,才会从队列中取出下一个任务开始执行。
并发队列:
并发队列有让队列中的任务在多个线程上同时进行的能力,这取决于是否开启新线程。
-
如果当前只开启了一个线程那么队列中的任务还是需要等待队头的任务在线程上执行完才能执行后面的任务,也就跟串行队列是一样的。
-
如果当前开启了多个线程比如说三个线程,那么队列会分配三个任务到这三个线程上同时执行,哪个线程先执行完就将队头的任务添加到哪个线程上。
如何理解任务的执行顺序
任务的执行顺序是任务被添加到队列中的顺序。如果是顺序执行的话就是任务按照队列的FIFO原则,先进的先执行,后进的后执行。如果是乱序的话就不是先进的先执行,后进的后执行。造成乱序的原因就是多线程中的并发执行。
队列+任务 组合方式
1.串行队列+同步执行
同步执行是是指将block块中的任务添加到队列的队尾时需要等待队头的任务在线程上执行完并且移除后才能添加。串行队列限制每次只执行一个任务,一旦当前任务完成,才会从队列中取出下一个任务开始执行。因此任务是顺序执行的。
2.串行队列+异步执行
异步执行是将所有任务依次添加到队列中,不会等待任务完成。并发队列有让队列中的任务在多个线程上同时进行的能力,并且在异步执行会开启新线程。所以任务是乱序执行的
3.并发队列+同步执行
同步执行时将block块中的任务添加到队列的队尾时需要等待队头的任务在线程上执行完并且移除后才能添加。并发队列有让队列中的任务在多个线程上同时进行的能力。所以这里关键要看是否开启新线程。因为是同步执行所以没有开启新线程,所以按顺序执行。
如果在同步执行之前,在该并发队列中异步执行了添加任务的操作的话,就会开启新线程。此时再同步执行的话,虽然添加任务这个操作需要等待线程上的任务执行完才能进行,但此时已经有多个线程,这样就不能保证任务的顺序。此时同步和异步执行的任务都是乱序的。
如果是同步执行之后再进行异步操作,虽然开启了新线程,但是同步执行的任务在开启新线程之前就已经全部执行完了,这个新线程只负责处理异步操作中的任务,也就是同步执行的还是顺序,异步执行的是乱序。
因此下面两段代码的运行结果是有区别的,第一段代码是不按照顺序执行,第二段代码按照顺序执行
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);dispatch_async(concurrentQueue, ^{NSLog(@"Task 1 started");sleep(0);NSLog(@"Task 1 completed");});dispatch_sync(concurrentQueue, ^{NSLog(@"Task 2 started");sleep(2);NSLog(@"Task 2 completed");});dispatch_sync(concurrentQueue, ^{NSLog(@"Task 3 started");sleep(2);NSLog(@"Task 3 completed");});
dispatch_sync(concurrentQueue, ^{NSLog(@"Task 1 started");sleep(0);NSLog(@"Task 1 completed");});dispatch_sync(concurrentQueue, ^{NSLog(@"Task 2 started");sleep(2);NSLog(@"Task 2 completed");});dispatch_async(concurrentQueue, ^{NSLog(@"Task 3 started");sleep(2);NSLog(@"Task 3 completed");});
4.并发队列+异步执行
异步执行是将所有任务依次添加到队列中,不会等待任务完成。并发队列有让队列中的任务在多个线程上同时进行的能力,并且在异步执行会开启新线程。所以任务是乱序执行的
5.主队列+同步执行
首先需要知道的是主队列的任务一定是在主线程上进行的。同步执行需要等待线程上的任务执行完之后才能将任务添加到队尾。将任务同步添加到队尾这个操作其实也是个任务,而这个任务一般是在主线程上进行。此时就形成了主队列中的任务需要等待将任务添加到队列的这个操作的任务执行完才能进行,而将任务添加到队列这个操作需要等待主线程上的任务执行完才能进行,形成了主队列和主线程相互等待的局面,造成了死锁。
如果是在 其他线程 调用 主队列+同步执行,则不会阻塞 主队列,也就不会造成死锁
6.主队列+异步执行
主队列的任务在主线程上进行。异步执行是不需要等待线程上的任务执行完就直接添加。当异步添加到队列的任务在主线程上执行完后就会执行队列里的任务。因此也不会造成死锁。因为主队列的本质是个串行队列,所以还是按顺序执行。
GCD的API
dispatch_set_target_queue(设置目标队列)
第一个参数为要变更优先级的队列,第二个参数为指定的队列所属的优先级(也就是第一个队列变换的优先级)
dispatch_set_target_queue(changedQueue, targetQueue);
还可以作为Dispatch Queue的执行阶层:如果在多个串行队列中用dispatch_set_target_queue函数指定目标为某一个串行队列,那么原本要并发执行的多个串行队列现在只能在指定的串行队列上处理,也就变成非并发处理。
dispatch_after(设置延迟执行)
实现在指定时间(例如 1 秒)之后执行某个任务,需要注意的是,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]); // 打印当前线程});
Dispatch Group(队列组)
当我们想分别并发执行多个耗时任务,然后这几个耗时任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组
先创建队列和队列组,将多个任务放到队列中再把队列放到队列组中,最后调用 dispatch_group_notify
回到指定线程执行任务。或者使用 dispatch_group_wait
回到当前线程继续向下执行(会阻塞当前线程)。
- (void)useDispatchGroup {//创建队列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_notify(group, queue, ^{NSLog(@"Done!");});
}
dispatch_group_notify
监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。
也就是当所有任务都执行完成之后,才执行 dispatch_group_notify 相关 block 中的任务。
第一个参数为队列组,第二个参数是要追加执行的任务
dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"add task");});
dispatch_group_wait
暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
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
中的任务。
dispatch_barrier_async(栅栏方法)
主要用来将并发队列中的多个任务分割成两组来执行,只有当第一组的任务执行完并且执行了dispatch_barrier_async
中追加的任务后才能执行第二组的任务。
这个函数传入的并发队列必须是自己通过dispatch_queue_cretate创建的 如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果
dispatch_once(限制一次方法)
使用 dispatch_once
方法能保证某段代码在程序运行过程中只被执行 1 次,并且即使在多线程的环境下,dispatch_once
也可以保证线程安全。
/*** 一次性代码(只执行一次)dispatch_once*/
- (void)once {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{// 只执行 1 次的代码(这里面默认是线程安全的)});
}
dispatch_apply(快速迭代方法)
按照指定的次数将指定的任务追加到指定的队列中,快速迭代一组任务,并等待全部队列执行结束。主要用于需要快速并行处理大量相同类型任务的场景,如图像处理、数据处理或任何形式的批处理任务。
在串行队列中无法体现出它的作用,因为队列中的任务始终是一次执行一个。
在并发队列中如果开启了多个线程的话就能在多个线程中同时展开执行,效率特别高。
void dispatch_apply(size_t iterations,dispatch_queue_t queue,dispatch_apply_iterate_t iterator
);
iterations
是你要执行的任务数量。queue
是你希望在其中执行任务的队列,通常是并发队列。iterator
是一个指向函数的指针,这个函数将为每个迭代执行一次。
用下面这段代码举个例子:
for (int i = 0; i < 100000; i++) {NSString *queueN = [NSString stringWithFormat:@"c%dqueue", i];dispatch_queue_t cQueue = dispatch_queue_create(queueN.UTF8String, DISPATCH_QUEUE_CONCURRENT);dispatch_async(cQueue, ^{NSLog(@"the queue = %s", dispatch_queue_get_label(cQueue));});
}
在上面代码中模拟了我们想要在多线程循环执行任务场景,但是突然创建很多线程或造成线程爆炸,可以使用dispatch_apply来实现,因为dispatch_apply的线程全部由GCD管理。从而避免了手动创建线程爆炸问题。
// 解决线程爆炸方案
dispatch_queue_t queue = dispatch_queue_create("xxQueue", DISPATCH_QUEUE_CONCURRENT);dispatch_apply(100000, queue, ^(size_t t) {NSLog(@"the current thread = %@, t = %ld", NSThread.currentThread, t);
});
dispatch_suspend & dispatch_resume(暂停和恢复)
dispatch_suspend函数可以随时暂停某个队列,等需要执行的时候使用dispatch_resume恢复即可。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);dispatch_suspend(queue);dispatch_resume(queue);
dispatch_semaphore(信号量)
- emaphore叫做”信号量”
- 信号量的初始值,可以用来控制线程并发访问的最大数量
- 信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步
//表示最多访问5个线程
dispatch_semaphore_create(5);
// 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
// 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
// 让信号量的值+1
dispatch_semaphore_signal(self.semaphore);
主要控制的是能够访问共享资源或执行临界区的线程数量,而不是直接控制线程开启的数量。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); // 创建一个初始值为1的信号量dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);dispatch_async(concurrentQueue, ^{dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // 等待信号量,获得资源访问权限NSLog(@"Task 1: Accessing shared resource");// 执行对共享资源的访问dispatch_semaphore_signal(semaphore); // 释放资源访问权限
});dispatch_async(concurrentQueue, ^{dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // 等待信号量,获得资源访问权限NSLog(@"Task 2: Accessing shared resource");// 执行对共享资源的访问dispatch_semaphore_signal(semaphore); // 释放资源访问权限
});// 为了确保主线程等待所有任务完成,可以使用 dispatch_sync 或 dispatch_group
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(concurrentQueue, ^{dispatch_group_leave(group);
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
什么场景下会出现死锁?
有三种场景会出现死锁:
- 主队列+同步执行
- 异步串行队列嵌套同步串行队列
- 信号量阻塞主线程
场景一:同步+主队列[self task1];
dispatch_sync(dispatch_get_main_queue(), ^{[self task2];NSLog(@"同步线程执行主队列任务");
});
[self task3];(1)执行task1
(2)阻塞同步线程,把task2加入到主队列的队尾
(3)task3需要等待task2执行完成后执行,但是此时task2又排在task3后面,所以造成了死锁场景二:异步串行队列嵌套同步串行队列dispatch_queue_t myQueue = dispatch_queue_create("com.bg.sQueue", DISPATCH_QUEUE_SERIAL);
[self task1];
dispatch_async(myQueue, ^{[self task2];dispatch_sync(myQueue, ^{[self task3];});[self task4];
});
[self task5];(1)执行task1
(2)执行task5
(3)执行task2
(4)阻塞同步线程,把task3加入到队列myQueue的队尾
(5)task4需要等待task3执行完成后执行,但是此时task3又排在task4后面,所以造成了死锁场景三:信号量阻塞主线程dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_main_queue(), ^{dispatch_semaphore_signal(sem);NSLog(@"the sem +1");
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"the sem -1");主线程中dispatch_semaphore_wait一直等着dispatch_semaphore_signal改变信号量(+1操作),但是dispatch_semaphore_wait却阻塞了主线程导致dispatch_semaphore_signal无法执行,从而造成了死锁。
GCD的任务能取消吗?
GCD本身没有提供取消任务的API,但是可以通过操作来间接设置取消任务比如:
-
在 block 内部添加一个布尔变量作为取消标志,然后在外部改变这个标志的值。block 在执行时可以检查这个标志是否被设置为取消状态,如果被设置,则提前结束 block 的执行。
-
DispatchSource
可以用来监控文件描述符、定时器等事件,但也可以用于模拟任务取消。可以创建一个DispatchSourceTimer
,并在定时器触发时检查是否应该取消任务。
如何用GCD实现任务间的依赖?
实现任务间的依赖通常涉及到确保一个任务在另一个任务完成之后才开始执行。
-
因此可以使用串行队列来解决。因为串行队列保证任务按顺序执行。当你向串行队列中添加任务时,GCD 会确保前一个任务完成之后才开始执行下一个任务
-
在并发队列中,还可以使用
dispatch_barrier_async
来确保在执行屏障任务之前,所有之前提交到队列的任务都已经完成。这可以用来实现任务间的依赖。
如何使用GCD来实现控制最大并发数?
可以使用**dispatch_semaphore
**
首先,创建一个信号量,其初始值等于你想要的最大并发数。每当开始一个新任务时,尝试减少信号量的值。如果信号量的值为0,新的任务将被阻塞,直到有任务完成并释放信号量。
如何实现一个常驻线程?(线程保活)
- 使用无限循环
在一个线程中使用无限循环(如 while
循环)来持续执行任务或监听事件。为了防止线程占用过多的 CPU 资源,可以在循环中加入适当的延时。
- 使用
dispatch_source_t
dispatch_source_t
是 GCD 中一种可以用于监听事件的对象,比如定时器、文件描述符等。可以创建一个 dispatch_source_t
定时器,让它定期触发并执行任务。
- 使用NSThread
使用NSThread创建线程并在其中执行无限循环或监听事件。
怎样利用GCD实现多读单写呢(怎么实现一个多读单写的模型)?
通过栅栏异步调用的方式,分配写操作到并发队列当中。