为啥要并发
- 让多个任务同时执行
- 提高app运行的性能,保证App实时响应
1因为UI界面运行在主线程之上,它是一个串行线程。如果将所有代码都放在主线程上运行,那么主线程将承担网络请求,数据处理,图像渲染等操作,无论是GPU还是计算机内存,都会性能耗尽,从而影响用户体验。
线程和进程的区别
同步和异步,串行和并行。多任务和阻塞
异步:异步和同步是相对的,同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行。异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。线程就是实现异步的一个方式。异步是让调用方法的主线程不需要同步等待另一线程的完成,从而可以让主线程干其它的事情。
异步和多线程并不是一个同等关系,异步是最终目的,多线程只是我们实现异步的一种手段。异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回而可以做其它的事情。实现异步可以采用多线程技术或则交给另外的进程来处理。
Sync Async Serial Concurrent
- Serial/Concurrent声明队列的属性是串行的还是并行的。串行队列(Serial Queue)指在同一时间内,队列中只能执行一个任务,当前任务执行完后才能执行下一个任务。
在串行队列中只有一个线程。
并行队列(Concurrent Queue)允许多个任务在同一个时间同时进行,在并行队列中有多个线程。串行队列的任务一定是按开始的顺序结束。而并行队列的任务并不一定会按照开始的顺序结束。
任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。执行任务有两种方式:『同步执行』 和 『异步执行』。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
同步执行(sync):
同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
只能在当前线程中执行任务,不具备开启新线程的能力。
异步执行(async):
异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
可以在新的线程中执行任务,具备开启新线程的能力。
串行队列(Serial Dispatch Queue):
每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
并发队列(Concurrent Dispatch Queue):
可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
注意:并发队列 的并发功能只有在异步(dispatch_async)方法下才有效。
- Sync/Async声明任务是同步还是异步执行的。
同步(Sync)会把当前的任务加到队列中,等到任务执行完成,线程才会返回继续运行。
也就是说,同步会阻塞线程。
异步(Async)也会把当前的任务添加到队列中,但它会立刻返回,无须等任务执行完成,也就是说异步不会阻塞线程。
无论是串行队列还是并行队列,都可以执行异步或者同步操作。
注意,在串行队列中执行同步操作容易造成死锁。
在并行队列中则不用担心这个问题。异步操作无论是在串行队列中执行还是在并行队列中执行,都可能出现竞态问题;
同时,异步操作经常与逃逸闭包一起出现在API的设计中
串行队列的代码实战
// 串行同步
serialQueue.sync {
print(1);
}
print(2);
serialQueue.sync {
print(3);
}
print(4);
1
2
3
4
串行队列中同步操作时打印1,2,3,4
// 串行异步
serialQueue.async {
print(1);
}
print(2);
serialQueue.asyn {
print(3);
}
print(4);
serialQueue mainqueue
1 2不用等1
3等1 3等2 4等2
1 不用等我执行完 12 21
2 主队列,同步 1243 1234 2134 2143 2413
3 不用等我执行完
4 主队列,同步
同步
同步执行:比如这里的
dispatch_sync
,这个函数会把一个block加入到指定的队列中,而且会一直等到执行完blcok,这个函数才返回。因此在block执行完之前,调用dispatch_sync
方法的线程是阻塞的。
异步
异步执行:一般使用
dispatch_async
,这个函数也会把一个block加入到指定的队列中,但是和同步
执行不同的是,这个函数把block加入队列后不等block的执行就立刻返回了。
// 串行异步嵌套同步 1
外边异步块 2
5 3阻塞线程等3完成
print(1) 4
serialQueue.async {
print(2)
serialQueue.sync {
print(3)
}
print(4)
}
print(5)
dispatch_sync() 同步执行,完成了它预定的任务后才返回,阻塞当前线程
dispatch_async() 异步执行,会立即返回,预定的任务会完成但不会等它完成,不阻塞当前线程
mainqueue serialQueue不同等
1 2 不同等
5 3 3进行的条件是整个block块结束,3等块结束
4 4等3结束
1,2
串行队列+同步任务:不会开启新的线程,任务逐步完成。
不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。
串行队列+异步任务:开启新的线程,任务逐步完成。
并发队列+同步任务:不会开启新的线程,任务逐步完成。
- 任务按顺序执行的。按顺序执行的原因:虽然 并发队列 可以开启多个线程,并且同时执行多个任务。但是因为本身不能创建新线程,只有当前线程这一个线程(同步任务 不具备开启新线程的能力),所以也就不存在并发。而且当前线程只有等待当前队列中正在执行的任务执行完毕之后,才能继续接着执行下面的操作(同步任务 需要等待队列的任务执行结束)。所以任务只能一个接一个按顺序执行,不能同时被执行。
并发队列+异步任务:可以开启多个线程,任务交替(同时)执行。
|
串 主
1 2
3 4
1一定在3之前被打印出来,因为1在3之前派发,串行队列一次只能执行一个任务。所以一旦派发完就执行任务。
2一定在4之前被打印出来
2一定在3之前被打印出来
多线程的实现方式?区别?每种方式的适用场景
1Pthread
pthread 是一套通用的多线程的 API,可以在Unix / Linux / Windows 等系统跨平台使用,使用 C 语言编写,需要程序员自己管理线程的生命周期,使用难度较大,我们在 iOS 开发中几乎不使用 pthread,但是还是来可以了解一下的。
场景 获取堆栈信息
https://blog.csdn.net/wxs0124/article/details/104961809
/_np 是指 not POSIX ,这里的 POSIX 是指操作系统的一个标准,特别是与 Unix 兼容的操作系统。np 表示与标准不兼容
pthread_t pt = pthread_from_mach_thread_np(list[i]);
获取当前调用栈的信息
#pragma mark - Interface
+ (NSString *)callStackWithType:(SMCallStackType)type {
// 所有线程
if (type == SMCallStackTypeAll) {
thread_act_array_t threads; //int 组成的数组比如 thread[1] = 5635
2NSThread
最大限度的掌控每个线程的生命周期。但是,也需要开发者手动管理所有的进程活动,
比如创建、同步、暂停、取消,其中手动加锁操作的挑战性很大。
NSThread总体使用场景很小,基本是在开发底层的开源软件或是测试时使用。
还有就是获取一些线程信息,如当前线程,主线程,线程名称。
NSThread *nsthread = [NSThread currentThread]; //当前执行的指令
3GCD
4Operation
// A、B、C、D、E、F六个任务
// A、B、C 并发执行
// D--->A,B
// E--->B,C
// F--->D、E
// 1A、B、C没有依赖关系
NSOperation实现
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 6;
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 3; i++) {
NSLog(@"A任务");
}
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 3; i++) {
NSLog(@"B任务");
}
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 3; i++) {
NSLog(@"C任务");
}
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 3; i++) {
NSLog(@"D任务");
}
}];
NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 3; i++) {
NSLog(@"E任务");
}
}];
NSBlockOperation *op6 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 3; i++) {
NSLog(@"F任务");
}
}];
[op4 addDependency:op1];
[op4 addDependency:op2];
[op5 addDependency:op2];
[op5 addDependency:op3];
[op6 addDependency:op4];
[op6 addDependency:op5];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
[queue addOperation:op4];
[queue addOperation:op5];
[queue addOperation:op6];
dispatch实现
/ D--->A,B
dispatch_group_t g1 = dispatch_group_create();
// E--->B,C
dispatch_group_t g2 = dispatch_group_create();
// F--->D、E
dispatch_group_t g3 = dispatch_group_create();
dispatch_group_enter(g1);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 3; i++) {
NSLog(@"A任务");
}
dispatch_group_leave(g1);
});
dispatch_group_enter(g1);
dispatch_group_enter(g2);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 3; i++) {
NSLog(@"B任务");
}
dispatch_group_leave(g1);
dispatch_group_leave(g2);
});
dispatch_group_enter(g2);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 3; i++) {
NSLog(@"C任务");
}
dispatch_group_leave(g2);
});
dispatch_group_enter(g3);
dispatch_group_notify(g1, dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 3; i++) {
NSLog(@"D任务");
}
dispatch_group_leave(g3);
});
dispatch_group_enter(g3);
dispatch_group_notify(g2, dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 3; i++) {
NSLog(@"E任务");
}
dispatch_group_leave(g3);
});
dispatch_group_notify(g3, dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 3; i++) {
NSLog(@"F任务");
}
});
dispatch_group_t底层
底层是一个结构体
类似一个链表的形式存储当前的任务
struct dispatch_group_s {
DISPATCH_SEMAPHORE_HEADER(group, dg)
int_volatile dg_waiters;
struct dispatch_continuation_s * volatitile dg_notify_head;
struct dispatch_continuation_s * volatitile dg_notify_tail;
}
enter函数原子自增,dg_value做加 两次enter之后就是等于2
leve函数原子自减,dg_value做减,每次减1
当dg_value为0的时候,就是执行_dispatch_group_wake(dg, false)
(dispatch源码苹果官网可下载)
enter,leave类似于信号量的标记
标记当前有多少个任务要执行
notify就是等group的任务为0,就是dg_value的任务为0的时候
BlockOperation是基于状态机制实现的
队列的任务是isReady状态时,才会被队列调度
operationqueue是一个队列
adddependency其实就是在底层建立一个依赖关系
比如D任务依赖于A,B其实实现就是,等A任务B任务的状态都编程isFinish后,D任务的状态变成isReady,然后isReady的任务就可以被调度了
所以D需要监听A,B的状态值变化,就是一个KVO
A依赖于B任务,A会存在一个dpendencies数组,里面存放着B
B有downdenpendices数组,存放着B
isReady的判断是根据当前计数器判断
然后最底层还是根据GCD来调度
operation源码在gihub上
多线程和runloop的关系
- 一一对应的关系
-通过k-value的形式存储在一个全局的字典中,来对应当前的线程和
runloop
-runloop源码
TSL是线程局部存储空间
当前线程访问autoreleasepool
return os_mac
pthread_getspecific
return os_linux
pthread_getspecific
return os_win32
pthread_getspecific
私有函数,当前线程的局部存储空间只能被当前线程所访问到,其他线程访问不到。
runloop没有就会创建,放在全局字典里的同时也会在TSL里做一个缓存。
当前线程的保活
子线程执行完任务后就会销毁掉,
线程常驻,就是
_port = [NSMacPort port];
_thread = [[LXJThread alloc] initWithTarget:self selector:@selector(threadTest) object:nil]];
[_thread start];
@autoreleasepool {
// port mach_msg 消息队列的收发,决定当前runloop是休眠还是执行任务
// port是和mac_os内核有关的东西
// 端口收发消息队列
// 先获取当前子线程的runloop
NSRunloop *runloop = [NSRunloop currentRunloop];
// 向当前的子线程runloop add port
// port用来收发消息
[self registerobserver]
[[runloop addPort:port forMode:nsrunloopdefaultnmode]];
[runloop run];
线程常驻后如何销毁子线程
子线程能存活就是依赖与port事件源
是否可以移除事件源头,然后解除
// runloop不会退出 [[runloop remove:port forMode:nsrunloopdefaultnmode]];
如果控制器销毁,runloop会不会退出
// runloop不会退出,还会有野指针问题
// 因为移除当前线程的事件源时,并不能保证系统不往线程里添加一些额外的事件源。所以没有办法通过移除自己事件源来移除常驻线程。
// 当前直接去退出当前线程
// [NSThread exit];线程无法响应,当前线程销毁,野指针。程序崩溃,线程销毁了,runloop不销毁还会造成内存泄漏。
// 看源码CFRunloop怎么stop的
// 判断CFRunloop finished是通过
// CFRunloop isFinished
// 如果当前runloop的mode是空的或者runloopmode是空,isfinish就是true。没有runloop mode 就finish了但是对现在没有用,因为当前runloop run 肯定有mode
// 如果mode name == null或者是commonmode(commomode只是一个标记)
// runloop run起来本质就是一个while循环
// 循环结束依赖于。。。
// _cfrunllop stop就相当于有了退出标记
// CFRunloopStop([CFRunloop current])当前的runloop还是退出不了
}
// 仿照源码
runloop runmode:nsdefaultmode beforedate: [snadate distantfuture]
- 有序列表第二项
# iOS 并发编程中的三大问题
* 竞态条件
两个或两个以上线程对共享的数据进行读写操作时,最终的数据结果不确定的情况。
* 优先倒置
低优先级的任务会因为各种原因先于高优先级的任务执行。
* 死锁问题
两个或者两个以上的线程,它们之间互相等待彼此停止执行,以获得某种资源,但是没有一方会提前退出的情况。在iOS开发中,**有一个经典的例子就是两个Operation互相依赖。
在对同一个串行队列中进行异步、同步嵌套时:
#如何Debug并发编程问题