「iOS」——GCD其他方法详解

GCD学习


GCD其他方法

前面对GCD进行了简单的学习,这里笔者对一些容易遗忘和重要的方法再次学习。

dispatch_semaphore (信号量)

什么是信号量

引用学长的解释

以一个停车场的运作为例。简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看 门人允许其中三辆直接进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开 车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用。

信号量主要用作同步锁,用于控制GCD最大并发数

主要涉及以下三个函数:

objectivec 复制代码
// 创建信号量,value:初始信号量数 如果小于0则会返回NULL
dispatch_semaphore_create(long value); 

// 发送信号量是的信号量+1
dispatch_semaphore_signal(dispatch_semaphore_t deem);

//可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); 

注意:信号量的使用前提是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后使用信号量。

dispatch_semaphore主要作用

dispatch_semaphore主要作用
  • 保持线程同步,将异步执行任务转换为同步执行任务
  • 保证线程安全,为线程加锁
异步转同步
objectivec 复制代码
- (void)cjl_testSemaphore1{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    //创建异步队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        
        sleep(1);
        NSLog(@"执行任务A");
        //信号量+1
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_async(queue, ^{
        
        sleep(1);
        NSLog(@"执行任务B");
        //信号量+1,相当于解锁
        dispatch_semaphore_signal(semaphore);
    });
    
    //当当前的信号量值为0时候会阻塞线,如果大于0的话,信号量-1,不阻塞线程
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_async(queue, ^{
        
        sleep(1);
        NSLog(@"执行任务C");
        //信号量+1,相当于解锁
        dispatch_semaphore_signal(semaphore);
    });
    
}

多次运行的结果都是A,B,C顺序执行,让A,B,C异步执行变成同步执行,dispatch_semaphore相当于加锁效果

设置一个最大开辟的线程数
objectivec 复制代码
- (void)cjl_testSemaphore{
//设置最大开辟的线程数为3
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
    //创建一个并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    //开启的是10个异步的操作,通过信号量,让10个异步的最多3个m,剩下的同步等待
    for (NSInteger i = 0; i < 10; i++) {
        
        dispatch_async(queue, ^{
            //当信号量为0时候,阻塞当前线程
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"执行任务 %ld", i);
            sleep(2);
            NSLog(@"完成当前任务 %ld", i);
            //释放信号
            dispatch_semaphore_signal(semaphore);
        });
    }
}

但如果sleep时间变成1,则会出现以下结果:

这是因为出现了优先级反转的问题。

先介绍什么是优先级反转。

在程序执行时多个任务可能会对同---份数据产生 竞争'因此任务会使用锁来保护共享数据°假设现在有3个任务A、B`C'它们的优先 级为A>B>C任务C在运行时持有一把锁'然后它被高优先级的任务A抢占了(任 务C的锁没有被释放)。此时任务A恰巧也想申请任务C持有的锁'但是申请失败,因 此进人阻塞状态等待任务C放锁。此时'任务B、C都处于可以运行的状态,由于任务 B的优先级高于C,因此B优先运行°综合观察该情况'就会发现任务B好像优先级高 于任务A'先于任务A执行。

信号量调度是公平队列(FIFO/不考虑 QoS)+ 系统线程调度是根据优先级(QoS)来的,两者机制不一致,所以可能造成高优先级线程因为信号量资源被低优先级线程占用而"被阻塞"。

加锁机制
  • 当你的信号设置为1的时候就相当于加锁
objectivec 复制代码
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"semaphore---begin");
    
    semaphoreLock = dispatch_semaphore_create(1);
    self.ticketCount = 50;
    
    // queue1 代表北京火车票售卖窗口
    dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);

    // queue2 代表上海火车票售卖窗口
    dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
    
    __weak typeof(self) weakSelf = self;
    
    dispatch_async(queue1, ^{
    
        [weakSelf saleTicketSafe];
    });
    
    dispatch_async(queue2, ^{
 
        [weakSelf saleTicketSafe];
    });
}

- (void)saleTicketSafe {
    while (1) {
        // 相当于加锁
        dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
        
        if (self.ticketCount > 0) {  
            //如果还有票,继续售卖
          self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
            
        } else { 
        
            //如果已卖完,关闭售票窗口
            NSLog(@"所有火车票均已售完"); 
            // 相当于解锁
        dispatch_semaphore_signal(semaphoreLock);
            break;
        } 
        // 相当于解锁
        dispatch_semaphore_signal(semaphoreLock);
    }    
}
dispatch_time_t 两种形式
  1. 基于dispatch_time函数,表示从当前时间开始的一个时间点
objectivec 复制代码
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);

使用:

objectivec 复制代码
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(60 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
    NSLog(@"dispatch_time");
});

时间单位:

objectivec 复制代码
#define NSEC_PER_SEC 1000000000ull 1秒
#define NSEC_PER_MSEC 1000000ull 1毫秒 
#define USEC_PER_SEC 1000000ull 1秒
#define NSEC_PER_USEC 1000ull 1微秒
时间点:
#define DISPATCH_TIME_NOW (0ull) 0->现在
#define DISPATCH_TIME_FOREVER (~0ull) -1->永远
dispatch_time_t定义
typedef uint64_t dispatch_time_t; unsigned long long 64位无符号长整形
GCD一次性代码(只执行一次)

