iOS底层技术

IOS多线程—GCD由浅入深总结

2018-07-16  本文已影响0人  wg刚

注意几个名词:

同步:不会开启子线程, 而且会阻塞当前线程
异步:不会阻塞当前线程, 且具备开启线程的能力(不一定开启)
任务:block里面的代码块
串行队列:只会开启一个子线程
并行队列:可以开启多个子线程,但是不一定一定开启线程,因为开启一个线程会:消耗CPU资源、占内存(子线程512kb,主线程1M)能开启几个线程取决于内核,一般开启3-6个就可以了
主队列:异步添加任务必须等到主线程中的任务执行完才执行,就是一个串行队列,但是在主队列中添加的异步任务不会开启子线程

demo

第一部分:基础

一、同步串行

//同步串行
/*
 同步: 看到这两个字首先想到的是不会开启子线程,而且会阻塞当前线程
 */

- (void)syncSerial{
    NSLog(@"1111");
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        NSLog(@"2222");
    });
    dispatch_sync(queue, ^{
        NSLog(@"3333");
    });
    dispatch_sync(queue, ^{
        NSLog(@"4444");
    });
    NSLog(@"5555");
}

执行结果:

image.png

二、异步串行

//异步串行
/*
 异步: 要想到的是不会阻塞当前线程,且具备开启线程的能力(不一定会创建子线程,例如在串行队列中,只会创建一条子线程)
 */
- (void)asyncSerial{
    NSLog(@"1111");
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"2222");
    });
    dispatch_async(queue, ^{
        NSLog(@"3333");
    });
    dispatch_async(queue, ^{
        NSLog(@"4444");
    });
    NSLog(@"5555");
}

执行结果:

image.png

三、异步并行

//异步并行
- (void)asyncConcurrent{
    NSLog(@"1111");
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"2222");
    });
    dispatch_async(queue, ^{
        NSLog(@"3333");
    });
    dispatch_async(queue, ^{
        NSLog(@"4444");
    });
    NSLog(@"5555");
}

执行结果:

image.png

四、同步并行

//同步并行
- (void)syncConcurrent{
    NSLog(@"1111");
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        NSLog(@"2222");
    });
    dispatch_sync(queue, ^{
        NSLog(@"3333");
    });
    dispatch_sync(queue, ^{
        NSLog(@"4444");
    });
    NSLog(@"5555");
}

执行结果:

image.png

第二部分:嵌套

一、同步并行嵌套异步任务

//同步并行嵌套异步任务
- (void)syncConcurrentAsync{
    NSLog(@"1111");
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        NSLog(@"2222");
        dispatch_async(queue, ^{
            sleep(2);
            NSLog(@"3333");
        });
        NSLog(@"4444");
    });
    NSLog(@"5555");
}

执行结果:

image.png

结果分析:
1、先执行11111
2、遇到sync,同步任务,不会开启子线程,而且会阻塞当前线程,所以会执行2222
3、遇到aync,异步任务会开启子线程,不会阻塞当前线程,所以 执行一下3333,但是不需要3333这个异步任务执行完(不会阻塞当前线程) 就可以执行4444
4、执行完4444后,因为3333延迟2秒,所以会执行5555(如果不延迟2秒的话,可能会先执行3333也可能会先执行5555)
5、2秒后执行3333

二、同步串行嵌套异步任务

//同步串行嵌套异步任务
- (void)syncSerialAsync{
    NSLog(@"1111");
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        //block1:任务1
        NSLog(@"2222");
        dispatch_async(queue, ^{
            //block2:任务2
            NSLog(@"3333");
        });
        sleep(2);
        NSLog(@"4444");
    });
    sleep(2);
    NSLog(@"5555");
}

执行结果:

image.png

结果分析:

1、在串行队列中,任务顺序执行,必修等一个任务完成才能执行下一个任务,代码中sync包含两个任务:block1(2222,3333,4444)和block2(3333)
2、首先执行1111
3、遇到sync:同步任务,且在串行队列中有两个任务,顺序为block1和block2,所以要先执行完block1才能执行block2,所以先执行2222
4、遇到block1中async:异步任务,开启一个线程,不会阻塞当前线程,所以执行sleep和4444,因为3333是在block2中,所以会要把sleep和4444执行完(谨记sleep和4444在block1中),才会执行block2中的3333
5、两个任务都执行完后,执行sleep和5555

