• 多线程基础知识


    一. 多线程介绍 

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

        比如同时打开微信、QQ,系统就会分别启动两个进程。

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

    2  线程:线程是进程的基本执行单元,一个进程要想执行任务,必须得有线程(每一个进程至少要有一个线程)。

        比如用微信进行视频聊天、QQ进行文字聊天,都需要在线程中执行。

    3 线程的串行:如果要在一个线程中执行多个任务,那么只能一个一个的按顺序执行这些任务,也就是说,同一时间内,一个线程只能执行一个任务,当一个任务执行完成,才能执行另外一个任务。

    4 多线程:一个进程中可以开启多条线程,每个线程可以并行(同时)执行不同的任务。、

    5 多线程原理:

    (1)同一时间,CPU只能处理一条线程,只有一条线程在工作;

    (2)多线程并发执行时,实际上是CPU快速的在线程之间调度;

    (3)如果CPU调度的足够快,就造成了多条线程并发执行的假象。

      如果线程非常非常多,会发生什么情况了?

        CPU会在多个线程之间调度,最终会导致CPU累死,消耗大量的CPU资源;并且每条线程被调执行的频率降低,导致线程的执行效率降低。

    6 多线程优缺点:

    优点:

    (1)能够适当的提高程序的执行效率

    (2)能够适当提高资源的利用率(CPU、内存的利用率)

    缺点:

    (1)开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用521KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能

    (2)线程越多,CPU在调度线程上的开销就越大

    (3)程序设计更加复杂:比如线程之间的通信,多线程的数据共享等。

    7 主线程:一个iOS程序运行后,默认会开启1条线程,该线程称为“主线程”或“UI线程”。

      作用:(1)显示、刷新UI界面 (2)处理UI事件(比如点击、滚动、拖拽事件等)。

      注意:不要将比较耗时的操作放置在主线程中。

    8 iOS中多线程的使用方案

    (1)NSThread

      简介:面向对象的使用;简单易用,可以直接操作线程对象。

      OC语言

      线程的生命周期:程序员手动管理

      使用频率:偶尔使用

    (2)GCD

      简介:旨在代替NSThread等线程技术;充分利用设备的内核。

      C语言

      线程的生命周期:自动管理

      使用频率:经常使用

    (3)NSOperation

      简介:基于GCD;使用更加面向对象;比GCD多了一些更简单使用的功能。

      OC语言

      线程的生命周期:自动管理

      使用频率:经常使用

    二.  NSThread的三种创建方式

    - (void)createThread1 {
    
        NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"123"];
        thread.name = @"zijie";
        //手动启动线程
        [thread start];
    }
    
    - (void)createThread2 {
        
        // 会自动启动线程
        [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"456"];
    }
    
    - (void)createThread3 {
        
        // 开启一个后台线程(子线程)
        [self performSelectorInBackground:@selector(run:) withObject:@"back"];
    }
    
    - (void)run:(id)obj {
        
        NSLog(@"-----%@-----%@", [NSThread currentThread], obj);
        
        for (int i = 0; i < 10000; i ++) {
            NSLog(@"%d", i);
        }
    }

     三. 线程状态(线程的生命周期)

    1 以画图的形式讲解线程状态

    2 用代码形式实现线程sleep和异常退出

    - (void)run:(id)obj {
           
        NSLog(@"-----%@-----%@", [NSThread currentThread], obj);
        
        // 第一种休眠方式
        [NSThread sleepForTimeInterval:2];
        
        // 第二种休眠方式
        [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
    
        NSLog(@"再次执行");
        
        // 强制退出线程
        for (int i = 0; i < 10000; i ++) {
            NSLog(@"-----%d------", i);
            if (i == 100) {
                // 线程退出
                [NSThread exit];
            }
        }
    }

     四. 线程安全

    互斥锁:互斥锁又叫同步锁

      优点:有效的防止因多线程抢夺资源造成的数据安全问题。

      缺点:因为线程等待,需要消耗大量的CPU资源。

    线程同步:

      线程同步指的是多条线程在同一条线上执行(按顺序的执行任务)。

    添加同步锁:

    @synchronized (self){ 

    }
    #import "ViewController.h"
    
    @interface ViewController ()
    
    @property (nonatomic, strong) NSThread *thread1;
    @property (nonatomic, strong) NSThread *thread2;
    @property (nonatomic, strong) NSThread *thread3;
    
    @property (nonatomic, assign) NSInteger tickets;
    
    @end
    
    @implementation ViewController
    
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.tickets = 1000;
        
        _thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets:) object:nil];
        _thread1.name = @"小张";
        _thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets:) object:nil];
        _thread2.name = @"小李";
        _thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets:) object:nil];
        _thread3.name = @"小王";    
    }
    
    - (void)sellTickets:(id)obj {
      
        while (self.tickets > 0) {
            
            @synchronized (self) {
                NSInteger curretTickets = self.tickets;
                if (curretTickets > 0) {                
                    NSLog(@"%@卖了一张票,还剩%d张票", [NSThread currentThread].name, --self.tickets);
                } else {
                    NSLog(@"票已卖完");
                }
            }
        }
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    
        [_thread1 start];
        [_thread2 start];
        [_thread3 start];
    }
    @end

    五. NSThread线程通信

      1 在一个线程中,线程往往不是孤立存在的,多个线程之间是存在有通信关系的。

      2 线程通信的体现:

        (1) 一个线程传递数据给另外一个线程

        (2) 一个线程执行完成任务后转到另外一个线程继续执行任务

      3 由子线程回到主线程的三种方法

    // 由子线程回到主线程-----在主线程给一个UIImageView赋值
        [self performSelectorOnMainThread:@selector(loadImage:) withObject:image waitUntilDone:YES];
        
        [self performSelector:@selector(loadImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
        
        [imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];

    六. GCD( http://www.cnblogs.com/muzijie/p/6437827.html

    1 什么是GCD

      (1)GCD的全称是Grand Central Dispatch(大中枢派发)

      (2)纯C语言,提供了非常强大的函数

    2 GCD的优点

      (1)GCD是苹果公司为多核的并行运算提出的解决的方案

      (2)GCD会自动利用更多的CPU内核(比如双核、四核)

      (3)GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)

      (4)程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

    3 GCD中有两个核心概念

      (1)任务:执行什么操作

      (2)队列:用来存放任务

    4 GCD的使用就2个步骤

      (1)定制任务-------确定想做的事情

      (2)将任务添加到队列中--------GCD会自动将队列的任务取出,放到相应的线程中执行

      注意:任务的取出遵循队列的FIFO原则:先进先出,后进后出。

    5 任务的执行方式

      (1)同步的执行方式

        dispatch_sync(dispatch_queue-t queue, dispatch_block_t block);

        queue: 队列

        block: 任务

      (2)异步的执行方式

        dispatch_async(dispatch_queue-t queue, dispatch_block_t block);

    6 同步和异步的区别

      同步:只能在当前线程中执行任务,不具备开启新线程的能力

      异步:可以在新的线程执行任务,具备开启新线程的能力

    7 队列的类型

      (1)并发队列:

        可以让多个任务并发(同时)执行(自动开启多分线程同时执行)

        并发的功能只有在异步函数(dispatch_async)下才有效

      (2)串行队列

        让任务一个接着一个的执行(也就是说必须等一个任务执行完毕后才可以执行下一个任务)

    8 同步和异步的主要影响:能不能开启新的线程

      同步------在当前线程中执行任务,不具备开启新线程的能力

      异步------可以在新线程中执行任务,具备开启新线程的能力

    9 并发和串行的主要影响:任务的执行方式

      并发-------多个任务并发执行

      串行-------一个任务完成之后,再执行下一个任务

    七. GCD队列任务的执行

      并发队列 + 同步任务: 没有开启新的线程,任务是逐个执行的。

      并发队列 + 异步任务: 开启了新线程,任务是并发的。

      全局队列 + 同步任务: 没有开启新的线程,任务是逐个执行的。

      全局队列 + 异步任务: 开启了新线程,任务是并发的。

      串行队列 + 同步任务: 没有开启新的线程,任务是逐个执行的。

      串行队列 + 异步任务: 开启了一个新的线程,任务是逐个完成的。

      主队列 + 同步任务:会造成死锁的现象,切记: 不能在主队列增加同步任务。

      主队列 + 异步任务:没有开启新的线程,任务逐个完成。
    八. GCD线程通信

        // 由子线程回归到主线程
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"1111111");
        });
        NSLog(@"22222222");
    
    // 打印结果:
    2017-02-27 15:33:05.351 UsingNSThread[80021:9372923] 22222222
    2017-02-27 15:33:05.352 UsingNSThread[80021:9372923] 1111111
        
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"1111111");
        });
        NSLog(@"22222222");
    
    // 打印结果:
    2017-02-27 15:33:05.351 UsingNSThread[80021:9372923] 1111111 
    2017-02-27 15:33:05.352 UsingNSThread[80021:9372923] 22222222

     九. NSOperation(http://www.cnblogs.com/muzijie/p/6438160.html

    1 NSOperation作用:

      配合使用NSOperation和NSOperationQueue来实现多线程

    2 NSOperation和NSOperationQueue使用多线程的步骤

      (1)先将需要执行的操作封装到一个NSOperation对象中

      (2)将NSOperation对象添加到NSOperationQueue中

      (3)系统会自动将NSOperationQueue中的NSOperation取出

      (4)将取出的NSOperation封装的对象放到线程中去执行

    十. NSOperation线程通信

       // 从子线程回归到主线程
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"---1----");
        }];

     十一. RunLoop

    1. RunLoop概念

      (1)字面意思:运行循环(跑圈)

      (2)内部实现:内部是由do-while循环来实现的

    2. RunLoop的作用

      (1)保证程序的持续运行

      (2)处理App的各种事件(滑动、点击、定时器、selector)

      (3)节省CPU资源,提高程序性能(可以使主线程在有事情做的时候,处理事情;没有事情做的时候,处于休眠的状态)

    3. RunLoop对象

      iOS提供了2套API来访问和使用RunLoop

      (1)Foundation框架下的NSRunLoop(OC语言)

      (2)Core Foundation框架下的CFRunLoopRef(C语言)

      NSRunLoop和CFRunLoopRef都代表RunLoop,它们的联系是NSRunLoop是基于CFRunLoopRef的。也就是说,要研究NSRunLoop。还是需要研究CFRunLoopRef。

    4. RunLoop与对象

      (1)每个线程都有唯一的一个与之对应的RunLoop对象

      (2)主线程的RunLoop随着程序已创建好,而子线程的RunLoop需要手动创建。

      (3)获得主线程RunLoop的方法是[NSRunLoop mainRunLoop]

      (4)创建子线程的RunLoop的方法是[NSRunLoop currentRunLoop]

        注意:苹果不允许创建RunLoop,只提供了上面两种方法来获取RunLoop。 

    5. RunLoop相关类

      若没有以下这几类,RunLoop是不会循环的。

      (1)CFRunLoopModeRef

          CFRunLoopModeRef代表了RunLoop的运行模式;

          一个RunLoop可以包含若干个Mode,每个Mode可以包含若干个Source、Timer、Observer;

          每次RunLoop启动时,只能指定其中的一个Mode,这个Mode被称为CurrentMode;

          如果需要切换Mode,需要退出RunLoop,再重新指定一个Mode进入。这么做的目的是为了分离不同组的Source、Timer、Observer,让其互不影响。

          CFRunLoopModeRef类型:系统默认注册了5个Mode

            (I)kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在该Mode下执行的。

            (II)UITrackingRunLoopMode:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时,不受其它Mode影响。

            (III)UIIntializationRunLoopMode:在刚启动时,App进入的第一个Mode,启动完成后就不再使用了。

            (IV)GSEventReceiveRunLoopMode:接受系统事件的内部Mode,通常情况下不使用。

            (V)kCFRunLoopCommonModes:这是一个占位用的Mode,不是一个真正的Mode。

          CommonModes是一个标记。有这个标记的模式有NSRunLoopDefaultMode、UITrackingRunLoopMode。      

        NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    // 将Mode设置为NSRunLoopCommonModes,此时timer在NSRunLoopDefaultMode和UITrackingRunLoopMode这两种模式下都会运行
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

      (2)CFRunLoopTimerRef

        CFRunLoopTimerRef是基于时间的触发器;在这里,基本上可以说是NSTimer。 

      (3)CFRunLoopSourceRef 

        CFRunLoopSourceRef是一个事件源,也可称为输入源。

        按官方文档分类的话,可以分为3类:

        (I)Port-Port-Based Sources:从其它线程或者内核发出的;

        (II)Custom Input Sources:自定义的;

        (III)Cocoa Perform Selector Sources

        按函数调用栈分类的话,可以分为2类:

        (I)Sources0:非基于Port的;---按钮的点击事件

        (II)Sources1:基于Port,通过线程或内核通信,接受、分发系统事件。 

      (4)CFRunLoopObserverRef  

        CFRunLoopObserverRef是观察者,能够监听RunLoop状态的变化。    

        typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {

            kCFRunLoopEntry = (1UL << 0),      //即将进入RunLoop

            kCFRunLoopBeforeTimers = (1UL << 1),  //即将处理Timer

            kCFRunLoopBeforeSources = (1UL << 2),  //即将处理Source

            kCFRunLoopBeforeWaiting = (1UL << 5),  //即将进入休眠

            kCFRunLoopAfterWaiting = (1UL << 6),  //即将从休眠中唤醒

            kCFRunLoopExit = (1UL << 7),       //即将退出RunLoop

            kCFRunLoopAllActivities = 0x0FFFFFFFU  //活跃中

        };

      6. RunLoop逻辑处理 

  • 相关阅读:
    写了个批量下载抖音无水印视频的小软件。
    ffmpeg转换参数码
    WPF
    使用EF的Code First模式创建模型
    桌面置顶显示服务器信息
    Assert.assertEquals
    XML报文解析思路
    定时任务,cron七域
    检查网络是否通畅
    Ngnix运行vue项目
  • 原文地址:https://www.cnblogs.com/muzijie/p/6472647.html
Copyright © 2020-2023  润新知