使用dispatch_once方法能保证某段代码在程序运行过程中只执被执行1次,即使在多线程的环境下,该方法也可以保证建成安全。之前在创建单例模式时就接触过该方法。

objectivec 复制代码
- (void)once {
	static dispatch_once_t onceToken;
	dispatch_once(&onceToken, ^{
        // 只执行 1 次的代码(这里面默认是线程安全的)
    });

}

dispatch_barrier_async/sync栅栏方法

在访问数据库或者文件的时候,我们可以使用Serial Dispatch Queue可避免数据竞争问题。即多读单写

objectivec 复制代码
- (void)cjl_testBarrier{
    /*
     dispatch_barrier_sync & dispatch_barrier_async
     
     应用场景:同步锁
     
     等栅栏前追加到队列中的任务执行完毕后,再将栅栏后的任务追加到队列中。
     简而言之,就是先执行栅栏前任务,再执行栅栏任务,最后执行栅栏后任务
     
     - dispatch_barrier_async:前面的任务执行完毕才会来到这里
     - dispatch_barrier_sync:作用相同,但是这个会堵塞线程,影响后面的任务执行
    
     - dispatch_barrier_async可以控制队列中任务的执行顺序,
     - 而dispatch_barrier_sync不仅阻塞了队列的执行,也阻塞了线程的执行(尽量少用)
     */
    
    [self cjl_testBarrier1];
    [self cjl_testBarrier2];
}
- (void)cjl_testBarrier1{
    //串行队列使用栅栏函数
    
    dispatch_queue_t queue = dispatch_queue_create("CJL", DISPATCH_QUEUE_SERIAL);
    
    NSLog(@"开始 - %@", [NSThread currentThread]);
    dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"延迟2s的任务1 - %@", [NSThread currentThread]);
    });
    NSLog(@"第一次结束 - %@", [NSThread currentThread]);
    
    //栅栏函数的作用是将队列中的任务进行分组,所以我们只要关注任务1、任务2
    dispatch_barrier_async(queue, ^{
        NSLog(@"------------栅栏任务------------%@", [NSThread currentThread]);
    });
    NSLog(@"栅栏结束 - %@", [NSThread currentThread]);
    
    dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"延迟2s的任务2 - %@", [NSThread currentThread]);
    });
    NSLog(@"第二次结束 - %@", [NSThread currentThread]);
}
- (void)cjl_testBarrier2{
    //并发队列使用栅栏函数
    
    dispatch_queue_t queue = dispatch_queue_create("CJL", DISPATCH_QUEUE_CONCURRENT);
    
    NSLog(@"开始 - %@", [NSThread currentThread]);
    dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"延迟2s的任务1 - %@", [NSThread currentThread]);
    });
    NSLog(@"第一次结束 - %@", [NSThread currentThread]);
    
    //由于并发队列异步执行任务是乱序执行完毕的,所以使用栅栏函数可以很好的控制队列内任务执行的顺序
    dispatch_barrier_async(queue, ^{
        NSLog(@"------------栅栏任务------------%@", [NSThread currentThread]);
    });
    NSLog(@"栅栏结束 - %@", [NSThread currentThread]);
    
    dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"延迟2s的任务2 - %@", [NSThread currentThread]);
    });
    NSLog(@"第二次结束 - %@", [NSThread currentThread]);
}

重点观察并发队列中使用栅栏函数。会在栅栏前的任务完成后,才执行栅栏后的任务。

dispatch_barrier_sync

  • 会阻塞当前线程,看打印结果可知,"start"一定在dispatch_barrier_sync函数前执行,"end"一定在dispatch_barrier_sync函数后执行
  • dispatch_barrier_sync 函数添加的block,在当前线程执行
  • 会将传入dispatch_barrier_sync函数的线程myQueue中的任务,通过dispatch_barrier_sync函数位置把前后分隔开。

dispatch_barrier_async

  • 不会阻塞当前线程,"start","end"与dispatch_barrier_async方法是同步执行的,执行顺序不定。
  • dispatch_barrier_sync函数添加的block,在新开的线程执行
  • 会将传入dispatch_barrier_async函数的线程myQueue中的任务,通过dispatch_barrier_async函数位置把前后分隔开。

这也是为什么栅栏结束会出现在栅栏任务之前。

相关推荐
秃然想通10 小时前
mac电脑搭载c、c++环境(基于vs code)
macos
啊啊啊~~17 小时前
新mac电脑软件安装指南(前端开发用)
macos·node·n·oh my zsh·solarized
lm down17 小时前
ComfyUI中运行Wan 2.1工作流,电影级视频,兼容Mac, Windows
人工智能·macos·ai作画·视频
天安彩17 小时前
mac下 vscode 运行 c++无法弹出窗口
c++·vscode·macos·clang
太阳伞下的阿呆17 小时前
配置Mac/Linux终端启动执行脚本
linux·运维·macos
游戏开发爱好者818 小时前
没有 Mac,如何上架 iOS App?多项目复用与流程标准化实战分享
android·ios·小程序·https·uni-app·iphone·webview
芦苇Z19 小时前
macOS 安装应用软件的三种方式
macos
Digitally1 天前
如何将荣耀手机的照片传输到 Mac
macos·智能手机
Digitally1 天前
如何将 iPhone 备份到 Mac/MacBook
macos·ios·iphone