多线程

一、同步/异步

1、1同步

  • 我们之前写程序的时候都是从上到下,从左到右,代码执行顺序
  • 1个人执行多个任务,也是依次执行,1个人同一时间执行1个任务

1.2异步

多个人可以同时执行多个任务

二、进程/线程

2.1进程

  • 进程是指在系统中正在运行的一个应用程序
  • 每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存空间内
  • 通过“活动监视器”可以查看Mac系统中所开启的进程

2.2线程

  • 1个进程有多个线程组成(1个进程至少要有1个线程)
  • 线程是进程的基本执行单元,一个进程的所有任务都在线程中执行

三、多线程执行原理

1个进程中可以开启多个线程,多个线程可以“同时”执行不同的任务
进程-公司,线程-员工,老板-主线程
多线程可以解决程序阻塞的问题
多线程可以提高程序的执行效率

  • 单任务操作系统  只有进程,没有线程  只能干一件事
  • 多任务操作系统  同时

ios 闲置线程 ios线程池的概念_多线程

    a.    (单核CPU)同一时间,cpu只能处理1个线程,只有1个线程在执行
    b.    多线程同时执行:是CPU快速的在多个线程之间的切换
    c.    cpu调度线程的时间足够快,就造成了多线程的"同时"执行
    d.    如果线程数非常多,cpu会在n个线程之间切换,消耗大量的cpu资源
    i.    每个线程被调度的次数会降低,线程的执行效率降低

3.1 多线程优/缺点

优点

  • 能适当提高程序的执行效率
  • 能适当提高资源的利用率(cpu,内存)
  • 线程上的任务执行完成后,线程会自动销毁

缺点

  • 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占512KB)
  • 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
  • 线程越多,cpu在调用线程上的开销就越大
  • 程序设计更加复杂,比如线程间的通信、多线程的数据共享

ios 闲置线程 ios线程池的概念_互斥锁_02

四、主线程

  • 一个程序运行后,默认会开启1个线程,称为“主线程”或“UI线程”
  • 主线程一般用来  刷新UI界面 ,处理UI事件(比如:点击、滚动、拖拽等事件)
  • 主线程使用注意
  • 别将耗时的操作放到主线程中
  • 耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种卡的坏体验

五、iOS中多线程的技术方案

ios 闲置线程 ios线程池的概念_多线程_03

POSIX

六、pthread演示

导入头文件

#import <pthread.h>

代码

//线程编号的地址
    pthread_t pthread;
    //第一个参数 线程编号的地址
    //第二个参数 线程的属性
    //第三个参数 线程要执行的方法
        //void *   (*)   (void *)
        //函数的返回值类型  void *    int *指向int的指针  void * 指向任意类型的指针    类似于oc中的id
        //函数的名称  函数指针
        //函数的参数  void *
    //第四个参数 线程要执行的方法的 参数
    
    //方法的返回值  0 成功 其它失败
    int result =  pthread_create(&pthread, NULL, demo, NULL);
方式1
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:nil];
[thread start];
方式2
[NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:nil];
方式3
    [self performSelectorInBackground:@selector(demo:) withObject:nil];

七、线程的状态

NSThread *thread = [[NSThread alloc] initWithTarget:self 
                                           selector:@selector(demo) object:nil];
[thread start];

ios 闲置线程 ios线程池的概念_主线程_04

- (void)viewDidLoad {
    [super viewDidLoad];
    //新建状态
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
    //就绪状态
    [thread start];
}
- (void)demo {
    for (int i = 0; i < 20; i++) {
        NSLog(@"%d",i);       
        if (i == 5) {
            //阻塞状态
            [NSThread sleepForTimeInterval:3];
        }
        if (i == 10) {
            //线程退出    死亡状态
            [NSThread exit];
        }
    }
}

八、控制线程的状态

启动线程

- (void)start;

线程进入就绪状态,当线程执行完毕后自动进入死亡状态。
暂停(阻塞)线程

+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

线程进入阻塞状态
停止线程

+ (void)exit;

