iOS-多线程

iOS原理 多线程1 -- 多线程的作用和原理

2020-11-10  本文已影响0人  东篱采桑人

iOS原理 文章汇总

前言

在iOS开发中,经常使用多线程来处理一些耗时任务,在学习多线程的实际运用前,需要先了解线程进程以及多线程的原理和概念

一、线程和进程

1. 进程
2. 线程
3. Mac上的进程和线程示例

如下图所示,在Mac上,通过“活动监视器”可以查看系统中所开启的所有进程,以及每个进程中运行的线程数。可以看到,每个应用程序都是一个进程,每个进程中会含有多条线程。

4. 线程和Runloop的关系

二、多线程

1. 多线程的作用

进程的任务必须要在线程中执行,程序启动后,所有任务会默认在主线程中执行。如果某个任务比较耗时,就会阻塞主线程,导致程序卡顿,这时就需要再创建一个子线程来处理这个耗时任务,来缓解主线程的压力。相比单线程,多线程的优缺点如下:

优点
1.能适当提⾼程序的执⾏效率;
2.能适当提⾼资源的利⽤率(CPU,内存);
3.线程上的任务执⾏完成后,线程会⾃动销毁。

缺点
1.开启线程需要占⽤⼀定的内存空间(默认情况下,每⼀个线程都占 512 KB),线程越多,占⽤的内存空间越大,程序性能就越低。
2.任务需要由CPU调度线程来执行,线程越多,CPU 在调⽤线程上的开销就越⼤,每个线程被调度的次数会降低,线程的执⾏效率也会降低。
3.程序设计更加复杂,⽐如线程间的通信、多线程的数据共享。

2. 多线程的原理

一个进程的全部任务,是需要通过CPU调度线程来进行处理的。当进程中存在多个线程时,CPU会在极短的时间间隔内快速调度不同的线程来处理这些任务,这个时间间隔也被称为『时间片』。

CPU分为『单核CPU』和『多核CPU』,它们调度线程的能力也有所不同:

虽然这些线程实际上并不是同时执行,但由于CPU调度线程的时间足够快,也就造成了多线程”同时“执⾏的效果。

CPU调度线程
3. 多线程的生命周期

多线程的生命周期分为5个状态:新建就绪运行阻塞死亡,流程如下图所示:

4. 线程池原理

线程池的处理逻辑如图所示,先来认识三个概念:

当一个新任务到来时,线程池的处理逻辑如下:

5. 线程安全问题

线程共享进程的地址空间和资源,因此当多个线程同时访问一块资源时,容易引发数据安全问题。为了保证多线程的数据读写安全,一般会对线程进行加锁处理,分为『互斥锁』和『自旋锁』两种。这两种锁均能保证锁内的代码,同⼀时间,只有⼀条线程能够执⾏

互斥锁
自旋锁

一般来说,当锁里的任务能较短时间处理完,使用自旋锁性能更好。反之,使用互斥锁。

atomic & nonatomic

atomicnonatomic都是用来修斯属性的,atomic表示原子属性,nonatomic表示非原子属性,它们的区别在于:

三、多线程的实现方案

在iOS中,多线程实现方式主要有这四种:pthreadNSThreadGCDNSOperation,如图所示:

由于pthread现在已几乎不用了,这里就只介绍另外三种方案的用法了。下面是对一个耗时任务分别以单线程和多线程处理来作示例:

- (void)viewDidLoad {
    [super viewDidLoad];

    // Do any additional setup after loading the view.
    
    //task1
    NSLog(@" ==== task1");
    
    //task2
    [self threadTest];
    
    //task3
    NSLog(@" ==== task3");
}

//耗时任务
- (void)threadTest{
  
    NSLog(@" ==== begin task2");
    NSInteger count = 50000;
    for (NSInteger i = 0; i < count; i++) {

        NSInteger num = I;
        NSLog(@" ==== num = %ld", num);
    }
    NSLog(@" ==== end task2");
}

//打印结果
2020-11-05 16:10:40.577616+0800 Test[71508:23639447]  ==== task1
2020-11-05 16:10:40.577703+0800 Test[71508:23639447]  ==== begin task2
2020-11-05 16:10:48.117782+0800 Test[71508:23639447]  ==== end task2
2020-11-05 16:10:48.117894+0800 Test[71508:23639447]  ==== task3

这个例子中,主线程中有task1、task2、task3这三个任务需要执行(这里暂把task2当成一个任务),其中task2是个耗时任务,需要执行8s,而task3其实并不依赖task2的执行结果,但还是需要等待task2结束后才开始执行,所以对于task3来说,task2造成了线程阻塞。像这种情况,完全可以创建一个子线程来执行task2,这样主线程可以立即执行task3,不用再耗时等待了。

//分别用三种方式创建子线程来处理task2
 -(void)performThreadTest{
    
    //1.NSThread
    [NSThread detachNewThreadSelector:@selector(threadTest) toTarget:self withObject:nil];
    
    //2.GCD
    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        [self threadTest];
    });

    //3.NSOperation
    [[[NSOperationQueue alloc] init] addOperationWithBlock:^{
        
        [self threadTest];
    }];
}

//打印结果
2020-11-05 16:12:03.541448+0800 Test[71597:23651072]  ==== task1
2020-11-05 16:12:03.541570+0800 Test[71597:23651072]  ==== task3
2020-11-05 16:12:03.545564+0800 Test[71597:23651434]  ==== begin task2
2020-11-05 16:12:12.445006+0800 Test[71597:23651434]  ==== end task2

从打印结果可知,在子线程中执行task2,这样主线程就不会被阻塞,可以立即执行task3。所以,当进程中存在耗时任务时,创建子线程来执行这个耗时任务,可以缓解主线程的压力,防止线程被阻塞,提高任务的处理效率

四、线程间通讯

在iOS中,线程间的通讯主要有下图所示的几种方式,详情可查阅苹果开发文档Threading Programming Guide

上一篇下一篇

猜你喜欢

热点阅读