三、异步串行嵌套同步任务

//异步串行嵌套同步任务
- (void)asyncSerialSync{
    NSLog(@"1111");
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        //block1:任务1
        NSLog(@"2222");
        dispatch_sync(queue, ^{
            //block2:任务2
            NSLog(@"3333");
        });
        NSLog(@"4444");
    });
    NSLog(@"5555");
}

执行结果:死锁

image.png

结果分析:
1、先执行1111
2、遇到aync:异步任务,开启子线程,不会阻塞当前线程,所以执行5555
3、在aync中先执行2222
4、遇到sync:同步任务,不会开启新的子线程,且阻塞当前线程(2步骤中开启的子线程),又因为任务block1和block2在串行队列中,所以必须要等到任务block1(4444)执行完才能执行任务block2(3333),综合刚才说的block2(3333)阻塞这个子线程(即必须等到block2(3333)完成才能继续执行block1中的(4444)),进而造成死锁。

四、主队列中添加异步任务

//主队列中添加异步任务
- (void)main{
    NSLog(@"1111");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        NSLog(@"2222");
    });
    sleep(2);
    NSLog(@"3333");
}

执行结果:

image.png

结果分析:
1、主队列中异步添加任务必须等到主线程中的任务执行完才执行
2、主队列就是一个串行队列,但是在主队列中添加的异步任务不会开启子线程

五、主队列中添加同步任务

//主队列中添加同步任务
- (void)main1{
    //任务1
    NSLog(@"1111");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        //block任务2
        NSLog(@"2222");
    });
    NSLog(@"3333");
}

执行结果:死锁

image.png

结果分析:
1、在主线程中有两个任务:main1和block,先执行1111
2、遇到sync:同步任务,阻塞主线程
3、但是要想执行任务2(2222)必须要先执行完主线程中的任务,即main1(1111和3333)造成main1和block相互阻塞。

第三部分:常用api

一、dispatch_apply

- (void)apply{
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"index=%ld线程:%@", index, [NSThread currentThread]);
    });
    NSLog(@"end");
}

执行结果:

image.png

结果分析:
dispatch_apply类似一个for循环,会在指定的队列中中运行block任务n次,如果队列是并发队列,则会并发执行block任务。
dispatch_apply在串行队列中按照顺序执行,完全没有意义。在并发队列中创建了N个任务,但并非所有任务都开辟线程,也有在主线程中完成的。输出end,因为dispatch_apply函数会等待所有的处理结束,才会向下执行。

二、dispatch_group_t

- (void)group{
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1111");
    });
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"2222");
    });
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"3333");
    });
    dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"4444");
    });
}

执行结果

image.png

结果分析:
在group中的任务异步执行。等全部执行完毕后,通过dispatch_group_notify再执行后面代码。

三、比较dispatch_apply和dispatch_group_t

通过前两个api讲述可以看到其功能很相似。
1、我在dispatch_apply中添加异步任务

- (void)apply{
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
    dispatch_apply(20, queue, ^(size_t index) {
        dispatch_async(queue, ^{
            NSLog(@"index=%ld线程:%@", index, [NSThread currentThread]);
        });
    });
    NSLog(@"end");
}

执行结果:

image.png

结果分析:
可以看出先执行end,在dispatch_apply中的异步任务均是在子线程中完成。

2、在dispatch_group_t中同样添加异步任务

- (void)group{
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"1111");
        });
    });
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"2222");
        });  
  });
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"3333");
        });   
 });
    dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"4444");
    });
    NSLog(@"5555");
}

执行结果:

image.png

结果分析:
可以看出,同dispatch_apply一样,也不能保证group中全部完成再执行4444。

3、但是dispatch_group_t可以这么做来解决

- (void)group{
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"1111");
            dispatch_group_leave(group);
        });
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"2222");
        });
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"3333");
            dispatch_group_leave(group);
        });
    });
    
    dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"4444");
    });
    NSLog(@"5555");
}

