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

注意几个名词:

同步:不会开启子线程, 而且会阻塞当前线程
异步:不会阻塞当前线程, 且具备开启线程的能力(不一定开启)
任务: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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,841评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,415评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,904评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,051评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,055评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,255评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,729评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,377评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,517评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,420评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,467评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,144评论 3 317
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,735评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,812评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,029评论 1 256
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,528评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,126评论 2 341