IOS多线程的一些总结

一直觉得多线程是一块难啃的骨头,倒不是因为他有多难,只是因为心里想这很难,在平时的学习和工作中总会有意无意的避开多线程的使用。今天写这篇文章倒不是因为掌握的多好,只是对多线程这块知识的随笔,下次看到也不至于太陌生。

IOS中实现多线程有三种方式:NSTHread,NSOperationQueue以及GCD(Grand Central Dispatch)。

GCD:

以优化的应用程序支持多核心处理器和其他的对称多处理系统的系统。这建立在任务并行执行的线程池模式的基础上的。它首次发布在Mac OS X 10.6 ,iOS 4及以上也可用。

串行队列:

IOS多线程的一些总结_第1张图片

 GCD 一次只执行一个任务,并且按照我们添加到队列的顺序来执行。因为同一时间不可能有两个任务访问临界区,所以不要担心临界区的数据安全问题;

1、常用的方法dispatch_async

用于处理比较耗时的操作,如下载、读取网络信息,等到完成之后在主线程中改变界面,注意一切要改变界面的操作最好在主线程中完成;这样做我们不会因为耗时操作卡在一个界面上,带来更好的用户体验。IOS多线程的一些总结_第2张图片

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
    // 耗时的操作  
    dispatch_async(dispatch_get_main_queue(), ^{  
        // 更新界面  
    });  
});  
以上代码是将任务从主线程移到全局线程。因为这是一个 dispatch_async() ,Block 会被异步地提交,提交之后继续执行下面的代码,并不会等待block里面的代码直接完毕。

2、dispatch_group_async的使用

GCD中可以将一组相关联的操作,定义到一个群组中,定义到群组中的操作可以当所有线程全部完成之后获得通知,这里举个例子,一个阅读软件,用户批量下载了很多本书,你不可能一本下完提醒一次,自然应该全部下完再提醒用户;

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        [self gcdSaleTicketWithName:@"gcd-1"];
    });
    dispatch_group_async(group, queue, ^{
        [self gcdSaleTicketWithName:@"gcd-2"];
    });
    dispatch_group_async(group, queue, ^{
        [self gcdSaleTicketWithName:@"gcd-3"];
    });
    //等的时间长的执行次数少,等待时间短的执行次数多
    //群组任务完成接收通知
    dispatch_group_notify(group, queue, ^{
        NSLog(@"卖完了所有的票");
    });

3、dispatch_barrier_async的使用

假如你创建一个NSMutableArray数组,一个方法在读取数据,而同时另一个方法在修改数组内容,这无疑是不安全的。我们可以通过创建一个阻塞池,等到池中的任务执行完成才让别的任务执行

图解如下:

IOS多线程的一些总结_第3张图片

    if (photo) { // 1 
        dispatch_barrier_async(self.concurrentPhotoQueue, ^{ // 2  
            [_photosArray addObject:photo]; // 3 
            dispatch_async(dispatch_get_main_queue(), ^{ // 4 
                [self postContentAddedNotification];  
            }); 
        }); 
    } 

4、dispatch_apply 

相当于for循环的左右,虽然我们可以用这种方法来代替for循环,但是一般来说我们不应该这样做,因为 你创建并行运行线程而付出的开销,很可能比直接使用 for 循环要多。若你要以合适的步长迭代非常大的集合,那才应该考虑使用 dispatch_apply。
    dispatch_apply(3, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(size_t i) { 
 
        NSURL *url; 
        switch (i) { 
            case 0: 
                url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString]; 
                break; 
            case 1: 
                url = [NSURL URLWithString:kSuccessKidURLString]; 
                break; 
            case 2: 
                url = [NSURL URLWithString:kLotsOfFacesURLString]; 
                break; 
            default: 
                break; 
        } 

5、dispatch_once

如创建一个单例对象:
+(Ticket *)sharedTicket
{
    //块代码里面只能使用外面的对象,不能修改外面的对象,要修改必须要加__block
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SharedInstance = [[Ticket alloc]init];
        NSLog(@"ticket address :%@",SharedInstance);
    });
    return SharedInstance;
}

