美文网首页
iOS多线程(一)

iOS多线程(一)

作者: 漆黑烈焰武士G | 来源:发表于2019-08-21 23:05 被阅读0次

    多线程(一)

    一、多线程的作用

    1、线程的定义

    · 线程是进程的基本执行单元,一个进程的所有任务都在线程中执行

    · 进程若想要执行任务,必须得有线程,进程至少要有一条线程

    · 程序启动会默认开启一条线程,这条线程被称为主线程或UI线程

    2、进程的定义

    · 进程是指在系统中正在运行的一个应用程序

    · 每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存

    (安卓可支持多进程,iOS只能支持单进程)

    3、进程与线程的关系 (都是书上的原话)

    · 地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。

    (疑问:啥是地址空间)

    · 资源拥有:同一进程内的线程共享本进程的资源如 内存、I/O、cpu等,但是进程之间的资源是独立的。

    · 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃会导致整个进程都死掉。所以多进程要比多线程健壮。

    · 进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程。

    · 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

    · 线程是处理器调度的基本单位,但是进程不是。

    4、多线程的意义

    · 优点:

    · 能适当提高程序的执行效率 (一些操作会阻塞主线程,影响用户体验,所以要放在子线程)

    · 能适当提高资源的利用率(CPU,内存)

    · 线程上的任务执行完成后,线程会自动销毁

    · 缺点:

    · 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占521kb)

    · 如果开启大量的线程,会占用大量的内存空间,降低程序的性能

    · 线程越多,CPU在调用线程上的开销就越大

    · 程序设计更加复杂,比如线程间的通信、多线程的数据共享

    · 疑问:为何ui要在主线程更新?

    答:UIKit的设计模式要求这么做。

    二、多线程的原理

    1、概览
    方案 简介 语言 线程生命周期 使用频率
    pthread · 一套通用的多线程API
    · 适用于 Unix / Linux / Windows 等系统
    · 跨平台\可移植
    · 使用难度大
    C 程序员管理 几乎不用
    NSThread · 使用更加面向对象
    · 简单易用,可直接操作线程对象
    OC 程序员管理 偶尔使用
    GCD · 旨在替代 NSTread 等线程技术
    · 充分利用设备的多核
    C 自动管理 经常使用
    NSOperation · 基于 GCD (底层是GCD)
    · 比 GCD 多了一些更简单实用的功能
    · 实用更加面向对象
    OC 自动管理 经常使用
    2、开辟空间
    //主线程 开辟空间
        NSLog(@"主线程 : %lu",[NSThread currentThread].stackSize / 1024);
        
        // 1:开辟线程
        NSThread *t = [[NSThread alloc] initWithTarget:self.p selector:@selector(study:) object:@100];
        // 2. 启动线程
        [t start];
        t.name = @"线程NSThreadOne";//名字方便用于查看调用堆栈
        NSLog(@"子线程 : %lu",t.stackSize / 1024);
    --------------------------------
      [1450:26273] 主线程 : 512
        [1450:26273] 子线程 : 512
    
    3、退出线程
    - (void)viewDidLoad 
    {
        [super viewDidLoad];
        self.p = [[Person alloc] init];
        //A: 1:开辟线程
        NSThread *t = [[NSThread alloc] initWithTarget:self.p selector:@selector(study:) object:@100];
        // 2. 启动线程
        [t start];
        t.name = @"线程NSThreadOne";
      
      
        // [NSThread exit]; //退出主线程  --  因为在主线程作用域  
        //程序不会崩溃,但是会卡住,引起野指针等一系列问题
        //此时主runloop会休眠
    }
    
    4、 线程的生命周期(重点)
    img1.png
    • 线程只能start一次,不能重复start
    • 多线程—切换到其他时间片—调度其他线程
    • 多核是真正的并发,单核是虚拟的并发
    • 当一个任务需要执行时:


      img2.png
    • 饱和策略(中止Abort、抛弃Discard、抛弃最旧的Discard-Oldest、调用者运行Caller-Runs)

    三、线程安全

    1、互共享资源需要锁
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event 
    {
        self.tickets = 20;
    
        // 1. 开启一条售票线程
        NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
        t1.name = @"售票 A";
        [t1 start];
        
        // 2. 再开启一条售票线程
        NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
        t2.name = @"售票 B";
        [t2 start];
    }
    
    - (void)saleTickets 
    {
        while (YES) {
            // 0. 模拟延时
            //NSObject *obj = [[NSObject alloc] init];
            //obj 是自己的临时对象,对其他访问该区域的无影响
            //可以锁self 那么访问该方法的时候所有的都锁住,可以根据需求特定锁
            @synchronized(self){
            // 递归 非递归
                [NSThread sleepForTimeInterval:1];
                // 1. 判断是否还有票
                if (self.tickets > 0) 
                {
                    // 2. 如果有票,卖一张,提示用户
                    self.tickets--;
                    NSLog(@"剩余票数 %zd %@", self.tickets, [NSThread currentThread]);
                }
                else 
                {
                    // 3. 如果没票,退出循环
                    NSLog(@"没票了,来晚了 %@", [NSThread currentThread]);
                    break;
                }
                //在锁里面操作其他的变量的影响
                [self.mArray addObject:[NSDate date]];
                NSLog(@"%@ *** %@",[NSThread currentThread],self.mArray);
            }
        }
    }
    
    2、信号量控制并发
            // GCD 控制并发数
            dispatch_semaphore_t lock =  dispatch_semaphore_create(2);
        for (int i = 0; i < 10; i++)
        {
            dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                sleep(5);
                NSLog(@"%d-%@",i,[NSThread currentThread]);
                dispatch_semaphore_signal(lock);
                NSLog(@"*************");
            });
        }
    
    3、自旋锁

    3.1 、自旋锁和互斥锁的区别:

    自旋锁:忙等 --- 代码量较小 --- 耗时较少,因为一直醒着等

    互斥锁:睡觉 --- 唤醒需要时间

    读写锁:多读单写

    //读写锁举例:
    - (NSString *)name 
    {
        return _name;
    }
    - (void)setName:(NSString *)name 
    {
        /**
         * 增加一把锁,就能够保证一条线程在同一时间写入!
         */
        @synchronized (self) {
            _name = name;
        }
    }
    

    3.2、 atomic与nonatomic 的区别:

    • nonatomic非原子属性

    • atomic原子属性(线程安全),针对多线程设计的,默认值

    • 保证同一时间只有一个线程能够写入(但是同一个时间多个线程都可以取值)

    • atomic 本身就有一把锁(自旋锁)

    • 单写多读:单个线程写入,多个线程可以读取

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

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

    iOS开发的建议

    • 所有属性都声明为nonatomic

    • 尽量避免多线程抢夺同一块资源

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

    四、线程和Runloop的关系

    1:runloop与线程是一一对应的,一个runloop对应一个核心的线程,为什么说是核心的,是因为runloop是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局的字典里。

    2:runloop是来管理线程的,当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务。

    3:runloop在第一次获取时被创建,在线程结束时被销毁。

    4:对于主线程来说,runloop在程序一启动就默认创建好了。

    5:对于子线程来说,runloop是懒加载的,只有当我们使用的时候才会创建,所以在子线程用定时器要注意:确保子线程的runloop被创建,不然定时器不会回调。

    疑问: 常驻线程、一般线程

    五、线程间通讯

    1、更新ui

    2、管道间port通信

    相关文章

      网友评论

          本文标题:iOS多线程(一)

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