重修笔记之多线程NSThread

作者: iOS_July | 来源:发表于2018-03-11 12:08 被阅读27次

    一、什么是多线程

    • 多线程是指从软件或者硬件上实现多个线程并发执行的技术。
    • 由于一条线程同一时间只能处理一个任务,所以线程里的任务必须按顺序执行。
    • 如果遇到耗时操作(网络通信、耗时计算、音乐播放等),等上一个操作完成再执行下一个任务,在此段时间内,用户得不到任何响应,这个体验无疑是极糟的。
    • 因此,在iOS编程中,通常将耗时操作单独放在一个线程中,而把用户交互的操作放在主线程中

    二、进程与线程

    • 进程:
    进程是指系统中正在运行的应用程序。这个'运行中的程序'就是一个进程。
    每个进程都拥有着自己的地址空间。
    

    进程有3个主要特征:

    独立性:  
    进程是一个能够独立运行的基本单位,它既拥有自己独立的资源,又拥有着自己私有的地址空间。
    在没有经过进程本身允许的情况下,一个用户的进程是不可以直接访问其他进程的地址空间的。 
    
    动态性:    
    进程的实质是程序在系统中的一次执行过程。
    程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。
    在进程中加入了时间的概念,它就具有了自己的生命周期和各自不同的状态,进程是动态消亡的。
      
    并发性:  
    多个进程可以在单个处理器中同时进行,而不会相互影响。
    
    
    • 线程:
    多线程扩展了多进程的概念,使得同一进程可以同时并发处理多个任务。
    一个程序的运行至少需要一个线程,一个进程想要执行任务,也必须要有至少一个线程,而这个线程就被称作主线程。 
    通常来说,只有一个主线程。
    当进程被初始化后,主线程就被创建了,主线程是其他线程最终的父线程,所有界面的显示操作必须在主线程进行。
    
    

    三、多线程的优势

    1. 进程间不能共享内存,但线程之间的共享内存是很容易的。

    2. 当硬件处理器的数量有所增加时,程序运行的速度更快,无需做其他调整。

    3. 充分发挥了多核处理器的优势,将不同的任务分配给不同的处理器,真正进入“并行运算”的状态。

    4. 将耗时、并发需求高的任务分配到其他线程执行,而主线程则负责统一更新界面,这样使得应用程序更加流畅,用户体验更好。

    四、多线程的劣势

    1. 开启线程需要占用一定的内存空间[默认情况下,主线程最大占用1M的栈区空间、子线程最大占用512K的栈区空间],如果要开启大量的线程,势必会占用大量的内存空间,从而降低程序的性能。
    2. 开启的线程越多,CPU在调度线程上的开销就越大,一般最好不要同时开启超过5个线程
    3. 程序的设计会变得更加复杂,如线程之间的通信、多线程间的数据共享等。

    五、线程的串行和并行

    • 串行:
    如果在一个进程中只有一个线程,而这个进程要执行多个任务,
    那么这个线程只能一个一个的按照顺序执行这些任务,也就是说,
    在同一时间内,一个线程只能执行一个任务,这样的线程执行方式称为线程的串行。
    
    • 并行:
    如果一个进程中包含的线程不止一条,
    每条线程之间可以并行执行不同的任务,这叫做线程的并行,也叫多线程。
    
    • 补充:
    假如一个进程有3个线程,每个线程执行1个任务,3个下载任务没有先后顺序。可以同时执行。 
    同一时间,CPU只能处理一个线程,也就是只有一个线程在工作。
    由于CPU快速的在多个线程之间调度,人眼无法察觉到,就造成了多线程并发执行的假象。
    

    六、线程的状态

    当线程被创建并启动之后,既不是一启动就进入执行状态,也不是一直处于执行状态,即便程序启动运行之后,它也不可能一直占用CPU独自运行。

    由于CPU需要在多个线程之间进行切换,造成了线程的状态会在多次运行、就绪之间进行切换。

    线程的状态主要有5个:

    1. 新建New        
    当程序新建一个线程之后,该线程就处于新建状态。
    和其他对象一样,只是由系统分配了内存,并初始化了内部成员变量的值。
    此时的线程没有任何动态特征
    
    2. 就绪Runable
    当线程被start之后,该线程就处于就绪状态。
    系统会为其创建 方法调用的栈和 程序计数器。
    
    3. 运行Running
    当CPU调度当前线程的时候,将其他线程挂起,当前线程变为运行状态。
    当CPU调度其他线程时,当前线程回到就绪状态。
    测试线程是否在运行,调用isExecuting方法,若返回YES,则处于运行状态。
    
    4. 终止Exit
    * 线程执行方法执行完成,线程正常结束
    * 线程执行的过程出现异常,线程崩溃结束
    * 直接调用NSThread类的exit方法,终止当前正在运行的线程
    * 测试线程是否结束,调用isFinished方法判断,若返回YES,则已终止。
    
    5. 阻塞Blocked    
    如果当前正在执行的线程需要暂停一段时间,并进入阻塞状态,通过NSThread类的两个方法:
    
    //让当前执行的线程暂停到date参数代表的时间,并且进入阻塞状态
    + (void)sleepUntilDate:(NSDate *)date;
    
    //让正在执行的线程暂停ti秒,并进入阻塞状态
    + (void)sleepForTimeInterval:(NSTimeInterval)ti;
    
    

    七、线程间的安全隐患

    进程中的一块资源可能会被多个线程共享,也就是多个线程可能访问同一块资源,这里的资源包括对象变量文件等。
    当多个线程同时访问同一块资源时,会造成资源抢夺,很容易引发数据错乱数据安全的问题。
    为了解决这个问题,实现数据的安全访问,可以使用线程间的加锁

    • 数据混乱示例:
    /** 售票处理 */
    - (void)saleTickets{
        while (true) {
            //模拟延时
            [NSThread sleepForTimeInterval:1.0];
            
            //判断是否还有票
            if (self.leftTickets > 0) {
                self.leftTickets--;
                
                NSLog(@"%@卖了一张票,还剩下%lu张票",[NSThread currentThread].name,self.leftTickets);
            } else{
                NSLog(@"票已售完");
                return;
            }
        }
    }
    
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        //设定总票数
        self.leftTickets = 100;
        //创建3个线程,启动后执行saleTickets方法卖票
        NSThread *t1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTickets) object:nil];
        t1.name = @"1号窗口";
        [t1 start];
        
        NSThread *t2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTickets) object:nil];
        t2.name = @"2号窗口";
        [t2 start];
        
        NSThread *t3 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTickets) object:nil];
        t3.name = @"3号窗口";
        [t3 start];
    }
    
    未加锁.png

    可以看到,由于3个线程的并发操作,同一时间抢夺一个资源leftTickets,造成了剩余票数统计的混乱。

    • 加锁示例:
    @synchronized (obj)
    {
        //插入被修饰的代码块
    }
    
    • 使用同步锁的时候,要尽量让同步代码块包围的代码范围最小,而且要锁定共享资源的全部读写部分的代码。

    obj就是加锁对象,添加了锁对象后,锁对象实现了对多线程的监管,保证同一时刻只有一个线程执行,当同步代码块执行完成后,锁定对象就会释放同步监视器的锁定。

    /** 售票处理 */
    - (void)saleTickets{
        while (true) {
            //模拟延时
            [NSThread sleepForTimeInterval:1.0];
            
            //判断是否还有票
            @synchronized(self){
                if (self.leftTickets > 0) {
                    self.leftTickets--;
                    
                    NSLog(@"%@卖了一张票,还剩下%lu张票",[NSThread currentThread].name,self.leftTickets);
                } else{
                    NSLog(@"票已售完");
                    return;
                }
            }
        }
    }
    
    加锁.png

    线程添加同步锁后,实现了线程的同步运行,也就是说,使多线程按顺序执行任务。需要注意的是,同步锁会消耗大量的CPU资源。

    相关文章

      网友评论

      本文标题:重修笔记之多线程NSThread

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