【iOS多线程(二)】GCD其他方法详解

14 篇文章 0 订阅
2 篇文章 0 订阅

dispatch_semaphore (信号量)

什么是dispatch_semaphore(信号量)?

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

dispatch_semaphore主要的三个方法

GCD信号量机制主要涉及到以下三个函数:

// 创建信号量,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主要作用

  • 保持线程同步,将异步执行任务转换为同步执行任务
  • 保证线程安全,为线程加锁
  1. 异步转同步
  • 众所周知并发队列中的任务,由异步线程执行的顺序是不确定的,两个任务分别由两个线程执行,很难控制哪个任务先执行完,哪个任务后执行完。但有时候确实有这样的需求:两个任务虽然是异步的,但仍需要同步执行。

实际案例:
比如说:AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法。通过引入信号量的方式,等待异步执行任务结果,获取到 tasks,然后再返回该 tasks。

- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
    __block NSArray *tasks = nil;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
            tasks = dataTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
            tasks = uploadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
            tasks = downloadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
            tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
        }

        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    return tasks;
}

使用举例:


//信号量初始化必须大于等于0, 因为dispatch_semaphore_wait 执行的是-1操作。
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);
});

//当当前的信号量值为0时候会阻塞线,如果大于0的话,信号量-1,不阻塞线程.(相当于加锁)
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");
    dispatch_semaphore_signal(semaphore);
});

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

  1. 设置最大开辟的线程数
  • 我们要下载很多图片,并发异步进行,每个下载都会开辟一个新线程,可是我们又担心太多线程肯定cpu吃不消,那么我们这里也可以用信号量控制一下最大开辟线程数。

这里出现优先级反转了


//设置最大开辟的线程数为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(1);
            NSLog(@"完成当前任务 %ld", i);
            //释放信号
            dispatch_semaphore_signal(semaphore);
        });
    }

执行结果:

执行任务 0
执行任务 2
执行任务 1
完成当前任务 2
完成当前任务 1
完成当前任务 0
执行任务 3
执行任务 4
执行任务 5
完成当前任务 3
完成当前任务 5
完成当前任务 4
执行任务 6
执行任务 8
执行任务 7
完成当前任务 7
完成当前任务 8
完成当前任务 6
执行任务 9
完成当前任务 9

执行结果表明:最开始执行3个异步操作,剩下的同步等待,只有释放出来才可以剩下的操作才可以进行异步操作

  1. 加锁机制
  • 当你的信号设置为1的时候就相当于加锁

- (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_semaphore 机制之后,得到的票数是正确的,没有出现混乱的情况。我们也就解决了多个线程同步的问题。

线程安全

线程安全
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作(更改变量),一般都需要考虑线程同步,否则的话就可能影响线程安全。

线程同步

线程同步:可理解为线程 A 和 线程 B 一块配合,A 执行到一定程度时要依靠线程 B 的某个结果,于是停下来,示意 B 运行;B 依言执行,再将结果给 A;A 再继续操作。

举个简单例子就是:两个人在一起聊天。两个人不能同时说话,避免听不清(操作冲突)。等一个人说完(一个线程结束操作),另一个再说(另一个线程再开始操作)。

(经典火车票售卖模型)

dispatch_after

我们经常会遇到这样的需求:在指定时间(例如 3 秒)之后执行某个任务。可以用 GCD 的dispatch_after 方法来实现。
需要注意的是:dispatch_after 方法并不是在指定时间之后才开始执行处理,而 是在指定时间之后将任务追加到主队列中 。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after 方法是很有效的。


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

dispatch_time_t 两种形式

  1. 基于dispatch_time函数,表示从当前时间开始的一个时间点

dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);

使用:

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");
});

时间单位:
#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位无符号长整形

  1. 基于diapatch_walltime函数,表示根据当前设备硬件的绝对时间

dispatch_time_t dispatch_walltime(const struct timespec *_Nullable when, int64_t delta);

dispatch_time_t walltime = dispatch_walltime(DISPATCH_TIME_NOW, (int64_t)(60 * NSEC_PER_SEC));
dispatch_after(walltime, dispatch_get_main_queue(), ^{
    NSLog(@"dispatch_walltime");
});

一般来讲客户端是不相信本地硬件时钟的,所以通常使用dispatch_time就好了

GCD 一次性代码(只执行一次):dispatch_once

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


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

GCD 快速迭代方法:dispatch_apply

通常我们会用 for 循环遍历,但是 GCD 给我们提供了快速迭代的方法 dispatch_apply。dispatch_apply 按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。

