iOS多线程之GCD

GCD的概念

GCD(Grand Central Dispatch),是有Apple公司开发的一个多核编程的解决方案,用以优化应用程序支持多核处理器,是基于线程模式之上执行并发任务。

GCD的优点

1.利用设备多核进行并行运算
2.GCD自动充分使用设备的CPU内核
3.GCD自动管理线程的生命周期(线程创建、线程调度、线程销毁)
4.使用简单

GCD任务和队列

任务:就是执行操作,即可以执行的代码;执行任务有两种方式:同步异步

  • 同步(sync)
    阻塞线程: 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行;
    不可以开辟新的线程
  • 异步(async)
    不会阻塞线程:异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务
    可以开辟新的线程

队列Dispatch Queue指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,遵循 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务;

在 GCD 中有两种队列:串行队列并发队列。两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。

  • 串行队列(Serial Dispatch Queue): 每次只有一个任务被执行。让任务一个接着一个地执行

  • 并发队列(Concurrent Dispatch Queue): 可以让多个任务同时执行。(可以开启多个线程,并且同时执行任务)

GCD 的使用步骤

GCD的使用分为两步:

1. 创建队列
2. 将任务追加到队列中

创建队列

创建队列使用dispatch_queue_create函数

   /*
     创建队列
      label:队列的唯一标识符
      attr:用来识别是串行队列还是并发队列;
           DISPATCH_QUEUE_SERIAL 表示串行队列;
           DISPATCH_QUEUE_CONCURRENT 表示并发队列;
     
     dispatch_queue_t
     dispatch_queue_create(const char *_Nullable label,
             dispatch_queue_attr_t _Nullable attr);

     */
    dispatch_queue_t queue = dispatch_queue_create("qctt", DISPATCH_QUEUE_SERIAL);

获取队列

串行队列,GCD 默认提供了主队列(Main Dispatch Queue

 //获取主队列
    dispatch_queue_t mainqueue = dispatch_get_main_queue();

并发队列,GCD 默认提供了全局并发队列(Global Dispatch Queue

 /**
    参数一表示队列优先级,默认 DISPATCH_QUEUE_PRIORITY_DEFAULT

    DISPATCH_QUEUE_PRIORITY_DEFAULT
    DISPATCH_QUEUE_PRIORITY_HIGH
    DISPATCH_QUEUE_PRIORITY_DEFAULT
    DISPATCH_QUEUE_PRIORITY_LOW
    DISPATCH_QUEUE_PRIORITY_BACKGROUND

    参数二暂时没用,用 0 即可
    */
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

创建任务

GCD提供了创建同步任务以及异步任务的函数;
同步任务: dispatch_sync

 // 同步执行任务创建方法
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"%s",__func__);
    });

异步任务: dispatch_async

  // 异步执行任务创建方法
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"%s",__func__);

    });

任务和队列不同组合方式的区别

区别串行队列并发队列主队列
sync没有开启新线程,串行没有开启新线程,串行死锁卡住不执行
async开启新线程,串行开启新线程,并发没有开启新线程,串行

GCD 的组合使用

串行队列 + 同步执行