执行结果:

image.png

第四部分:线程锁(线程安全)

线程不安全:内存数据被多个线程读取之后,出现的结果不可预见。
线程锁:确保在读取数据时只有一条线程再操作。
这里总结三种锁:synchronized、nslock、dispatch_semaphore_t、nonatomic和atomic的区别

先看下边代码

- (void)synchronized{
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        
        NSLog(@"1111");
        sleep(2);
        NSLog(@"2222");
    });
    dispatch_async(queue, ^{
        NSLog(@"3333");
        sleep(2);
        NSLog(@"4444");
    });
}

执行顺序:

image.png

出现线程不安全,因此需要加锁。

1、synchronized
- (void)synchronized{
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        @synchronized (self){
            NSLog(@"1111");
            sleep(2);
            NSLog(@"2222");
        }
    });
    dispatch_async(queue, ^{
        @synchronized (self){
            NSLog(@"3333");
            sleep(2);
            NSLog(@"4444");
        }
    });
    NSLog(@"5555");
}

执行结果:

image.png

结果分析:
等到一个线程完成,才会执行另一个线程。

2、dispatch_semaphore_t
/*
 信号量:控制我们的线程并发数
 */
- (void)semaphore{
    //线程并发数设为1,只有一个线程在走
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"1111");
        sleep(2);
        NSLog(@"2222");
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"3333");
        sleep(2);
        NSLog(@"4444");
        dispatch_semaphore_signal(semaphore);
    });
    NSLog(@"5555");
}

执行结果:

image.png
3、项目中为什么用nonatomic不用atomic

nonatomic:不安全
atomic:加锁+耗性能

//有两个属性,分别设置为nonatomic和atomic
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property (nonatomic, strong) NSString *name;
@property (atomic, assign) int number;

@end
. 10000个异步任务,修改name属性的值
- (void)nonatomic{
    for (NSInteger i = 0; i < 10000; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            self.name = [NSString stringWithFormat:@"name:%ld", i];
        });
    }
}

执行结果:崩溃,崩溃原因是在子线程Thread8上,对象释放了。

image.png

结果分析:
1、在MRC模式下,属性name的set方法如下:

-(void)setName:(NSString *)name{
    if (_name != name) {
        [_name release];
        [name retain];
        _name = name;
    }
}

2、虽然在ARC模式下不用写其set方法,但是在底层还是会走到这里
3、因为是多线程,且没有加锁保护,所以在一个线程走到[_name release]后,可能在另一个线程又一次去释放,这时候造成崩溃。
4、把name属性的nonatomic改成atomic就不会崩溃了,因为atomic加锁了,是安全的。

. 接着上步说用atomic就安全了,再举个例子

number属性使用atomic修饰的

- (void)atomic{
    _number = 0;
    dispatch_apply(10000, dispatch_get_global_queue(0, 0), ^(size_t index) {
        self->_number ++;
    });
    NSLog(@"_number:%d", _number);
}

执行结果:执行结果并不是10000,而且每次运行结果都不一样,即运行结果不可预见。

image.png

结果分析:

_number++等价于
 int temp = _number+1;
 _number = temp;

虽然atomic保证了number属性线程安全了,但是并不能保证temp变量的线程安全,又因为是多线程的,所以有可能同时执行多次 int temp = _number+1;才执行一次 _number = temp;导致结果越来越小,而且结果不可预知。

这时候就可以知道为什么不用atomic了:因为atomic会耗性能,而且大部分情况下并不会保证线程安全。

什么时候可以用atomic呢:在最简单的,只有一个set时,简单的读写实例变量。

UIKIT不需要使用atomic:因为UIKIT是在主线程做的,不存在线程安全问题。

4、NSLock

解决上步的问题

- (void)atomic{
    _number = 0;
    NSLock *lock = [[NSLock alloc] init];
    dispatch_apply(10000, dispatch_get_global_queue(0, 0), ^(size_t index) {
        [lock lock];
        self->_number ++;
        [lock unlock];
    });
    NSLog(@"_number:%d", _number);
}

执行结果:

image.png
上一篇下一篇

猜你喜欢

热点阅读