线程进入死亡状态

注意:一旦线程停止(死亡)了,就不能再次开启任务

九、线程的属性

  • 线程名称
  • 设置线程名称可以当线程执行的方法内部出现异常的时候记录 异常和当前线程
  • 线程优先级
  • 内核调度算法在决定该运行哪个线程时,会把线程的优先级作为考量因素,较高优先级的线程会比较低优先级的线程具有更多的运行机会。较高优先级不保证你的线程具体执行的时间,只是相比较低优先级的线程,它更有可能被调度器选择执行而已。

异步下载图片 线程间通信 更新ui的操作应该在主线程上

@interface ViewController ()
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIImageView *imageView;
@property (strong, nonatomic) IBOutlet UILabel *lbl;
@end

@implementation ViewController
- (void)loadView {
    //初始化scrollview
    self.scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.scrollView.backgroundColor = [UIColor whiteColor];
    self.view = self.scrollView;    
    //初始化imageView
    self.imageView = [[UIImageView alloc] init];
    [self.scrollView addSubview:self.imageView];
}
- (void)viewDidLoad {
    [super viewDidLoad];
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downloadImage) object:nil];
    [thread start];
}
//下载网络图片
- (void)downloadImage {
    //图片的地址
    NSURL *url = [NSURL URLWithString:@"http://img04.tooopen.com/images/20130701/tooopen_20083555.jpg"];
    //下载图片
    NSData *data = [NSData dataWithContentsOfURL:url];
    //把NSData转换成UIImage
    UIImage *img = [UIImage imageWithData:data];
    
    //在主线程上更新UI控件  线程间通信
    //waitUntilDone  值是YES 会等待方法之行完毕,才会执行后续代码
    [self performSelectorOnMainThread:@selector(updateUI:) withObject:img waitUntilDone:YES];
}
- (void)updateUI:(UIImage *)img {
    self.imageView.image = img;
    //让imageview的大小和图片的大小一致
    [self.imageView sizeToFit];   
    //设置scrollView滚动范围
    self.scrollView.contentSize = img.size;
}
@end

十、多线程访问共享资源的问题

  • 共享资源
  • 1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
  • 比如多个线程访问同一个对象、同一个变量、同一个文件
  • 当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题

ios 闲置线程 ios线程池的概念_主线程_05

    

ios 闲置线程 ios线程池的概念_多线程_06

      

ios 闲置线程 ios线程池的概念_主线程_07

@interface ViewController ()
//总票数
@property (nonatomic, assign) int ticketsCount;
@property (nonatomic, strong) NSObject *obj;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.ticketsCount = 10;  
    self.obj = [NSObject new];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //模拟买票窗口1
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets) object:nil];
    [thread1 start];    
    //模拟买票窗口2
    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets) object:nil];
    [thread2 start];
}
//线程是不安全的
//模拟卖票的方法
- (void)sellTickets {
    while (YES) {
        //模拟耗时
        [NSThread sleepForTimeInterval:1.0];
        //任意一个对象内部都有一把锁
        //加锁会影响程序的性能
        //互斥锁
        //线程同步
        @synchronized(self.obj) {
            //判断还有没有票
            if (self.ticketsCount > 0) {
                self.ticketsCount = self.ticketsCount - 1;
                NSLog(@"剩余%d张票",self.ticketsCount);
            }else{
                NSLog(@"来晚了,票没了");
                break;
            }
        }
    }
}
//输出两次9
//t1   t=10
//t2   t=10
//t1   t=9   log
//t2   t=9   log

//输出8,9
//t2   t=9
//t1   t=8  log
//t2   log
@end

十一、互斥锁(线程同步)

  • 互斥锁使用