不会开启新线程,在当前线程执行任务。执行完一个任务,再执行下一个任务。

  /**
     * 串行队列 + 同步执行
     * 特点:不会开启新线程,在当前线程执行任务。执行完一个任务,再执行下一个任务。
     */
    dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
            // 追加任务 1
            [NSThread sleepForTimeInterval:1];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_sync(queue, ^{
            // 追加任务 2
            [NSThread sleepForTimeInterval:3];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_sync(queue, ^{
            // 追加任务 3
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    NSLog(@"syncSerial---end");

运行结果同步顺序执行

 

串行队列 + 异步执行

会开启新线程。执行完一个任务,再执行下一个任务。

 /**
     * 串行队列 + 异步执行
     * 特点:会开启新线程。执行完一个任务,再执行下一个任务。
     */
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
            // 任务 1
            [NSThread sleepForTimeInterval:1];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_async(queue, ^{
            // 任务 2
            [NSThread sleepForTimeInterval:3];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_async(queue, ^{
            // 任务 3
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    NSLog(@"asyncSerial---end");

打印日志:

2022-05-31 10:20:28.125523+0800 DemoTest2022[5271:199261] asyncSerial---end
2022-05-31 10:20:29.125730+0800 DemoTest2022[5271:199349] 1---<NSThread: 0x600003d8fb80>{number = 5, name = (null)}
2022-05-31 10:20:32.131137+0800 DemoTest2022[5271:199349] 2---<NSThread: 0x600003d8fb80>{number = 5, name = (null)}
2022-05-31 10:20:34.132492+0800 DemoTest2022[5271:199349] 3---<NSThread: 0x600003d8fb80>{number = 5, name = (null)}

并发队列 + 同步执行

不会开启新线程,在当前线程执行任务。执行完一个任务,再执行下一个任务。

  /**
    * 并发队列 + 同步执行
    * 特点:在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。
    */
    dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
           // 任务 1
           [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
           NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_sync(queue, ^{
           // 任务 2
           [NSThread sleepForTimeInterval:3];              // 模拟耗时操作
           NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_sync(queue, ^{
           // 任务 3
           [NSThread sleepForTimeInterval:1];              // 模拟耗时操作
           NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    NSLog(@"syncConcurrent---end");

2022-05-31 10:31:42.719133+0800 DemoTest2022[5476:209855] 1---<_NSMainThread: 0x6000032507c0>{number = 1, name = main}
2022-05-31 10:31:45.719679+0800 DemoTest2022[5476:209855] 2---<_NSMainThread: 0x6000032507c0>{number = 1, name = main}
2022-05-31 10:31:46.720250+0800 DemoTest2022[5476:209855] 3---<_NSMainThread: 0x6000032507c0>{number = 1, name = main}
2022-05-31 10:31:46.720599+0800 DemoTest2022[5476:209855] syncConcurrent---end

并发队列 + 异步执行

可以开启多个线程,任务同时执行。

   /**
     * 并发队列 + 异步执行
     * 特点:可以开启多个线程,任务同时执行。
     */
    dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
            // 任务 1
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_async(queue, ^{
            // 任务 2
            [NSThread sleepForTimeInterval:3];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_async(queue, ^{
            // 任务 3
            [NSThread sleepForTimeInterval:1];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    NSLog(@"asyncConcurrent---end");

执行结果:

2022-05-31 10:27:26.350321+0800 DemoTest2022[5398:206005] asyncConcurrent---end
2022-05-31 10:27:27.351422+0800 DemoTest2022[5398:206201] 3---<NSThread: 0x600002d22dc0>{number = 4, name = (null)}
2022-05-31 10:27:28.354696+0800 DemoTest2022[5398:206202] 1---<NSThread: 0x600002d3c7c0>{number = 6, name = (null)}
2022-05-31 10:27:29.352304+0800 DemoTest2022[5398:206200] 2---<NSThread: 0x600002d34e00>{number = 9, name = (null)}

GCD 的其他方法栅栏函数

GCD栅栏方法: dispatch_barrier_async

dispatch_barrier_async 方法会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。然后在 dispatch_barrier_async 方法追加的任务执行完毕之后,异步队列才恢复为一般动作,接着追加任务到该异步队列并开始执行。

void
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

 

   /**
     * 栅栏方法 dispatch_barrier_async
     */
    dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
            // 追加任务 1
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_async(queue, ^{
            // 追加任务 2
            [NSThread sleepForTimeInterval:1];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_barrier_async(queue, ^{
            // 追加任务 barrier
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
    });
    dispatch_async(queue, ^{
            // 追加任务 3
            [NSThread sleepForTimeInterval:1];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_async(queue, ^{
            // 追加任务 4
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"4---%@",[NSThread currentThread]);      // 打印当前线程
    });

执行日志:先执行栅栏之前的21,然后执行栅栏,再添加后续任务并执行

2022-05-31 10:48:48.606711+0800 DemoTest2022[5722:224160] 2---<NSThread: 0x600002b12b80>{number = 8, name = (null)}
2022-05-31 10:48:49.607101+0800 DemoTest2022[5722:224161] 1---<NSThread: 0x600002b05d00>{number = 5, name = (null)}
2022-05-31 10:48:51.608045+0800 DemoTest2022[5722:224161] barrier---<NSThread: 0x600002b05d00>{number = 5, name = (null)}
2022-05-31 10:48:52.609360+0800 DemoTest2022[5722:224161] 3---<NSThread: 0x600002b05d00>{number = 5, name = (null)}
2022-05-31 10:48:53.608776+0800 DemoTest2022[5722:224165] 4---<NSThread: 0x600002b0f900>{number = 7, name = (null)}

栅栏函数实现多读单写

#import "UserCenter.h"

@interface UserCenter()
{
    // 定义一个并发队列
    dispatch_queue_t concurrent_queue;
    
    // 用户数据中心, 可能多个线程需要数据访问
    NSMutableDictionary *userCenterDic;
}

@end

// 多读单写模型
@implementation UserCenter

- (id)init
{
    self = [super init];
    if (self) {
        // 通过宏定义 DISPATCH_QUEUE_CONCURRENT 创建一个并发队列
        concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
        // 创建数据容器
        userCenterDic = [NSMutableDictionary dictionary];
    }
    
    return self;
}

- (id)objectForKey:(NSString *)key
{
    __block id obj;
    // 同步读取指定数据
    dispatch_sync(concurrent_queue, ^{
        obj = [userCenterDic objectForKey:key];
    });
    
    return obj;
}

- (void)setObject:(id)obj forKey:(NSString *)key
{
    // 异步栅栏调用设置数据
    dispatch_barrier_async(concurrent_queue, ^{
        [userCenterDic setObject:obj forKey:key];
    });
}

@end

 GCD 延时执行方法:dispatch_after

dispatch_after 方法并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。

   /**
     * 延时执行方法 dispatch_after
     */
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 2.0 秒后异步追加任务代码到主队列,并开始执行
        NSLog(@"after---%@",[NSThread currentThread]);  // 打印当前线程
    });
}

GCD 执行一次:dispatch_once

在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了 GCD 的 dispatch_once 方法。使用 dispatch_once 方法能保证某段代码在程序运行过程中只被执行 1 次,并且即使在多线程的环境下,dispatch_once 也可以保证线程安全。

void
dispatch_once(dispatch_once_t *predicate,
        DISPATCH_NOESCAPE dispatch_block_t block);
/**
 * 只执行一次 dispatch_once
 */
static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只执行 1 次的代码(这里面默认是线程安全的)
});

GCD 队列组:dispatch_group

场景:当需要异步执行多个耗时任务,然后执行完毕后回到主线程;

  • dispatch_group_notify: 监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务;
      dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t global = dispatch_get_global_queue(0, 0);
        dispatch_group_async(group, global, ^{
            //添加任务
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
            dispatch_async(global, ^{
                [NSThread sleepForTimeInterval:1];
    
                NSLog(@"11111---%@",[NSThread currentThread]);
    
            });
        });
        
        dispatch_group_async(group, global, ^{
            //添加任务
            [NSThread sleepForTimeInterval:1];
            NSLog(@"2---%@",[NSThread currentThread]);
            dispatch_async(global, ^{
                [NSThread sleepForTimeInterval:2];
    
                NSLog(@"222222---%@",[NSThread currentThread]);
    
            });
        });
        
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            //等待 1,2 任务执行完成后到主线程执行3
            [NSThread sleepForTimeInterval:1];
            NSLog(@"3---%@",[NSThread currentThread]);
            NSLog(@"group end");
        });
    

    2022-05-31 14:12:15.717237+0800 DemoTest2022[8494:370194] 2---<NSThread: 0x60000224d940>{number = 9, name = (null)}
    2022-05-31 14:12:16.717106+0800 DemoTest2022[8494:370195] 1---<NSThread: 0x600002230980>{number = 4, name = (null)}
    2022-05-31 14:12:17.718496+0800 DemoTest2022[8494:369922] 3---<_NSMainThread: 0x600002228ac0>{number = 1, name = main}
    2022-05-31 14:12:17.718496+0800 DemoTest2022[8494:370195] 11111---<NSThread: 0x600002230980>{number = 4, name = (null)}
    2022-05-31 14:12:17.718496+0800 DemoTest2022[8494:370194] 222222---<NSThread: 0x60000224d940>{number = 9, name = (null)}
    2022-05-31 14:12:17.718751+0800 DemoTest2022[8494:369922] group end

  • dispatch_group_wait: 暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行
       dispatch_group_t group =  dispatch_group_create();
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_group_async(group,queue , ^{
            // 追加任务 1
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        });
        dispatch_group_async(group, queue, ^{
            // 追加任务 2
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        });
        // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
        NSLog(@"group---end");

    2022-05-31 14:36:09.722970+0800 DemoTest2022[8888:390059] 1---<NSThread: 0x6000027da3c0>{number = 3, name = (null)}
    2022-05-31 14:36:09.722967+0800 DemoTest2022[8888:390062] 1---<NSThread: 0x6000027de940>{number = 7, name = (null)}
    2022-05-31 14:36:09.723212+0800 DemoTest2022[8888:389913] group---end

  • dispatch_group_enter: 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1; 
    void
    dispatch_group_enter(dispatch_group_t group);
    
  • dispatch_group_leave: 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1;
    void
    dispatch_group_leave(dispatch_group_t group);
    

    当 group 中未执行完毕任务数为0的时候,才会使 dispatch_group_wait 解除阻塞,以及执行追加到 dispatch_group_notify 中的任务;

    /**
     * 队列组 dispatch_group_enter、dispatch_group_leave
     */
    - (void)groupEnterAndLeave {
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"group---begin");
        dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            // 追加任务 1
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    
            dispatch_group_leave(group);
        });
        
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            // 追加任务 2
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
            
            dispatch_group_leave(group);
        });
        
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            // 等前面的异步操作都执行完毕后,回到主线程.
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        
            NSLog(@"group---end");
        });
    }
    

    2022-05-31 14:56:53.413320+0800 DemoTest2022[9165:405241] currentThread---<_NSMainThread: 0x600001024500>{number = 1, name = main}
    2022-05-31 14:56:53.413467+0800 DemoTest2022[9165:405241] group---begin
    2022-05-31 14:56:55.415122+0800 DemoTest2022[9165:405400] 1---<NSThread: 0x60000102e080>{number = 6, name = (null)}
    2022-05-31 14:56:55.415122+0800 DemoTest2022[9165:405398] 2---<NSThread: 0x600001078040>{number = 8, name = (null)}
    2022-05-31 14:56:57.415797+0800 DemoTest2022[9165:405241] 3---<_NSMainThread: 0x600001024500>{number = 1, name = main}
    2022-05-31 14:56:57.416083+0800 DemoTest2022[9165:405241] group---end

GCD 信号量:dispatch_semaphore 

信号量是基于计数器的一种多线程同步机制,用来管理对资源的并发访问。
GCD 中的信号量是指持有计数的信号。在 Dispatch Semaphore 中,使用计数来完成这个功能,信号量为0则阻塞线程,大于0则不会阻塞。则我们通过改变信号量的值,来控制是否阻塞线程,从而达到线程同步。
Dispatch Semaphore 提供了三个方法:

  • dispatch_semaphore_create:创建一个 Semaphore 并初始化信号的总量
  • dispatch_semaphore_signal:发送一个信号,让信号总量加 1
  • dispatch_semaphore_wait:可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。

信号量dispatch_semaphore主要用于两个方面:保持线程同步、为线程加锁

  • 保持线程同步

/** 以AFNetworking源码为例*/ 
  • 为线程加锁 
     dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
        for (int i = 0; i < 10; i++) {
             dispatch_async(queue, ^{
                
                  // 相当于加锁 -1操作
                  dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
                  /*
                   todo:
                   */
                  // 相当于解锁 +1操作。 计数器>= 0就不阻塞线程
                 int t = rand()%3;//随机0-3秒休眠耗时操作
                 [NSThread sleepForTimeInterval:t];
                 NSLog(@"%d",i);
                  dispatch_semaphore_signal(semaphore);
              });
        }

    2022-05-31 17:18:02.966335+0800 DemoTest2022[11107:503565] 0
    2022-05-31 17:18:03.970640+0800 DemoTest2022[11107:503570] 1
    2022-05-31 17:18:05.972503+0800 DemoTest2022[11107:503564] 2
    2022-05-31 17:18:07.976567+0800 DemoTest2022[11107:503566] 3
    2022-05-31 17:18:08.980964+0800 DemoTest2022[11107:503569] 4
    2022-05-31 17:18:10.984335+0800 DemoTest2022[11107:503568] 5
    2022-05-31 17:18:10.984578+0800 DemoTest2022[11107:503572] 6
    2022-05-31 17:18:12.985732+0800 DemoTest2022[11107:503573] 7
    2022-05-31 17:18:14.991233+0800 DemoTest2022[11107:503574] 8
    2022-05-31 17:18:15.995794+0800 DemoTest2022[11107:503575] 9

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风雨「83」

你的鼓励将是我创作最大的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值