• iOS多线程——GCD篇


    什么是GCD

    GCD是苹果对多线程编程做的一套新的抽象基于C语言层的API,结合Block简化了多线程的操作,使得我们对线程操作能够更加的安全高效。

    在GCD出现之前Cocoa框架提供了NSObject类的

    performSelectorInBackground:withObject

    performSelectorOnMainThread

    方法来简化多线程编程技术。

    GCD可以解决以下多线程编程中经常出现的问题:
    1.数据竞争(比如同时更新一个内存地址)

    2.死锁(互相等待)

    3.太多线程导致消耗大量内存

    在iOS中,如果把需要消耗大量时间的操作放在主线程上面,会妨碍主线程中被称为RunLoop的主循环的执行,从而导致不能更新用户界面、应用程序的画面长时间停滞等问题。

    Dispatch Queue

    Dispatch Queue是GCD中对于任务的抽象队列(FIFO)执行处理。

    queue分为两种,

    SERIAL_DISPATCH_QUEUE           等待现在执行中处理结束

    CONCURRENT_DISPATCH_QUEUE       不等待现在执行中处理结束

    换句话说也就是 SERIAL_DISPATCH_QUEUE 是串行,CONCURRENT_DISPATCH_QUEUE是并行。

    具体到线程上,就是SERIAL_DISPATCH_QUEUE只会创在一个线程来处理任务序列,而CONCURRENT_DISPATCH_QUEUE则会创在多个线程,但是具体创建多少个则是有运行的操作系统根据资源决定的。

    所以SERIAL_DISPATCH_QUEUE 中处理的代码是有序的,而CONCURRENT_DISPATCH_QUEUE中则是无序的,但是相对会更高效一点。

    API

    dispatch_queue_create

    用于创建一个任务执行queue

    参数列表

    const char *label             queue的名称,作为该queue的唯一标示,改名会在Xcode和Instruments的调试器中直接作为DispatchQueue名称显示出来

    dispatch_queue_attr_t     设定queue的类型,即ConcurrentQueue还是SerialQueue,NULL则默认为SerialQueue

    返回值

    dispatch_queue_t变量

    这里要说一下main_dispatch_queue 和 global_dispatch_queue 这两种系统提供的,

    main_queue通过

    dispatch_get_main_queue()

    global_queue通过

    dispatch_get_global_queue(),global等级分为

    HIGH、DEFAULT、LOW、BACKGROUND四种

    dispatch_async

    向指定的queue中添加block操作,异步的执行,屏蔽了多线程的实现细节,自动为我们生成线程执行。

    dispatch_after

    类似延迟函数,可以指定queue来进行延迟操作

        dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
        dispatch_after(time, dispatch_get_main_queue(), ^{
            NSLog(@"等待3秒");
        });

    dispatch_group_notify

    对于监听queue的执行,当所有任务完成后可以进行回调操作

    复制代码
        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(@"1");
        });
        dispatch_group_async(group, queue, ^{
            NSLog(@"2");
        });
        dispatch_group_async(group, queue, ^{
            NSLog(@"3");
        });
        dispatch_group_notify(group, queue, ^{
            NSLog(@"finish");
        });
    复制代码

    对于一系列的block在同一queue中执行,如果是serialQueue是顺序进行的,因此可以在最后一个任务来处理结束操作。但是对于concurrentQueue是并行的,如果想监听完结操作,就要用该方法。

    dispatch_group_wait和notify差不多,只不过wait方法可以设置等待时间。如果时间到了还没有结束queue的所有操作,那么接下来还是会继续进行,不过还是可以设定为forever一直等待下去,这样就和notify起到一样的作用。

    dispatch_barrier_async

    该操作主要是为了防止资源竞争。在concurrentQueue中,所有block无序的按照所创建的线程数量同时进行。如果在concurrentQueue中有两个写入操作,而且他都是读取操作,这时两个写入操作间就会出现资源竞争,而读取操作则会读取脏数据。所以对于在concurrentQueue中不能够与其它操作并行的block就需要使用dispatch_barrier_async方法来防止资源竞争。

    dispatch_sync

    和dispatch_async不同,dispatch_sync用于线程之间的同步操作,比如说A线程要做一件事必须要放在B线程之后来进行,那么此时就需要用到dispatch_sync。

    另外,不能够在某个执行线程中同步自己,这样会造成线程死锁,比如说

    复制代码
        dispatch_queue_t queue1 = dispatch_get_main_queue();
        dispatch_sync(queue1, ^{
            NSLog(@"main queue 中同步main queue操作");
        });
        
        dispatch_queue_t queue = dispatch_queue_create("com.queue.www", NULL);
        dispatch_async(queue, ^{
            dispatch_sync(queue, ^{
                NSLog(@"在新的serial queue中同步serial queue操作");
            });
        });
    复制代码

    所以说使用serial queue的时候一定不要同步自己。

    dispatch_apply

    dispatch_apply函数是dispatch_sync和dispatch group的关联函数,是用指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理执行结束,例如

    复制代码
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_apply(10, queue, ^(size_t index) {
            NSLog(@"%ld",index);
        });
        NSLog(@"apply finish");
    
    2015-08-02 09:38:18.296 Dispatch[7388:2035125] 4
    2015-08-02 09:38:18.296 Dispatch[7388:2035244] 2
    2015-08-02 09:38:18.296 Dispatch[7388:2035241] 1
    2015-08-02 09:38:18.296 Dispatch[7388:2035259] 6
    2015-08-02 09:38:18.296 Dispatch[7388:2035243] 0
    2015-08-02 09:38:18.296 Dispatch[7388:2035257] 3
    2015-08-02 09:38:18.296 Dispatch[7388:2035258] 5
    2015-08-02 09:38:18.296 Dispatch[7388:2035260] 7
    2015-08-02 09:38:18.296 Dispatch[7388:2035125] 8
    2015-08-02 09:38:18.296 Dispatch[7388:2035244] 9
    2015-08-02 09:38:18.296 Dispatch[7388:2035125] apply finish
    复制代码

    实际上可以看出来,该函数让主线程和queue进行同步操作,并且等queue中所有线程执行完毕后才继续执行。

    dispatch_semaphore

    在进行数据处理时,dispatch_barrier_async可以避免这类问题,但是有时需要更加精细的操作。

    比如要对数组添加10000个对象,用concurrentQueue添加。我们知道concurrentQueue会生成多个线程,很可能会出现多个线程一起对数组访问的情况,很容易出现问题。我们需要控制一次只让一个线程操作数组,如下:

    复制代码
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
        
        NSMutableArray *array = [NSMutableArray new];
        for (int i =  0 ; i < 10000 ; i++)
        {
            dispatch_async(queue, ^{
                dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
                [array addObject:[NSNumber numberWithInt:i]];
                NSLog(@"add %d",i);
                dispatch_semaphore_signal(semaphore);
            });
        }
    复制代码

    这里简单说一下信号量,也就是创建dispatch_semaphore的第二个参数。指定一个信号量,那么当信号量是大于0的时候所有线程都是可访问的。一旦有现成访问信号量会减1,如果信号量为0就会进入等待,知道dispatch_semaphore_signal函数调用来重新恢复信号量。所以基本上可以理解为有几个信号量就能有几个线程并发的访问。

    再比如说现在有两个线程一个添加数据一个删除数据,那么就需要两个信号量变量来实现多线程间的协作

    复制代码
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_semaphore_t semaphoreAdd = dispatch_semaphore_create(1);
        dispatch_semaphore_t semaphoreRemove = dispatch_semaphore_create(0);
        NSMutableArray *array = [NSMutableArray new];
        for (int i =  0 ; i < 2 ; i++)
        {
            dispatch_async(queue, ^{
                dispatch_semaphore_wait(semaphoreAdd, DISPATCH_TIME_FOREVER);
                [array addObject:[NSNumber numberWithInt:i]];
                NSLog(@"add %lu",[array count]);
                dispatch_semaphore_signal(semaphoreRemove);
            });
            dispatch_async(queue, ^{
                dispatch_semaphore_wait(semaphoreRemove, DISPATCH_TIME_FOREVER);
                [array removeObject:[NSNumber numberWithInt:i]];
                NSLog(@"add %lu",[array count]);
                dispatch_semaphore_signal(semaphoreAdd);
            });
        }
    复制代码

    dispatch_once

    dispatch_once用来标记一个操作,只执行一次,该方法一般在生产单例对象使用。如果不用dispatch_once创建单例是不安全的,需要进行加锁处理,但是dispatch_once可以很好地解决这一点。

    复制代码
    +(instancetype)sharedInstance
    {
        static CustomObject *obj;
        static dispatch_once_t once;
        dispatch_once(&once, ^{
            obj = [[CustomObject alloc] init];
        });
        return obj;
    }
    复制代码
  • 相关阅读:
    (转) 解析 char *p和 char[]
    Linux下C程序内存、内存对齐问题 (实战)
    关于子网划分的两个例子
    子网掩码与子网划分 (转载)
    A、B、C类地址及子网掩码学习笔记
    本机ip、127.0.0.1和0.0.0.0区别(转载)
    初识const
    流媒体协议
    i2c-tools的使用方法
    linux ——内存共享映射mmap和munmap
  • 原文地址:https://www.cnblogs.com/pb89/p/4708791.html
Copyright © 2020-2023  润新知