@synchronized(锁对象) { // 需要锁定的代码

  • 互斥锁
  • 能有效防止因多线程抢夺资源造成的数据安全问题
  • 相关专业术语:线程同步
  • 线程同步的意思是:多条线程按顺序地执行任务
  • 互斥锁,就是使用了线程同步技术

互斥锁原理

  • 互斥锁原理
  • 每一个对象(NSObject)内部都有一个锁(变量),当有线程要进入synchronized到代码块中会先检查对象的锁是打开还是关闭状态,默认锁是打开状态(1),如果是线程执行到代码块内部 会先上锁(0)。如果锁被关闭,再有线程要执行代码块就先等待,直到锁打开才可以进入。

线程执行到synchronized

  • i. 检查锁状态 如果是开锁状态(1)转到ii  如果上锁(0)转到v
  • ii. 上锁(0)
  • iii.执行代码块
  • iv. 执行完毕 开锁(1)
  • v.线程等待(就绪状态)

加锁后程序执行的效率比不加锁的时候要低,因为要线程要等待锁,但是锁保证了多个线程同时操作全局变量的安全性

十二、原子属性

  • 属性中的修饰符
  • nonatomic  非原子属性
  • atomic

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

                    atomic

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

  • nonatomic和atomic对比
  • atomic:线程安全,需要消耗大量的资源
  • nonatomic:非线程安全,适合内存小的移动设备
  • iOS开发的建议
  • 所有属性都声明为nonatomic
  • 尽量避免多线程抢夺同一块资源
  • 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力
@interface ViewController ()
//总票数
@property (nonatomic, assign) int ticketsCount;
@property (nonatomic, strong) NSObject *obj;
//原子属性是线程安全的  自旋锁
@property (nonatomic, copy) NSString *name;
@end

@implementation ViewController
//当同时重写属性的setter和getter方法,不会自动生成_name 成员变量
//为属性生成对应的成员变量
@synthesize name = _name;
//模拟原子属性
- (NSString *)name {
    return _name;
}
- (void)setName:(NSString *)name {
    @synchronized(self) {
        _name = name;
    }
}
- (void)viewDidLoad {
    [super viewDidLoad];
    self.ticketsCount = 10; 
    self.obj = [NSObject new];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //模拟买票窗口1
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets) object:nil];
    [thread1 start];    
    //模拟买票窗口2
    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets) object:nil];
    [thread2 start];
}
//线程是不安全的
//模拟卖票的方法
- (void)sellTickets {   
    while (YES) {
        //模拟耗时
        [NSThread sleepForTimeInterval:1.0];
        //任意一个对象内部都有一把锁
        //加锁会影响程序的性能
        //互斥锁
        //线程同步
        @synchronized(self.obj) {
            //判断还有没有票
            if (self.ticketsCount > 0) {
                self.ticketsCount = self.ticketsCount - 1;
                NSLog(@"剩余%d张票",self.ticketsCount);
            }else{
                NSLog(@"来晚了,票没了");
                break;
            }
        } 
    }
}
//输出两次9
//t1   t=10
//t2   t=10
//t1   t=9   log
//t2   t=9   log

//输出8,9
//t2   t=9
//t1   t=8  log
//t2   log
@end

十三、互斥锁和自旋锁

  • 互斥锁
  • 如果发现其他线程正在执行锁定代码,线程会进入休眠(就绪状态),等其它线程时间片到打开锁后,线程会被唤醒(执行) 
  • 自旋锁
  • 如果发现有其它线程正在锁定代码,线程会用死循环的方式,一直等待锁定的代码执行完成 自旋锁更适合执行不耗时的代码 
  • 互斥锁
  • 如果发现其他线程正在执行锁定代码,线程会进入休眠(就绪状态),等其它线程时间片到打开锁后,线程会被唤醒(执行) 
  • 自旋锁
  • 如果发现有其它线程正在锁定代码,线程会用死循环的方式,一直等待锁定的代码执行完成 自旋锁更适合执行不耗时的代码 

ios 闲置线程 ios线程池的概念_主线程_08

  • 线程安全
  • 线程同时操作是不安全的,多个线程同时操作一个全局变量
  • 线程安全:在多个线程进行读写操作时,仍然能够保证数据的正确 
  • 主线程(UI线程)
  • 几乎所有UIKit提供的类都是线程不安全的,所有更新UI的操作都在主线程上执行
  • 所有包含€的类都是线程不安全的
