理解iOS多线程

线程是程序执行的最小单位。它被包含在进程之中,是进程中的实际运作单位。

多线程是指程序可以同一时间运行多个线程,以更加合理地利用系统资源。

iOS中跟UI显示相关的操作都在main线程中。为了不阻塞main线程(卡住UI),通常把耗时工作放在其他线程。

iOS多线程有3种使用方式:NSThread、GCD(Grand Central Dispatch)、NSOperation

因为NSThread要自己管理线程的生命周期和同步、加锁问题,这会导致一定的性能开销,所以开发中通常使用GCD和NSOperation

NSThread

动态方法创建

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"alloc"];
// 手动启动线程
[thread start];

静态方法创建

[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];  

隐式创建

[self performSelectorInBackground:@selector(run) withObject:nil];  

获取当前线程

NSThread *current = [NSThread currentThread];  

在主线程上执行操作

[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES]; 

多线程的安全隐患

1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源

线程同步互斥、加锁

为了防止因多线程抢夺资源造成的数据安全问题,需要使用互斥锁来实现多线程的同步(不同线程的任务按一定的顺序执行)

- (void)began{
//    座位一共15个
    self.seat = 15;

//    开启一个线程 
    NSThread *thread1 = [[NSThread alloc]initWithTarget:self selector:@selector(reserveSeat) object:nil];
    thread1.name = @"thread1";
    [thread1 start];
//    开启一个线程  
    NSThread *thread2 = [[NSThread alloc]initWithTarget:self selector:@selector(reserveSeat) object:nil];
    thread2.name = @"thread2";
    [thread2 start];
}

-(void)reserveSeat{
//    我们必须座位预定完   也就是一直循环 直到seat属性没有值
    while (true) {
        // 注意,锁一定要是所有线程共享的对象
        // 如果代码中只有一个地方需要加锁,大多都使用 self
        @synchronized(self) {
//            判断如果座位大于0  客户就可以预订
            if(self.seat > 0)
            {
                NSLog(@"预定%d号座位  ------%@",self.seat,[NSThread currentThread]);
                self.seat --;
            }else{
                NSLog(@"没有座位了 ------%@",[NSThread currentThread]);
                break;
            }
        }
    }
}

GCD

纯C语言线程库,提供了非常多强大的函数
GCD是苹果公司为多核的并行运算提出的解决方案
GCD会自动利用更多的CPU内核(比如双核、四核)
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

GCD是纯C语言的,因此我们在编写GCD相关代码的时候,面对的函数,而不是方法。
GCD中的函数大多数都以dispatch开头。

使用GCD之前需要理解以下几个概念:

串行队列:队列中的任务是一个一个按存放顺序取出来的,前一个任务没执行完,后一个任务不能取出。

并行队列:队列中的任务可以同时取出,前一个任务刚开始执行,后一个任务就能取出了

注意:队列只是管理任务的调度,不负责执行

同步执行:会阻塞线程,直到block中的任务执行完毕。并且只在main线程中执行

异步执行:当前线程会直接往下执行,它不会阻塞当前线程。只要有空闲的线程(只要系统允许,就能创建出新的空闲线程),就拿来执行

线程池:管理系统的线程。GCD有一个底层线程池,这个池中存放的是一个个的线程。之所以称为“池”,很容易理解出这个“池”中的线程是可以重用的,当一段时间后这个线程没有被调用的话,这个线程就会被销毁。注意:开多少条线程是由底层线程池决定的(线程建议控制再3~5条),池是系统自动来维护,不需要我们程序员来维护

GCD的使用

自定义串行队列

dispatch_queue_t queue0 = dispatch_queue_create("com.deeepthinking", DISPATCH_QUEUE_SERIAL);
// 同步执行
dispatch_sync(queue0, ^{

});
// 异步执行
dispatch_async(queue0, ^{

});

“com.deeepthinking”是你自定义队列的标识,用来debug用,可以自己设置。

自定义并行队列

dispatch_queue_t queue0 = dispatch_queue_create("com.deeepthinking", DISPATCH_QUEUE_CONCURRENT);
// 同步执行
dispatch_sync(queue0, ^{

});
// 异步执行
dispatch_async(queue0, ^{

});

串行队列+同步执行,任务一个一个执行;
串行对列+异步执行,任务一个一个执行;
并行队列+同步执行,任务一个一个执行;
并行队列+异步执行,任务同时进行;

所以要实现真正的并发,要使用并行队列+异步执行的方式。

将任务放到主队列中:

dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_async(main_queue, ^{
});

主队列是个特殊的串行队列,通常用来刷新UI

不管是同步执行还是异步执行,主队列是一定在main线程中执行的。但是同步执行会阻塞main线程,有可能还会造成死锁,所以将任务放到主线程执行时,要用异步执行。比如获取图片并更新到UI:

    dispatch_queue_t queue1 = dispatch_queue_create("dpt", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue1, ^{
        UIImage *image = [self downloadImage];
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
        });
    });