如果是在串行队列中使用 dispatch_apply,那么就和 for 循环一样,按顺序同步执行。但是这样就体现不出快速迭代的意义了。

我们可以利用并发队列进行异步执行。比如说遍历 0~5 这 6 个数字,for 循环的做法是每次取出一个元素,逐个遍历。dispatch_apply 可以 在多个线程中同时(异步)遍历多个数字。

还有一点,无论是在串行队列,还是并发队列中,dispatch_apply 都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的 dispatch_group_wait方法。


/**
 * 快速迭代方法 dispatch_apply
 */
- (void)apply {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    NSLog(@"apply---begin");
    dispatch_apply(6, queue, ^(size_t index) {
        NSLog(@"%zd---%@",index, [NSThread currentThread]);
    });
    NSLog(@"apply---end");
}

在这里插入图片描述

这里dispatch_apply函数中

  1. 参数一:传的时循环次数
  2. 参数二:传派发队列进去。
    • 如果传并发队列,就是并发的执行6次,开多少线程由操作系统决定
    • 如果传主队列(当前串行队列),会死锁
    • 如果传一个新创建的串行队列,等于同步循环执行,从0-5挨个执行(跟for循环没区别了)

因为是在并发队列中异步执行任务,所以各个任务的执行时间长短不定,最后结束顺序也不定。但是 apply—end 一定在最后执行。这是因为 dispatch_apply 方法会等待全部任务执行完毕。

有一个实际的应用场景:处理图像中的像素点时,并行执行:


- (UIImage *)applyGrayscaleFilterToImage:(UIImage *)image {
    // 获取图像的宽度和高度
    CGSize size = image.size;
    CGFloat width = size.width;
    CGFloat height = size.height;

    // 创建一个新的 bitmap 图像上下文
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 4 * width, colorSpace, kCGImageAlphaPremultipliedFirst);
    CGColorSpaceRelease(colorSpace);

    // 在上下文上绘制原始图像
    CGRect rect = CGRectMake(0, 0, width, height);
    CGContextDrawImage(context, rect, image.CGImage);

    // 使用 dispatch_apply 并行处理每个像素
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(width * height, queue, ^(size_t index) {
        // 计算当前像素的坐标
        size_t x = index % (size_t)width;
        size_t y = index / (size_t)width;

        // 获取当前像素的 RGBA 值
        uint8_t *pixelData = (uint8_t *)CGBitmapContextGetData(context) + y * CGBitmapContextGetBytesPerRow(context) + x * 4;
        uint8_t red = pixelData[0];
        uint8_t green = pixelData[1];
        uint8_t blue = pixelData[2];

        // 计算灰度值并更新像素
        uint8_t gray = (uint8_t)((red * 0.3 + green * 0.59 + blue * 0.11));
        pixelData[0] = gray;
        pixelData[1] = gray;
        pixelData[2] = gray;
    });

    // 从上下文中创建一个新的 UIImage 并返回
    CGImageRef newImage = CGBitmapContextCreateImage(context);
    UIImage *filteredImage = [UIImage imageWithCGImage:newImage];
    CGImageRelease(newImage);
    CGContextRelease(context);

    return filteredImage;
}

GCD 队列组:dispatch_group

用途:我希望等几个任务执行完毕后,再执行最后一个任务时。(控制执行顺序)

有时候我们会有这样的需求:分别异步执行2个耗时任务,然后当2个耗时任务都执行完毕后,再回到主线程执行结束处理。这时候我们可以用到 GCD 的队列组。

使用队列组的 dispatch_group_enterdispatch_group_leave 组合来实现 dispatch_group_async


- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 创建一个队列组
    dispatch_group_t group = dispatch_group_create();

    // 获取用户信息
    dispatch_group_enter(group);
    [self fetchUserInfo:^(UserInfo *userInfo) {
        // 处理用户信息
        NSLog(@"User info: %@", userInfo);
        dispatch_group_leave(group);
    }];

    // 获取订单列表
    dispatch_group_enter(group);
    [self fetchOrderList:^(NSArray *orders) {
        // 处理订单列表
        NSLog(@"Orders: %@", orders);
        dispatch_group_leave(group);
    }];

    // 获取优惠券列表
    dispatch_group_enter(group);
    [self fetchCouponList:^(NSArray *coupons) {
        // 处理优惠券列表
        NSLog(@"Coupons: %@", coupons);
        dispatch_group_leave(group);
    }];

    // 等待所有任务完成
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 所有数据获取完成,进行最终处理
        NSLog(@"All data fetched!");
    });
}