@interface ViewController ()
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIImageView *imageView;
@property (strong, nonatomic) IBOutlet UILabel *lbl;
@end

@implementation ViewController
- (void)loadView {
    //初始化scrollview
    self.scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.scrollView.backgroundColor = [UIColor whiteColor];
    self.view = self.scrollView;    
    //初始化imageView
    self.imageView = [[UIImageView alloc] init];
    [self.scrollView addSubview:self.imageView];
}
- (void)viewDidLoad {
    [super viewDidLoad];
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downloadImage) object:nil];
    [thread start];
}
//下载网络图片
- (void)downloadImage {
    //图片的地址
    NSURL *url = [NSURL URLWithString:@"http://img04.tooopen.com/images/20130701/tooopen_20083555.jpg"];
    //下载图片
    NSData *data = [NSData dataWithContentsOfURL:url];
    //把NSData转换成UIImage
    UIImage *img = [UIImage imageWithData:data];  
    //在主线程上更新UI控件  线程间通信
    //waitUntilDone  值是YES 会等待方法之行完毕,才会执行后续代码
    [self performSelectorOnMainThread:@selector(updateUI:) withObject:img waitUntilDone:YES];
}
- (void)updateUI:(UIImage *)img {
    self.imageView.image = img;
    //让imageview的大小和图片的大小一致
    [self.imageView sizeToFit];  
    //设置scrollView滚动范围
    self.scrollView.contentSize = img.size;
}
@end

十四、weak和strong

  • 什么时候用strong和weak
  • OC对象用strong 
  • 连线的UI控件为什么用weak

controller ==》 view ==》view.subViews ==》imageView  强引用

controller --》imageView          弱引用 

controller --》imageView这个位置换成strong也可以,但是不建议,如果一个对象被多个对象强引用, 

这多个对象中有一个对象忘记释放,那么该对象也不能释放 

十五、自动释放池

  • iOS开发中的内存管理
  • 在iOS开发中,并没有JAVA或C#中的垃圾回收机制
  • 在MRC中对象谁申请,谁释放
  • 使用ARC开发,只是在编译时,编译器会根据代码结构自动添加了retain、release和autorelease
  • 自动释放池
  • 标记为autorelease的对象,会被添加到最近一次创建的自动释放池中
  • 当自动释放池被销毁或耗尽时,会向自动释放池中的所有对象发送release消息
  • 自动释放池是什么时候创建的?又是什么时候销毁的?
  1. 每一次主线程的消息循环开始的时候会先创建自动释放池
  2. 消息循环结束前,会释放自动释放池
  3. 自动释放池被销毁或耗尽时会向池中所有对象发送 release 消息,释放所有 autorelease 的对象
  4. 使用 NSThread 做多线程开发时,需要在线程调度方法中手动添加自动释放池
  • 自动释放池和主线程

//在主线程消息循环开始的时候

//在消息循环开始的时候创建了自动释放池,在消息循环结束的时候倾倒自动释放池

The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event. If you use the Application Kit, you therefore typically don’t have to create your own pools. If your application creates a lot of temporary autoreleased objects within the event loop, however, it may be beneficial to create “local” autorelease pools to help to minimize the peak memory footprint.

  • 什么时候使用自动释放池
  • If you write a loop that creates many temporary objects.
  • You may use an autorelease pool block inside the loop to dispose of those objects before the next iteration. Using an autorelease pool block in the loop helps to reduce the maximum memory footprint of the application.
  • If you spawn a secondary thread.
  • You must create your own autorelease pool block as soon as the thread begins executing; otherwise, your application will leak objects. (See Autorelease Pool Blocks and Threads for details.)

ios 闲置线程 ios线程池的概念_多线程_09

for (int i = 0; i < largeNumber; ++i) {
    NSString *str = @"Hello World";
    str = [str stringByAppendingFormat:@" - %d", i];
    str = [str uppercaseString];
}

