美文网首页iOS底层技术
IOS多线程—GCD由浅入深总结

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

作者: wg刚 | 来源:发表于2018-07-16 21:26 被阅读0次

    注意几个名词:

    同步:不会开启子线程, 而且会阻塞当前线程
    异步:不会阻塞当前线程, 且具备开启线程的能力(不一定开启)
    任务: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

    相关文章

      网友评论

        本文标题:IOS多线程—GCD由浅入深总结

        本文链接:https://www.haomeiwen.com/subject/ngkspftx.html