美文网首页我爱编程
iOS——多线程理解

iOS——多线程理解

作者: 奇怪的她的他 | 来源:发表于2018-04-13 16:33 被阅读42次

    首先先说明此文是学习了李峰峰大牛的博客后所写,有兴趣的可以百度搜索一下李峰峰的博客。

    一、线程和进程

    1、线程

    线程,是程序执行流的最小单元,线程是程序中一个单一的顺序控制流程。是进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位,指运行中的程序的调度单位。

    简单来说,1个进程要想执行任务,必须得有线程。

    线程中任务的执行是串行的,要在1个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务,也就是说,在同一时间内,1个线程只能执行1个任务,由此可以理解线程是进程中的1条执行路径。

    一个进程中至少包含一条线程,即主线程,创建线程的目的就是为了开启一条新的执行路径,运行指定的代码,与主线程中的代码实现同时运行。

    2、进程

    进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础,每一个进程都有自己独立的虚拟内存空间。

    简单来说,进程是指在系统中正在运行的一个应用程序,每一个程序都是一个进程,并且进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内。

    二、多线程

    1.多线程简介

    多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。

    原理:

    • 同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)
    • 多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)
    • 如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象
      注意:多线程并发,并不是cpu在同一时刻同时执行多个任务,只是CPU调度足够快,造成的假象。

    优点:

    • 能适当提高程序的执行效率
    • 能适当提高资源利用率(CPU、内存利用率)

    缺点:

    • 开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
    • 线程越多,CPU在调度线程上的开销就越大

    主线程栈区的1M,非常非常宝贵。一个进程,至少有一个线程(主线程),不能杀掉一个线程!但是可以暂停、休眠。

    2.iOS中多线程的实现方式

    • NSThread:

    (1)使用NSThread对象建立一个线程非常方便

    (2)但是!要使用NSThread管理多个线程非常困难,不推荐使用

    (3)技巧!使用[NSThread currentThread]获得任务所在线程,适用于这三种技术

    (4)使线程休眠3秒:[NSThread sleepForTimeInterval:0.3f];

    • GCD —— Grand Central Dispatch:

    (1)是基于C语言的底层API

    (2)用Block定义任务,使用起来非常灵活便捷

    (3)提供了更多的控制能力以及操作队列中所不能使用的底层函数

    • NSOperation/NSOperationQueue:

    (1)是使用GCD实现的一套Objective-C的API

    (2)是面向对象的线程技术

    (3)提供了一些在GCD中不容易实现的特性,如:限制最大并发数量、操作之间的依赖关系

    3.多线程的安全问题

    多个线程访问同一块资源进行读写,如果不加控制随意访问容易产生数据错乱从而引发数据安全问题。为了解决这一问题,就有了加锁的概念。加锁的原理就是当有一个线程正在访问资源进行写的时候,不允许其他线程再访问该资源,只有当该线程访问结束后,其他线程才能按顺序进行访问。对于读取数据,有些程序设计是允许多线程同时读的,有些不允许。UIKit中几乎所有控件都不是线程安全的,因此需要在主线程上更新UI。

    解决多线程安全问题:

    (1)互斥锁

    // 注意:锁定1份代码只用1把锁,用多把锁是无效的
    @synchronized(锁对象) { // 需要锁定的代码  }
    

    使用互斥锁,在同一个时间,只允许一条线程执行锁中的代码。因为互斥锁的代价非常昂贵,所以锁定的代码范围应该尽可能小,只要锁住资源读写部分的代码即可。使用互斥锁也会影响并发的目的。

    (2)使用NSLock对象

    _lock = [[NSLock alloc] init];
     - (void)synchronizedMethod {
        [_lock lock];
        //safe
        [_lock unlock];
     }
    

    (3)atomic加锁,加锁过程系统已自动完成

    OC在定义属性时有nonatomic和atomic两种选择。

    atomic:原子属性,为setter方法加锁(默认就是atomic)。

    nonatomic:非原子属性,不会为setter方法加锁。

    atomic加锁原理:

     @property (assign, atomic) int age;
     
     - (void)setAge:(int)age
     { 
     
         @synchronized(self) { 
            _age = age;
         }
     }
    

    atomic:线程安全,需要消耗大量的资源

    nonatomic:非线程安全,适合内存小的移动设备

    iOS开发的建议:

    (1)所有属性都声明为nonatomic

    (2)尽量避免多线程抢夺同一块资源

    (3)尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力

    4.多线程间的通信

    //在主线程上执行操作,例如给UIImageVIew设置图片
    - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
    //在指定线程上执行操作
    - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thread withObject:(id)arg waitUntilDone:(BOOL)wait
    

    三、GCD的介绍

    GCD(Grand Central Dispatch) 伟大的中央调度系统,是苹果为多核并行运算提出的C语言并发技术框架。

    GCD会自动利用更多的CPU内核;
    会自动管理线程的生命周期(创建线程,调度任务,销毁线程等);
    程序员只需要告诉 GCD 想要如何执行什么任务,不需要编写任何线程管理代码。

    • 多线程中的一些概念
    • dispatch :派遣/调度
    • queue:队列——用来存放任务的先进先出(FIFO)的容器
    • sync:同步——只是在当前线程中执行任务,不具备开启新线程的能力
    • async:异步——可以在新的线程中执行任务,具备开启新线程的能力
    • concurrent:并发——指一个处理器,宏观上同时处理多个任务。
    • 串行:一个任务执行完毕后,再执行下一个任务
    • 并行:指多个处理器或者是多核的处理器同时处理多个不同的任务。
    并行和并发的详细介绍:
    • 并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。就好像两个人各拿一把铁锨在挖坑,一小时后,每人一个大坑。所以无论从微观还是从宏观来看,二者都是一起执行的。
    • 并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。这就好像两个人用同一把铁锨,轮流挖坑,一小时后,两个人各挖一个小一点的坑,要想挖两个大一点得坑,一定会用两个小时。
    sync和async的详细介绍:
    • dispatch_sync()——同步添加操作。他是等待添加进队列里面的操作完成之后再继续执行。
    dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
        NSLog(@"1");
        dispatch_sync(concurrentQueue, ^(){
            NSLog(@"2");
            [NSThread sleepForTimeInterval:10];
            NSLog(@"3");
        });
        NSLog(@"4");
    

    //控制台打印
    11:36:25.313 GCDSeTest[544:303] 1
    11:36:25.313 GCDSeTest[544:303] 2
    11:36:30.313 GCDSeTest[544:303] 3//模拟长时间操作
    11:36:30.314 GCDSeTest[544:303] 4

    • dispatch_async ,异步添加进任务队列,它不会做任何等待
    dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
        NSLog(@"1");
        dispatch_async(concurrentQueue, ^(){
            NSLog(@"2");
            [NSThread sleepForTimeInterval:5];
            NSLog(@"3");
        });
        NSLog(@"4");
    

    //控制台打印
    11:42:43.820 GCDSeTest[568:303] 1
    11:42:43.820 GCDSeTest[568:303] 4
    11:42:43.820 GCDSeTest[568:1003] 2
    11:42:48.821 GCDSeTest[568:1003] 3//模拟长时间操作时间

    • 线程死锁
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        NSLog(@"=================4");
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"=================5");
        });
        NSLog(@"=================6");
    }
    

    dispatch_async(queue,block) async 异步队列,dispatch_async 函数会立即返回, block会在后台异步执行。

    dispatch_sync(queue,block) sync 同步队列,dispatch_sync 函数不会立即返回,及阻塞当前线程,等待 block同步执行完成。

    分析上面代码:

    viewDidLoad 在主线程中, 及在
    dispatch_get_main_queue() 中,执行到sync 时 向
    dispatch_get_main_queue()插入 同步 threed1.

    sync 会等到 后面block 执行完成才返回, sync 又再 dispatch_get_main_queue() 队列中,
    它是串行队列,sync 是后加入的,前一个是主线程,
    所以 sync 想执行 block 必须等待主线程执行完成,主线程等待 sync 返回,去执行后续内容。

    造成死锁,sync 等待mainThread 执行完成, mianThread 等待sync 函数返回。

    • 这种情况又不会造成线程死锁。
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
    
        NSLog(@"=================1");
        
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"=================2");
        });
        NSLog(@"=================3");
    });
    

    程序会完成执行,为什么不会出现死锁。

    首先: async 在主线程中 创建了一个异步线程 加入 全局并发队列,async 不会等待block 执行完成,立即返回,

    1,async 立即返回, viewDidLoad 执行完毕,及主线程执行完毕。
    2,同时,全局并发队列立即执行异步 block , 打印 1, 当执行到 sync 它会等待 block 执行完成才返回, 及等待dispatch_get_main_queue() 队列中的 mianThread 执行完成, 然后才开始调用block 。

    因为1 和 2 几乎同时执行,因为2 在全局并发队列上, 2 中执行到sync 时 1 可能已经执行完成或 等了一会,mainThread 很快退出, 2 等已执行后续内容。

    dispatch中的其他用法

    (1)延时执行
    //参数1:从现在开始经过多少纳秒,参数2:调度任务的队列,参数3:异步执行的任务
    dispatch_after(when, queue, block)
    例如:

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 2秒后异步执行这里的代码...
    });
    

    (2)一次性执行
    应用场景:保证某段代码在程序运行过程中只被执行一次,在单例设计模式中被广泛使用。
    // 使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只执行1次的代码(这里面默认是线程安全的)
    });
    

    相关文章

      网友评论

        本文标题:iOS——多线程理解

        本文链接:https://www.haomeiwen.com/subject/offckftx.html