内存泄漏

for (int i=0; i < 100000000; i++) {
        @autoreleasepool {
            NSString *str = [NSString stringWithFormat:@"hello %d",i];
        }
    }

十五、属性修饰符

retain strong weak assign copy

字符串用copy?

当给属性赋值NSMutableString,如果用strong修饰,是一个地址的指向,NSMutableString发生变化,我们指向的始终是最新的值。

block为什么用cooy?

block就是一个函数,

//属性修饰符
//retain  mrc中使用
// strong arc中使用
// weak   只有arc下才能用weak
// assign arc和mrc都可以使用
// copy   arc和mrc都可以使用
//1 字符串为什么用copy
//2 block作为属性的时候 为什么要用copy
//3 delegate为什么用weak修饰
@property (nonatomic, copy) void (^myBlock)();
@property (nonatomic, copy) NSString *name;
@property (nonatomic, weak) Person *weakPerson;
@property (nonatomic, assign) Person *assignPerson;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //1 字符串为什么用copy
//    NSMutableString *str = [NSMutableString string];
//    [str appendString:@"hello"];
//    self.name = [str copy];
//    [str appendString:@"zs"];
//    NSLog(@"%@",self.name);
    
    //2 block 为什么要用copy
    //第一种 block   全局block  __NSGlobalBlock__  存储在代码区
//    void (^demo)() = ^{
//        NSLog(@"aaa");
//    };
//    NSLog(@"%@",demo);
    //第二种block   栈Block  __NSStackBlock__  block内部访问了bloak外部的变量
//    int number = 5;
//    void (^demo)() = ^{
//        NSLog(@"aaa %d",number);
//    };
//    NSLog(@"%@",demo);
    //第三种block   堆block  __NSMallocBlock__
//    int number = 5;
//    void (^demo)() = ^{
//        NSLog(@"aaa %d",number);
//    };
//    NSLog(@"%@",[demo copy]);   
//    [self test];    
//    self.myBlock();
//    self.myBlock();   
    
    //3 delegate为什么用weak修饰
//    self.person = [Person new];
//    self.person.delegate = self; 
    //vc-->person-->delegate-->self(vc)
    
    //4 weak和assign的区别
    self.weakPerson = [Person new];
    self.weakPerson.name = @"zs";
    NSLog(@"weakPerson  %@",self.weakPerson.name);
    
    self.assignPerson = [Person new];
    self.assignPerson.name = @"ls";
    NSLog(@"assignPerson  %@",self.assignPerson.name);
}

//给block属性赋值
- (void)test {
    int n = 5;
    [self setMyBlock:^{
        NSLog(@"%d",n);
    }]; 
    NSLog(@"%@",self.myBlock);
}
1 模拟耗时操作
2 多线程的概念
    2.1 同步/异步
    2.2 进程/线程
    2.3 多线程
3 多线程的原理
     多线程之间是切换执行的
     线程执行完毕后会自动销毁
4 主线程
5 iOS中实现多线程的四种技术方案
6 pthread   __bridge  CF和OC对象转化时只涉及对象类型不涉及对象所有权的转化
7 NSThread 基本使用
8 多线程的状态
9 多线程的属性
10 多线程访问共享资源的问题 (模拟卖票的问题)
11 互斥锁  解决 问题 ---》线程同步
12 自旋锁  --  原子属性 (单写多读)
    原子属性    线程安全   效率低
    非原子属性  线程不安全  效率高
13 互斥锁和自旋锁的区别
14 异步下载图片  线程间通信  更新UI的操作应该在主线程上
15 weak和strong--》自动释放池
16 自动释放池
    自动释放池,在主线程的消息循环开启的时候会创建
    当消息循环结束,倾倒自动释放池
17 什么时候使用自动释放池
    循环内部创建大量的临时对象,在循环内部创建自动释放池
    创建子线程的时候,在线程刚刚开始的位置,创建自动释放池
18 自动释放池的面试题
19 属性修饰符 retain strong weak assign copy