调用队列组的 dispatch_group_async 先把任务放到队列中,然后将队列放入队列组中


// 创建一个队列组
dispatch_group_t group = dispatch_group_create();

// 获取用户信息
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [fetcher fetchUserInfo:^(UserInfo *userInfo) {
        // 处理用户信息
        NSLog(@"User info: %@", userInfo);
    }];
});

// 获取订单列表
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [fetcher fetchOrderList:^(NSArray<Order *> *orders) {
        // 处理订单列表
        NSLog(@"Orders: %@", orders);
    }];
});

// 获取优惠券列表
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [fetcher fetchCouponList:^(NSArray<Coupon *> *coupons) {
        // 处理优惠券列表
        NSLog(@"Coupons: %@", coupons);
    }];
});

// 等待所有任务完成
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 所有数据获取完成,进行最终处理
    NSLog(@"All data fetched!");
});

//或者用下面这个:
// 等待所有任务完成
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC));
if (dispatch_group_wait(group, timeout) == 0) {
    // 所有数据获取完成,进行最终处理
    NSLog(@"All data fetched!");
} else {
    // 超时,可以执行一些错误处理逻辑
    NSLog(@"Timeout waiting for data");
}

或者:
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);  //永久阻塞

dispatch_group_notify 不同,dispatch_group_wait 是一个同步操作,会阻塞当前线程,直到所有任务完成或者超时。在这里,我们设置了一个 10 秒的超时时间,如果在 10 秒内所有任务都完成,则打印 “All data fetched!”。如果超时,则打印 “Timeout waiting for data”。

这种实现方式更加简单,但可能会导致主线程被阻塞,影响用户体验。如果需要更好的用户体验,可以考虑使用 dispatch_group_notify 的异步方式。

dispatch_group_notify

  • 监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。

dispatch_group_wait

  • 暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。

dispatch_group_enter、dispatch_group_leave

  • dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1
  • dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1。
  • 当 group 中未执行完毕任务数为0的时候,才会使 dispatch_group_wait 解除阻塞,以及执行追加到 dispatch_group_notify 中的任务。

dispatch_barrier_async/sync栅栏方法

多读单写问题:

多读单写

解决方法一:栅栏方法
解决方法二:读写锁

#import <Foundation/Foundation.h>
 
@interface ZYPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
 
 
#import "ZYPerson.h"
 
@interface ZYPerson ()
@end
 
static NSString *_name;
static dispatch_queue_t _concurrentQueue;
@implementation ZYPerson
- (instancetype)init
{
    if (self = [super init]) {
       _concurrentQueue = dispatch_queue_create("com.person.syncQueue", DISPATCH_QUEUE_CONCURRENT);
    }
    return self;
}
- (void)setName:(NSString *)name
{
    dispatch_barrier_async(_concurrentQueue, ^{
        _name = [name copy];
    });
}
- (NSString *)name
{
    __block NSString *tempName;
    dispatch_sync(_concurrentQueue, ^{
        tempName = _name;
    });
    return tempName;
}
@end

- (void)viewDidLoad {
    
    ZYPerson *obj = [[ZYPerson alloc] init];
    obj.name = @"aaa";
    
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(concurrentQueue, ^{
        NSString *tmp = [obj name];
        NSLog(@"1 - %@", tmp);
    });
    
    dispatch_async(concurrentQueue, ^{
        NSString *tmp = [obj name];
        NSLog(@"2 - %@", tmp);
    });
    
    dispatch_async(concurrentQueue, ^{
        NSString *tmp = [obj name];
        NSLog(@"3 - %@", tmp);
    });
    
    dispatch_barrier_async(concurrentQueue, ^{
        [obj setName:@"bbb"];
        NSLog(@"%@", obj.name);
    });
    
    dispatch_async(concurrentQueue, ^{
        NSString *tmp = [obj name];
        NSLog(@"4 - %@", tmp);
    });
    
    dispatch_async(concurrentQueue, ^{
        NSString *tmp = [obj name];
        NSLog(@"5 - %@", tmp);
    });
    
    dispatch_async(concurrentQueue, ^{
        NSString *tmp = [obj name];
        NSLog(@"6 - %@", tmp);
    });
    
}

在这里插入图片描述

这里有一个很关键的理解:读操作的内部实现这里,使用的是dispatch_sync同步函数,不是异步函数。这里使用同步函数是为了保证读操作的原理性。并不影响读操作可以同时异步进行。读操作时同步还是异步,取决于将set方法是使用同步方法还是使用异步方法,加入到派发队列的。

