iOS:线程终止以及GCD中dispatch_semaphore,dispatch_barrier_async,dispatch_group使用说明

本文介绍GCD中线程并发控制的方法,包括通过dispatch_semaphore_t和NSOperationQueue设置最大并发数,以及如何使用dispatch_barrier_async进行任务隔离。同时探讨了dispatch_group的功能及其在并行任务中的应用。

1,今天更新一下关于线程的一些小知识。

前提:dispatch_get_main_queue()本质是串行队列,dispatch_get_global_queue(0, 0)本质是并行队列

串行队列:DISPATCH_QUEUE_SERIAL 最多开辟一个线程

并行队列:DISPATCH_QUEUE_CONCURRENT 可开启n的线程,n>=0

主队列执行async任务时,会在主线程执行,因为主线程就是主队列开辟的线程。

并行队列执行sync任务时,不会开辟新线程,会在当前线程中执行。

一、设置线程并发量

线程并发量:如果需要加载1000张图片,如果开启1000个子线程,那么CPU可能就忙不过来了,这时我们可以设置最大并发量来更加合理的利用资源。同时只允许10个线程下载,每个任务完成后,让改线程执行下一个任务

两种实现:

1、GCD:通过dispatch_semaphore实现

static dispatch_semaphore_t semaphoreLock;
void testDispatch_semaphore () {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        semaphoreLock = dispatch_semaphore_create(5);
    });
    dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
    //code...
    dispatch_semaphore_signal(semaphoreLock);
}

其中dispatch_semaphore_create(5)中的5就是最大并发数。执行到wait时,会变成4,只到变成0,在有线程来到wait就会阻塞到这里了,要等要某个线程dispatch_semaphore_signal()时,信号就会+1,然后等待的线程就是执行,始终保持着最大线程数是5

2、NSOperationQueue实现

NSOperationQueue有个属性maxConcurrentOperationCount设置最大并发数,和dispatch_semaphore_create一样

NSOperationQueue *queue =[[NSOperationQueue alloc] init];
[queue addOperation:...];
[queue addOperation:...];
queue.maxConcurrentOperationCount = 5;

注意:线程的cancel操作都不能取消正在执行的任务,只能取消在队列中排队的任务。要想取消正在执行的任务,只能通过添加信号量的方式,强制让任务提前return

//线程已经停止,需要跳出循环
if ([_invo isCancelled]) {
    return; // 跳出循环。
}

//或者
if (_stopFlag){
    return;
}

二、GCD常用函数

1、dispatch_barrier_async

dispatch_barrier_async:栅栏化,在栅栏任务前面的任务可以异步执行,等到前面的异步任务执行完了,才会执行栅栏中的任务,栅栏后面的任务也可以异步执行。

dispatch_barrier_async要搭配并发队列才能达到隔离效果,如果使用的是串行队列,那么就会一个线程一个线程执行,没有隔离效果

void testGCD () {
    dispatch_queue_t queue = dispatch_queue_create("ob", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"start -- %@",[NSThread currentThread]);
        sleep(2);
        NSLog(@"end -- %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"start -- %@",[NSThread currentThread]);
        sleep(2);
        NSLog(@"end -- %@",[NSThread currentThread]);
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"start -- %@",[NSThread currentThread]);
        sleep(4);
        NSLog(@"end -- %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"start -- %@",[NSThread currentThread]);
        sleep(2);
        NSLog(@"end -- %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"start -- %@",[NSThread currentThread]);
        sleep(2);
        NSLog(@"end -- %@",[NSThread currentThread]);
    });
}

打印如下:
 

2020-07-09 14:01:27.801176+0800 start -- <NSThread: 0x1005156d0>{number = 3, name = (null)}
2020-07-09 14:01:27.801179+0800 start -- <NSThread: 0x10063a510>{number = 2, name = (null)}
2020-07-09 14:01:29.806139+0800 end -- <NSThread: 0x1005156d0>{number = 3, name = (null)}
2020-07-09 14:01:29.806139+0800 end -- <NSThread: 0x10063a510>{number = 2, name = (null)}
2020-07-09 14:01:29.806349+0800 start -- <NSThread: 0x1005156d0>{number = 3, name = (null)}
2020-07-09 14:01:33.811750+0800 end -- <NSThread: 0x1005156d0>{number = 3, name = (null)}
2020-07-09 14:01:33.811914+0800 start -- <NSThread: 0x1005156d0>{number = 3, name = (null)}
2020-07-09 14:01:33.811915+0800 start -- <NSThread: 0x10063a510>{number = 2, name = (null)}
2020-07-09 14:01:35.816482+0800 end -- <NSThread: 0x10063a510>{number = 2, name = (null)}
2020-07-09 14:01:35.816497+0800 end -- <NSThread: 0x1005156d0>{number = 3, name = (null)}

二、dispatch_group

  1. dispatch_group_enter :通知 group,下个任务要放入 group 中执行了
  2. dispatch_group_leave: 通知 group,任务成功完成,要移除,与 enter成对出现
  3. dispatch_group_wait: 在任务组完成时调用,或者任务组超时是调用(完成指的是enter和leave次数一样多)
  4. dispatch_group_notify: 只要任务全部完成了,就会在最后调用

其中,dispatch_group_notify安全起见要使用dispatch_group_wait,等到前面的执行完了才行,同样的只能适应在并行队列。串行队列按顺序执行,并且一个线程执行所有任务。即使使用async也不会创建多线程。