使用全局队列:
这是系统提供的一个并行队列,方便开发者调用。通常我们做并发任务都是加入到这个队列。

dispatch_queue_t queue1 = dispatch_get_global_queue(0, 0);
dispatch_async(queue1, ^{
    UIImage *image = [self downloadImage];
    dispatch_async(dispatch_get_main_queue(), ^{
        self.imageView.image = image;
    });
});

除此之外,GCD还有其他一些功能

一次性执行,用来做单例的实例初始化正好:

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // code to be executed once
    });

延迟执行

// 延迟2秒执行:
    double delayInSeconds = 2.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        // code to be executed on the main queue after delay
    });

合并汇总执行,就是任务执行完,会等其他任务执行完,然后执行总任务:

// 合并汇总结果
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
        // 并行执行的线程一
    });
    dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
        // 并行执行的线程二
    });
    dispatch_group_notify(group, dispatch_get_global_queue(0,0), ^{
        // 汇总结果
    });

NSOperation

NSOperation 是苹果公司对 GCD 的封装,完全面向对象,所以使用起来更好理解。 大家可以看到 NSOperation 和 NSOperationQueue 分别对应 GCD 的任务和队列 。操作步骤也很好理解:

将要执行的任务封装到一个 NSOperation 对象中。
将此任务添加到一个 NSOperationQueue 对象中。
然后系统就会自动在执行任务。

NSOperation 只是一个抽象类,所以不能封装任务。但它有 2 个子类用于封装任务。分别是:NSInvocationOperation 和 NSBlockOperation 。创建一个 Operation 后,需要调用 start 方法来启动任务,它会默认同步执行。

NSInvocationOperation *invoOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationRun) object:nil];
[invoOperation start];

NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"#current thread:%@", [NSThread currentThread]);
}];
[blockOperation start];

NSBlockOperation 还有一个方法addExecutionBlock: ,通过这个方法可以给 Operation 添加多个执行 Block。这样 Operation 中的任务会并发执行:

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"%@", [NSThread currentThread]);
}];

for (NSInteger i = 0; i < 5; i++) {
    [operation addExecutionBlock:^{
        NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
    }];
}

[operation start];

除了上面的两种 Operation 以外,我们还可以自定义 Operation。自定义 Operation 需要继承 NSOperation 类,并实现其 main() 方法,因为在调用 start() 方法的时候,内部会调用 main() 方法完成相关逻辑。所以如果以上的两个类无法满足你的欲望的时候,你就需要自定义了。你想要实现什么功能都可以写在里面。除此之外,你还需要实现 cancel() 在内的各种方法。

NSOperation允许我们调用-(void)cancel取消一个操作的执行。当然,这个操作并不是我们所想象的取消。这个取消的步骤是这样的,如果这个操作在队列中没有执行,那么这个时候取消并将状态finished设置为YES,那么这个时候的取消就是直接取消了。如果这个操作已经在执行了,那么我们只能等其操作完成。当我们调用cancel方法的时候,他只是将isCancelled设置为YES。所以,在我们的操作中,我们应该在每个操作开始前,或者在每个有意义的实际操作完成后,先检查下这个属性是不是已经设置为YES。如果是YES,则后面操作都可以不用在执行了。

[blockOperation cancel];

操作完成时,将会调用下面这个方法,这样也非常方便的让我们对view进行更新或者添加自己的业务逻辑代码:

[blockOperation setCompletionBlock:^{
    NSOperationQueue *queue = [NSOperationQueue mainQueue];
    [queue addOperationWithBlock:^{
        // 更新UI
    }];
}];

NSOperation 有一个非常实用的功能,那就是添加依赖:

NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"下载图片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"打水印   - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"上传图片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

[operation2 addDependency:operation1];      //任务二依赖任务一
[operation3 addDependency:operation2];      //任务三依赖任务二

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];

队列

NSOperation 对象的 start() 方法来启动任务,这样做他们默认是同步执行的。就算是 addExecutionBlock 方法,还是会占用当前线程。这是就要用到队列NSOperationQueue 了。

使用NSOperationQueue默认是并行队列异步执行的:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"%@", [NSThread currentThread]);
}];

for (NSInteger i = 0; i < 5; i++) {
    [operation addExecutionBlock:^{
        NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
    }];
}

[queue addOperation:operation];

在使用NSOperationQueue过程中,不用管串行、并行、同步、异步这些名词。NSOperationQueue 有一个参数 maxConcurrentOperationCount 最大并发数,用来设置最多可以让多少个任务同时执行。当你把它设置为 1 的时候,就是串行队列。

博客直达

理解iOS多线程

参考文章

iOS中的多线程技术
iOS多线程篇-NSThread-synchronized(互斥锁)
iOS开发多线程篇—GCD介绍
iOS多线程同步异步、串行并发的个人剖析(GCD)
关于iOS多线程,你看我就够了

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值