进程与线程:GCD

30 4月
  • NSThread
  • Runloop
  • 线程间通信
  • 线程间同步
  • GCD
  • NSOperation

NSThread,Runloop,线程间通信,线程间同步

点这里

直接使用NSThread可能会有问题:

// 在线程里请求网络图片
NSThread *downloadImageThread = [[NSThread alloc] initWithBlock:^{
    UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:item.picUrl]]];
    self.rightImageView.image = image;   // 会有警告,在非主线程中操作UI
}];
downloadImageThread.name = @"downloadImageThread";
[downloadImageThread start];

上面代码会有警告,因为在在非主线程中操作了UI,所以更推荐GCD。

GCD

GCD(Grand Central Dispatch)是个自动利用CPU的高性能多线程解决方案。能自动管理分配线程池。当我们的列表请求很多图片时,每个请求网络图片的地方都用NSThread申请线程,其实也是一种浪费。

使用GCD会将我们的网络图片请求视为任务,加入到任务队列。从预先申请好的线程池中用空闲线程执行队列任务。

上述图片下载可以改用GCD:

// 非主队列网络请求
dispatch_queue_global_t downloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_main_t mainQueue = dispatch_get_main_queue();  // 主队列中更新UI

dispatch_async(downloadQueue, ^{
    UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:item.picUrl]]];
    dispatch_async(mainQueue, ^{
        self.rightImageView.image = image;
    });
});

GCD有三种队列:

  1. 主线程有对应的主队列:dispatch_get_main_queue
  2. 非主线程有4个优先级的全局队列:High/Default/Low/Background:dispatch_get_global_queue
  3. 自定义用户队列,可以指定是串行还是并行:dispatch_queue_create

整体关系图:

GCD会在合适的时机在线程池中为我们选择合适的线程来执行任务。执行代码方式有:

dispatch_async          // 异步执行
dispatch_sync           // 同步执行
dispatch_after          // 延迟xxx毫秒后执行
dispatch_once           // 保证在整个APP生命周期内只执行一次,常用于创建单例
dispatch_source         // 事件源,自定义触发和监听
dispatch_group_xxx      // 管理一组GCD操作
dispatch_semaphore_xxx  // 信号量,用于线程间同步
dispatch_barrier_xxx    // 并发队列中的同步点

dispatch_asyncdispatch_syncdispatch_after比较简单:

- (void)testAsync
{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(2);
        NSLog(@"%@", @"in global queue");
    });
    NSLog(@"%@", @"in main queue");
    // 依次输出in main queue,in global queue
}

- (void)testSync
{
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"%@", @"in global queue");
        sleep(2);
    });
    NSLog(@"%@", @"in main queue");
    // 依次输出in global queue,in main queue
}

- (void)testAfter
{
    // 系统时间,APP进入后台后会暂停计时
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC);
    dispatch_after(time, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"wait 10 seconds");  // 等待10s后输出
    });
    
    // 墙上时间,不受后台限制
    dispatch_time_t wallTime = dispatch_walltime(DISPATCH_TIME_NOW, 15 * NSEC_PER_SEC);
    dispatch_after(wallTime, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"wait 15 seconds");  // 等待15s后输出
    });
}

dispatch_once通常用于单例设计:

#import "GCDSingleton.h"

@implementation GCDSingleton

+ (instancetype)sharedInstance
{
    static GCDSingleton *sharedInstance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

- (void)printAddress
{
    NSLog(@"<%@: %p>", NSStringFromClass([self class]), self);
}

@end

// xxxx.m
- (void)testOnce
{
    [[GCDSingleton sharedInstance] printAddress];
}

dispatch_group_xxx用于管理一组GCD操作,有两种执行方式:异步模式会先执行notify下面的主线程代码,两个线程里的任务之后执行。同步模式会等两个线程都执行完,再执行wait下面的主线程代码。一图胜千言:

dispatch_group_async 
dispatch_group_create
dispatch_group_enter
dispatch_group_leave
dispatch_group_notify // 唤醒接下去要执行的任务
dispatch_group_wait

// 异步模式
- (void)testGCDGroupNotify
{
    dispatch_group_t group = dispatch_group_create();

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"%@", @"任务代码1");
    });

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"%@", @"任务代码2");
    });

    dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"%@", @"group任务完成");
    });
    NSLog(@"%@", @"主线程");
    // 依次输出:主线程,任务代码1/2(12执行顺序不一定),group任务完成
}

