ios基础篇(十一)—— 同步/异步、进程/线程、pthread、线程状态、线程同步、自动释放池

el/2024/7/17 21:01:20

多线程

一、同步/异步

1、1同步

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

1.2异步

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

二、进程/线程

2.1进程

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

2.2线程

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

三、多线程执行原理

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

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

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

3.1 多线程优/缺点

优点

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

缺点

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

四、主线程

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

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

POSIX 表示可移植操作系统接口(Portable Operating System Interface )-----pthread

六、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);
方式1NSThread *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];

- (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 {//初始化scrollviewself.scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds];self.scrollView.backgroundColor = [UIColor whiteColor];self.view = self.scrollView;    //初始化imageViewself.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转换成UIImageUIImage *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块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
    • 比如多个线程访问同一个对象、同一个变量、同一个文件
  • 当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题

   

       

@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 {//模拟买票窗口1NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets) object:nil];[thread1 start];    //模拟买票窗口2NSThread *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 {//模拟买票窗口1NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets) object:nil];[thread1 start];    //模拟买票窗口2NSThread *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

十三、互斥锁和自旋锁

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

  • 线程安全
    • 线程同时操作是不安全的,多个线程同时操作一个全局变量
    • 线程安全:在多个线程进行读写操作时,仍然能够保证数据的正确 
  • 主线程(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 {//初始化scrollviewself.scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds];self.scrollView.backgroundColor = [UIColor whiteColor];self.view = self.scrollView;    //初始化imageViewself.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转换成UIImageUIImage *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.)

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

 


http://www.ngui.cc/el/3419343.html

相关文章

ios基础篇(十二)—— 消息循环、GCD、任务和队列、串行、并行、Barrier阻塞、延时操作、一次性执行

一、消息循环 什么是消息循环 Runloop就是消息循环&#xff0c;每一个线程内部都有一个消息循环只有主线程的消息循环默认开启&#xff0c;子线程的消息循环默认不开启消息循环的目的 保证程序不退出负责处理输入事件如果没有事件发生&#xff0c;会让程序进入休眠状态 1.1 消…

ios基础篇(十三)—— 调度组、NSOperationQueue

一、调度组 有时候需要在多个异步任务都执行完成之后继续做某些事情&#xff0c;比如下载歌曲&#xff0c;等所有的歌曲都下载完毕之后 转到 主线程提示用户 //1 全局队列dispatch_queue_t queue dispatch_get_global_queue(0, 0);//2 调度组dispatch_group_t group dispatc…

ios基础篇(十五)—— SDWebImage

一、SDWebImage介绍 什么是SDWebImage iOS中著名的牛逼的网络图片处理框架包含的功能&#xff1a;图片下载、图片缓存、下载进度监听、gif处理等等用法极其简单&#xff0c;功能十分强大&#xff0c;大大提高了网络图片的处理效率国内超过90%的iOS项目都有它的影子项目地址 ht…

ios基础篇(十六)—— 网络之sockct

一、网络基本概念 客户端&#xff1a;应用 C/S B/S服务器&#xff1a;为客户端提供服务、数据、资源的机器请求&#xff1a;客户端向服务器索取数据响应&#xff1a;服务器对客户端的请求作出反应&#xff0c;一般是返回给客户端数据服务器 内网服务器外网服务器本地测试服…

时序图

一、时序图简介&#xff08;Brief introduction&#xff09; 时序图&#xff08;Sequence Diagram&#xff09;是显示对象之间交互的图&#xff0c;这些对象是按时间顺序排列的。 顺序图中显示的是参与交互的对象及其对象之间消息交互的顺序。 时序图中包括的建模元素主要有&a…

《Objective-C高级编程:iOS与OS X多线程和内存管理》 一 自动引用计数

一、自动引用计数 1.1 内存管理的思考方式 对象操作与objective-c 方法的对应对象操作Object-C 方法生成并持有对象alloc、new、copy、mutableCopy持有对象retain方法释放对象release方法废弃对象dealloc方法1.1.1 自己生成的对象&#xff0c;自己持有 使用以下名称开头的方…

《Objective-C高级编程:iOS与OS X多线程和内存管理》 一 Blocks模式GCD

一、Blocks摘要 Blocks&#xff1a;带有自动变量&#xff08;局部变量&#xff09;的匿名函数。 匿名函数&#xff1a;不带有名称的函数。 int func(int cout); //声明名称为func的函数 int result func(10); // 调用该函数&#xff0c;必须使用该函数的名称 // 若像下面这…

JXCategoryTitleView的使用

最近写项目&#xff0c;遇到了一个vc下面有多个vc 指示器可以点击切换&#xff0c;也可也左右滑动进行切换&#xff0c;解除了JXCategoryTitleView 感觉很好用&#xff1b; 一般要求主vc遵循JXCategoryViewDelegate、JXCategoryListContainerViewDelegate两个协议 子vc遵循JX…

基本编程能力练习

一、首先下载Java的编译环境并安装 JDK下载地址&#xff1a;https://www.oracle.com/java/technologies/javase-jdk15-downloads.html 选择适合自己系统的版本进行安装 这里我选择的是jdk-15_windows-x64_bin.exe 下载完成后直接运行安装&#xff0c;安装位置选择默认的C盘&am…

基本任务1.1Java语言基础

一、任务要求 完成基本数据类型的使用。完成基本运算符和表达式的使用。完成基本控制语句&#xff1a;判断、分支、循环等语句的使用。完成数组的使用。所有源代码必须加入行一级注释。 二、任务的理解 本次任务是让我们对Java编程中所遇到的基础知识进行学习和掌握&#xf…