dispatch_barrier_async/sync 的区别

  1. dispatch_barrier_async

- (void) barrier {
    NSLog(@"start");
    
    dispatch_queue_t myQueue = dispatch_queue_create("ff", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(myQueue, ^{
        NSLog(@"%@--1", [NSThread currentThread]);
    });
    dispatch_async(myQueue, ^{
           NSLog(@"%@--2", [NSThread currentThread]);
       });
    
    dispatch_barrier_sync(myQueue, ^{
        NSLog(@"+++barrier ++++++");
    });
    NSLog(@"++++++++++++barrier 之后");
    
    dispatch_async(myQueue, ^{
          NSLog(@"%@--3", [NSThread currentThread]);
      });
      dispatch_async(myQueue, ^{
             NSLog(@"%@--4", [NSThread currentThread]);
         });
    NSLog(@"last");
}

打印结果:


2020-06-20 08:31:08.892663+0800 OCTestFirst[6439:172483] start
2020-06-20 08:31:08.893001+0800 OCTestFirst[6439:172646] <NSThread: 0x600002a48440>{number = 6, name = (null)}--2
2020-06-20 08:31:08.893007+0800 OCTestFirst[6439:172648] <NSThread: 0x600002a4aa40>{number = 5, name = (null)}--1
2020-06-20 08:31:08.893166+0800 OCTestFirst[6439:172483] +++barrier ++++++
2020-06-20 08:31:08.893274+0800 OCTestFirst[6439:172483] ++++++++++++barrier 之后
2020-06-20 08:31:08.893375+0800 OCTestFirst[6439:172483] last
2020-06-20 08:31:08.893430+0800 OCTestFirst[6439:172646] <NSThread: 0x600002a48440>{number = 6, name = (null)}--4
2020-06-20 08:31:08.893488+0800 OCTestFirst[6439:172648] <NSThread: 0x600002a4aa40>{number = 5, name = (null)}--3

  1. ** dispatch_barrier_async**

- (void) barrier {
    NSLog(@"start");
    
    dispatch_queue_t myQueue = dispatch_queue_create("ff", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(myQueue, ^{
        NSLog(@"%@--1", [NSThread currentThread]);
    });
    dispatch_async(myQueue, ^{
           NSLog(@"%@--2", [NSThread currentThread]);
       });
    
    dispatch_barrier_async(myQueue, ^{
        NSLog(@"+++barrier ++++++");
    });
    NSLog(@"++++++++++++barrier 之后");
    
    dispatch_async(myQueue, ^{
          NSLog(@"%@--3", [NSThread currentThread]);
      });
      dispatch_async(myQueue, ^{
             NSLog(@"%@--4", [NSThread currentThread]);
         });
    NSLog(@"last");
}

打印结果:


2020-06-20 08:34:09.223552+0800 OCTestFirst[6485:174586] start
2020-06-20 08:34:09.223936+0800 OCTestFirst[6485:174586] ++++++++++++barrier 之后
2020-06-20 08:34:09.224071+0800 OCTestFirst[6485:174586] last
2020-06-20 08:34:09.225737+0800 OCTestFirst[6485:175133] <NSThread: 0x60000371bb00>{number = 10, name = (null)}--2
2020-06-20 08:34:09.225876+0800 OCTestFirst[6485:174911] <NSThread: 0x600003702440>{number = 8, name = (null)}--1
2020-06-20 08:34:09.226888+0800 OCTestFirst[6485:175133] +++barrier ++++++
2020-06-20 08:34:09.227540+0800 OCTestFirst[6485:175133] <NSThread: 0x60000371bb00>{number = 10, name = (null)}--3
2020-06-20 08:34:09.227540+0800 OCTestFirst[6485:174911] <NSThread: 0x600003702440>{number = 8, name = (null)}--4

分析以上两个函数:
dispatch_barrier_sync

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

** dispatch_barrier_async**

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

dispatch_suspend / dispatch_resume

dispatch_suspend暂时挂起指定dispatch_queue

dispatch_resume回复执行的dispatch_queue

这些函数对已执行的处理没有影响。挂起后,追加到Dispatch Queue中但尚未执行的处理在此之后停止执行。而恢复则使得这些处理能够继续执行。

GCD线程同步方法总结

我们使用GCD的时候如何让线程同步,也有多种方法

1.dispatch_group
2.dispatch_barrier
3.dispatch_semaphore

面试题

1. 多读单写问题

答:

    1. 栅栏方法(dispatch_barrier_async)
    1. 读写锁(pthread_rwlock)
  • 13
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值