// 同步模式
- (void)testGroupWait
{
    dispatch_group_t group = dispatch_group_create();
    
    Data *myData = malloc(sizeof(Data));
    myData->number = 10;

    // 绑定context
    dispatch_set_context(group, myData);
    // 设置finalizer函数,用于在队列执行完成后释放对应context内存
    dispatch_set_finalizer_f(group, cleanStaff);
    
    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
        NSLog(@"%@", @"任务代码1");
        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"%@", @"任务代码2");
        dispatch_group_leave(group);
    });

    NSLog(@"%@", @"任务分发完成");
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"%@", @"主线程");
    // 依次输出:任务分发完成,任务代码1/2(12执行顺序不一定),主线程
}

dispatch_semaphore_xxx用于线程间同步,可以类比餐厅吃饭,当客人吃完后发出signal表示离开,服务员收到signal就可以放下一个客人进来

dispatch_semaphore_create
dispatch_semaphore_signal:发送信号量,信号量+1
dispatch_semaphore_wait:如果信号总量为0,进入等待状态,信号量大于0时,继续执行代码,同时将信号总量-1

@property (nonatomic) NSMutableArray *testArray;

- (void)testGCDSemaphore
{
    [self.testArray removeAllObjects];
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    //任务1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

        for (NSInteger i = 0; i < 5; i++) {
            NSLog(@"task 1 add: %ld", (long)i);
            [self.testArray addObject:@(i)];
            sleep(1);
        }
        dispatch_semaphore_signal(semaphore);

    });
    //任务2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

        for (NSInteger i = 0; i < 3; i++) {
            NSLog(@"task 2 add: %ld", (long)i);
            [self.testArray addObject:@(i)];
            sleep(1);
        }
        dispatch_semaphore_signal(semaphore);

    });
    //任务3
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        
        for (NSInteger i = 0; i < 3; i++) {
            NSLog(@"task 3 add: %ld", (long)i);
            [self.testArray addObject:@(i)];
            sleep(1);
        }
        dispatch_semaphore_signal(semaphore);
        
    });
    // task1(依次输出0~4),task2(依次输出0~2),task3(依次输出0~2)是乱序的,3个task不保证顺序
}

dispatch_barrier_xxx栅栏,就是栅栏前的任务必须全部完成后,才能进入栅栏后的任务。常见于读写锁

dispatch_barrier_async (dispatch_barrier_async_f)
dispatch_barrier_sync (dispatch_barrier_sync_f)

其他常用方法:

dispatch_get_current_queue  // 获取当前队列
dispatch_queue_get_label    // 返回创建队列时为队列指定的标签
dispatch_set_target_queue   // 为给定的对象设置目标队列

// GCD-队列设置数据和清理
dispatch_set_context
dispatch_get_context
dispatch_set_finalizer_f

// GCD-queue
dispatch_suspend // 不能用于主队列(其实技术上可以,但千万不要视图这么做)和全局队列,只能用于用户队列,而且当前执行的任务不会被挂起
dispatch_resume  // 和suspend必须成对使用

例如用dispatch_set_target_queue设置目标队列:

- (void)testGCDTargetQueue
{
    // 自定义串行队列
    dispatch_queue_t serialQueue = dispatch_queue_create("com.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
    // 自定义并行队列
    dispatch_queue_t firstQueue = dispatch_queue_create("com.gcddemo.firstqueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t secondQueue = dispatch_queue_create("com.gcddemo.secondqueue", DISPATCH_QUEUE_CONCURRENT);
    
    // 注释掉这两行,输出就是乱序的。有了这两行,就按1~6的顺序依次输出
    dispatch_set_target_queue(firstQueue, serialQueue);
    dispatch_set_target_queue(secondQueue, serialQueue);

    dispatch_async(firstQueue, ^{
        NSLog(@"1");
        [NSThread sleepForTimeInterval:3.f];
    });
    dispatch_async(secondQueue, ^{
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"2");
    });
    dispatch_async(secondQueue, ^{
        [NSThread sleepForTimeInterval:1.f];
        NSLog(@"3");
    });
    dispatch_async(secondQueue, ^{
        [NSThread sleepForTimeInterval:1.f];
        NSLog(@"4");
    });
    dispatch_async(secondQueue, ^{
        [NSThread sleepForTimeInterval:1.f];
        NSLog(@"5"); 
    });
    dispatch_async(secondQueue, ^{
        [NSThread sleepForTimeInterval:1.f];
        NSLog(@"6");
    });
}

NSOperation

点这里

发表评论

电子邮件地址不会被公开。 必填项已用*标注