6、dispatch_after


    double delayInSeconds = 3.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        [self reloadArray];
        [refreshControl endRefreshing];
    });

以上代码是一个刷新的动画,我设定在执行3s之后重载数据,移除动漫;

大家还可以参考一下:

GCD 深入理解(一)

GCD 深入理解(二)

NSOperationQueue

首先定义一个strong的变量
可以设置每次执行多少个线程,也可以设置线程间依赖关系也就是控制线程先后顺序

1、设置最大线程数:

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^(){
        NSLog(@"执行第1次操作,线程:%@", [NSThread currentThread]);
    }];
    
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^(){
        NSLog(@"执行第2次操作,线程:%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^(){
        NSLog(@"执行第3次操作,线程:%@", [NSThread currentThread]);
    }];
    [queue setMaxConcurrentOperationCount:2];
    //第三次操作被同步锁挡在外面了
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
打印信息:
IOS多线程的一些总结_第4张图片
由上图可知,只有number= 2,3这两个线程,number = 1为主线程;
假如设置成3,打印结果如下:

2、设置线程间依赖关系:

    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^(){
        NSLog(@"执行第1次操作,线程:%@", [NSThread currentThread]);
    }];
    
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^(){
        NSLog(@"执行第2次操作,线程:%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^(){
        NSLog(@"执行第3次操作,线程:%@", [NSThread currentThread]);
    }];
    [_queue setMaxConcurrentOperationCount:3];
    [operation1 addDependency:operation2];
    [operation2 addDependency:operation3];
    //第三次操作被同步锁挡在外面了
    [_queue addOperation:operation1];
    [_queue addOperation:operation2];
    [_queue addOperation:operation3];

1依赖2,2依赖3,所以执行会严格按照3->2->1这个顺序来执行:不管执行多少次都是这个结果,可以多试试

IOS多线程的一些总结_第5张图片

假如三个任务互相依赖结果就是一个任务都不执行,互相等待;

3、取消Operations

一旦添加到operation queue,queue就拥有了这个Operation对象并且不能被删除,唯一能做的事情是取消。你可以调用Operation对象的cancel方法取消单个操作,也可以调用operation queue的cancelAllOperations方法取消当前queue中的所有操作。

// 取消单个操作  
[operation3 cancel];  
  
// 取消queue中所有的操作  
[_queue cancelAllOperations];
一个是线程1被取消,一个是queue队列中所有任务被取消,这时候3的block也不会打印;

但是依赖3的其他两个任务会打印

4、等待operations

如果需要在当前线程中处理operation完成后的结果,可以使用NSOperation的waitUntilFinished方法阻塞当前线程,等待operation完成。通常我们应该避免编写这样的代码,阻塞当前线程可能是一种简便的解决方案,但是它引入了更多的串行代码,限制了整个应用的并发性,同时也降低了用户体验。绝对不要在应用主线程中等待一个Operation,只能在第二或次要线程中等待。阻塞主线程将导致应用无法响应用户事件,应用也将表现为无响应。

// 会阻塞当前线程,等到某个operation执行完毕  
[operation3 waitUntilFinished];  

[queue waitUntilAllOperationsAreFinished];
在等待一个进程或者整个queue时,我们还可以往queue中添加其他的operation;

    [queue addOperationWithBlock:^{
        NSLog(@"等待之后的operation4添加");
    }];
这时候4的打印会在以上三个任务完成以后

5、暂停queue和继续queue

    // 暂停queue
    [queue setSuspended:YES];

在我们设置queue暂停的时候并不会停止正在进行的任务,如果已经执行的任务会继续执行不会被停止





你可能感兴趣的:(【开发技术】IOS)