void testGroup () {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("ob", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_notify(group, queue, ^{
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        NSLog(@"notify -- %@",[NSThread currentThread]);
    });
    
//    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        NSLog(@"start -- %@",[NSThread currentThread]);
        sleep(5);
        NSLog(@"end -- %@",[NSThread currentThread]);
//        dispatch_group_leave(group);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"start -- %@",[NSThread currentThread]);
        sleep(1);
        NSLog(@"end -- %@",[NSThread currentThread]);
    });
  
    dispatch_group_async(group, queue, ^{
        NSLog(@"start -- %@",[NSThread currentThread]);
        sleep(3);
        NSLog(@"end -- %@",[NSThread currentThread]);
    });
  
}


后期更新中。。。

iOS 开发中,处理图片加载尤其是从相册中加载大量图片时,通常会涉及到多线程操作,以避免阻塞主线程并提高用户体验。`dispatch_async` 和 `TZImageManager` 是实现这一目标的重要工具。以下是对使用 `dispatch_async` 和 `TZImageManager` 实现多线程加载相册数据的代码逻辑进行分析与优化的详细说明。 ### 图片管理与多线程加载的基本逻辑 `TZImageManager` 是一个封装了图片获取与缓存的类,通常用于从相册中读取图片资源。为了防止主线程阻塞,图片的获取和处理通常会使用 `dispatch_async` 将任务提交到后台队列执行。例如: ```objective-c dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 使用 TZImageManager 获取图片 UIImage *image = [tzImageManager getImage:asset]; dispatch_async(dispatch_get_main_queue(), ^{ // 在主线程更新 UI self.imageView.image = image; }); }); ``` 上述代码通过 `dispatch_async` 将图片获取任务提交到全局队列中执行,确保不会阻塞主线程。当图片获取完成后,再次使用 `dispatch_async` 将 UI 更新操作提交到主队列执行,以保证 UI 操作在主线程完成。 ### 优化点分析与建议 #### 1. **使用合适的队列优先级** 在使用 `dispatch_get_global_queue` 时,可以通过指定队列的优先级来优化任务的执行顺序。例如: ```objective-c dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); ``` 将图片加载任务分配到低优先级队列,可以避免影响其他关键任务的执行,同时也能减少对主线程的干扰。 #### 2. **限制并发任务数量** 在加载大量图片时,可能会创建过多的异步任务,导致内存占用过高。可以通过使用 `NSOperationQueue` 或者 `dispatch_semaphore_t` 来限制并发任务的数量。例如,使用信号量控制并发数: ```objective-c dispatch_semaphore_t semaphore = dispatch_semaphore_create(3); // 最多同时执行 3 个任务 for (PHAsset *asset in assets) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); UIImage *image = [tzImageManager getImage:asset]; dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; dispatch_semaphore_signal(semaphore); }); }); } ``` 上述代码通过信号量限制了并发执行的任务数量,从而减少内存压力。 #### 3. **优化图片加载与缓存机制** `TZImageManager` 内部通常已经实现了图片缓存机制,但在实际使用中,可以通过自定义缓存策略进一步优化。例如,使用 `NSCache` 或 `SDURLCache` 来缓存已经加载过的图片,避免重复加载。 ```objective-c @interface TZImageManager () @property (nonatomic, strong) NSCache<NSString *, UIImage *> *imageCache; @end @implementation TZImageManager - (instancetype)init { self = [super init]; if (self) { _imageCache = [[NSCache alloc] init]; } return self; } - (UIImage *)getImage:(PHAsset *)asset { NSString *cacheKey = asset.localIdentifier; UIImage *cachedImage = [self.imageCache objectForKey:cacheKey]; if (cachedImage) { return cachedImage; } // 从相册中加载图片 UIImage *image = [self loadFromAsset:asset]; [self.imageCache setObject:image forKey:cacheKey]; return image; } @end ``` 通过缓存机制,可以显著减少重复加载图片的开销,提高应用性能。 #### 4. **使用 `dispatch_barrier_async` 保证线程安全** 当多个线程同时访问共享资源(如缓存)时,可能会导致数据竞争问题。为了避免这种情况,可以使用 `dispatch_barrier_async` 确保写入操作的线程安全性: ```objective-c dispatch_queue_t cacheQueue = dispatch_queue_create("com.example.cacheQueue", DISPATCH_QUEUE_CONCURRENT); - (void)cacheImage:(UIImage *)image forKey:(NSString *)key { dispatch_barrier_async(cacheQueue, ^{ [self.imageCache setObject:image forKey:key]; }); } - (UIImage *)getImageForKey:(NSString *)key { __block UIImage *cachedImage = nil; dispatch_sync(cacheQueue, ^{ cachedImage = [self.imageCache objectForKey:key]; }); return cachedImage; } ``` 通过 `dispatch_barrier_async` 和 `dispatch_sync`,可以确保缓存操作的线程安全,避免数据竞争问题。 #### 5. **避免频繁切换线程** 在多线程环境中,频繁地在后台线程和主线程之间切换可能会引入额外的性能开销。因此,在处理图片数据时,应尽量减少不必要的线程切换。例如,在后台线程完成图片处理后,可以直接将处理好的图片传递给主线程进行 UI 更新,而不是在后台线程中多次切换线程。 ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值