多线程基础知识

一. 多线程介绍 

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逻辑处理 

 

posted @ 2017-02-27 10:51  紫洁  阅读(195)  评论(0编辑  收藏  举报