iOS的多线程问题一丶NSThread简介,数据安全和通信

今天看了一天小码哥的视频,重新理解了一下多线程中的NSThread。希望把自己学到的和理解的能够分享给大家。

多线程的原理

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

多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)

如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象

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

CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源

每条线程被调度执行的频次会降低(线程的执行效率降低)


•一个NSThread对象就代表一条线程•创建、启动线程

•创建、启动线程

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

[thread start];

(NSThread *)mainThread; //获得主线程

- (BOOL)isMainThread; //是否为主线程

(BOOL)isMainThread; //是否为主线程

其他方式启动线程

创建线程后自动开启

[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];

•隐式创建并启动线程

[self performSelectorInBackground:@selector(run) withObject:nil];


多线程的安全隐患----------资源共享

1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源

比如多个线程访问同一个对象、同一个变量、同一个文件

当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题

安全隐患解决方法---------互斥锁使用格式

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

注意:锁定1份代码只用1把锁,用多把锁是无效的

•互斥锁的优缺点

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

缺点:需要消耗大量的CPU资源

互斥锁的使用前提:多条线程抢夺同一块资源

相关专业术语:线程同步

线程同步的意思是:多条线程按顺序地执行任务

互斥锁,就是使用了线程同步技术


•nonatomic和atomic对比

atomic:线程安全,需要消耗大量的资源

nonatomic:非线程安全,适合内存小的移动设备

所以iOS开发的属性声明一般用nonatomic


线程间通信

在1个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信   

在1个线程中执行完特定任务后,转到另1个线程继续执行任务

•线程间通信常用方法

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

e.g.:     所有关于UI的更新都要在主线程

主线程                                      子线程

添加UIImageView  ------->下载图片

显示图片      < -------------下载完毕

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;

一、多线程基础

基本概念

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

  • 线程
    进程要想执行任务,必须得有线程,进程至少要有一条线程
    程序启动会默认开启一条线程,这条线程被称为主线程或UI 线程
    线程是进程的基本执行单元,进程的所有任务都在线程中执行

  • 多线程

    • 一个进程中可以开启多条线程,每条线程可以同时执行不同的任务
      进程 -> 公司
      线程 -> 员工
      主线程 -> 老板(第一个员工)
    • 多线程技术可以提高程序的执行效率
  • 多线程原理
    • 同一时间,CPU只能处理一条线程,只有一条线程在执行
    • 多线程同时执行,其实是CPU快速地在多条线程之间切换
    • 如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象
    • 如果线程非常多,会在多条线程之间来回切换,消耗大量的 CPU 资源
      • 每个线程被调度的次数会降低
      • 线程的执行效率会下降

iOS 8.0 主线程的默认堆栈大小也是 512K

图片 1

  • 多线程优缺点

    • 优点
      能适当提高资源利用率(CPU、内存利用率)
      能适当提高程序的执行效率

    • 缺点
      开启线程需要占用一定的内存空间,如果开启大量的线程,会占用大量的内存空间,降低程序的性能
      线程越多,CPU在调度线程上的开销就越大
      程序设计更加复杂:比如线程之间的通信、多线程的数据共享

  • 主线程

    • 程序启动创建的线程,被称为主线程或UI 线程
    • 主线程的作用
      • 显示/刷新 UI 界面
      • 处理 UI 事件:点击、滚动、拖拽等事件
    • 注意:要将耗时操作放在后台线程执行,否则会影响 UI 的流畅度,破坏用户体验
    • 所有网络访问都是耗时操作!
  • iOS中多线程的实现方案

图片 2

当线程的 number == 1 的时候说明该线程是主线程,反之为子线程

1、多线程的优缺点

图片 3

 // 获取主线程 NSThread * mainThread = [NSThread mainThread]; NSLog(@"主线程---%zd",mainThread); // 获取当前线程 NSThread * currentThread = [NSThread currentThread]; NSLog(@"子线程---%zd",currentThread);

2、耗时操作示例

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // 在主线程执行
    [self longOperation];
    // 在后台线程执行
//[self performSelectorInBackground:@selector(longOperation) withObject:nil];
}
// 耗时操作
- (void)longOperation{
    NSLog(@"start = %@",[NSThread currentThread]);
    int largeNumber = 1000 * 1000 * 10;
    for (int index = 0; index < largeNumber; index   ) {
        // 栈区
//        int num = 10;

        // 静态区/常量区
//        NSString *str = @"hello world";
        // 在 oc 中,只要使用 @"" 定义的字符串,如果内容一样,无论在哪里,地址都一样。

        // stringWithFormat:生成的字符串是保存在堆区的
        // 栈区操作效率要比堆区快
        // 程序员只需要管理堆区的内存
        NSString *str = [NSString stringWithFormat:@"hello world - %d",index];
    }
    NSLog(@"over");
}

[NSThread currentThread] 是获取当前线程的对象。
最常用的就是根据 number 判断是否主线程。
number == 1 就是主线程 。
number != 1 就是后台线程。
不要纠结 number 的具体数字,由 CPU 决定。
演示因耗时操作导致按钮和 UITextView 不能继续响应用户点击和拖拽事件。
学习多线程的目的:就是将耗时操作放到后台去执行。
  1. alloc init 创建线程,需要手动启动线程

3、pthread

  • 1、简介

    • pthread 是 POSIX 多线程开发框架,由于是跨平台的 C 语言框架,在苹果的头文件中并没有详细的注释。
    • 要查阅 pthread 有关资料,可以访问 http://baike.baidu.com。
  • 2、导入头文件

#import <pthread.h>
  • 3、pthread演练
// 创建线程,并且在线程中执行 demo 函数
- (void)pthreadDemo {
    /**
     参数:
     1> 指向线程标识符的指针,C 语言中类型的结尾通常 _t/Ref,而且不需要使用 *
     -- 在 C 语言中,没有对象的概念,对象是以结构体的方式来实现的。
     ---- 通常,在 C 语言框架中,对象类型以 _t/Ref 结尾,而且声明时不需要使用 *
     2> 用来设置线程属性
     3> 线程运行函数的起始地址
     --- 在 C 语言中,函数名就是指向函数在内存中的起始地址
     --- 类似的一个概念:数组名是指向数组第一个元素的地址。
     在 C 语言中, void *(指向任何地址的指针) 和 OC 中的 id(万能指针) 是等价的
     参数3的格式: void * (*) (void *)
     返回值 (*函数指针) (参数)
     4> 运行函数的参数

     返回值:
     - 若线程创建成功,则返回0
     - 若线程创建失败,则返回出错编号
     */
    pthread_t threadId = NULL;
    NSString *str = @"Hello Pthread";
    int result = pthread_create(&threadId, NULL, demo, (__bridge void *)(str));
    if (result == 0) {
        NSLog(@"创建线程 OK");
    } else {
        NSLog(@"创建线程失败 %d", result);
    }
}
// 后台线程调用函数
void *demo(void *params) {
    NSString *str = (__bridge NSString *)(params);
    NSLog(@"%@ - %@", [NSThread currentThread], str);
    return NULL;
}
  • 4、小结
    • 在 C 语言中,没有对象的概念,对象是以结构体的方式来实现的。
    • 通常,在 C 语言框架中,对象类型以 _t/Ref 结尾,而且声明时不需要使用 *
    • C 语言中的 void * 和 OC 中的 id 是等价的
    • 内存管理
      • 在 OC 中,如果是 ARC 开发,编译器会在编译时,会根据代码结构,自动添加retain/release/autorelease
      • 但是,ARC 只负责管理 OC 部分的内存管理,而不负责 C 语言 代码的内存管理
      • 因此,开发过程中,如果使用的 C 语言框架出现 retain/create/copy/new 等字样的函数,大多都需要 release,否则会出现内存泄漏
    • 在混合开发时,如果在 C 和 OC 之间传递数据,需要使用 __bridge 进行桥接,桥接的目的就是为了告诉编译器如何管理内存。__bridge 表示什么特殊处理都不做。
    • 桥接的添加可以借助 Xcode 的辅助功能添加。
    • MRC 中不需要使用桥接。因为MRC的内存管理需要程序员手动管理。

二、NSThread

1、创建线程的方式(3种)
准备在后台线程调用的方法 longOperation:

  • (void)longOperation:(id)obj {

    NSLog(@"%@ - %@", [NSThread currentThread], obj);

}

1.1、alloc / init - start

  • (void)threadDemo1 {
    // 创建线程
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(download:) object:@"Alloc"];
    // 开启线程
    [thread start];
    NSLog(@"after %@", [NSThread currentThread]);

}

小结

[thread start];执行后,会在另外一个线程执行 longOperation: 方法
在 OC 中,任何一个方法的代码都是从上向下顺序执行的
同一个方法内的代码,都是在相同线程执行的(block除外)

      1.2、detachNewThreadSelector
  • (void)threadDemo2 {
    NSLog(@"before %@", [NSThread currentThread]);

    [NSThread detachNewThreadSelector:@selector(longOperation:) toTarget:self withObject:@"DETACH"];

    NSLog(@"after %@", [NSThreadcurrentThread]);

}

或:

  • (void)threadDemo2 {
    // 在同一个方法中,代码是从上往下执行的.
    // 同一个线程中,代码也是从上往下执行的(block除外)
    // 多线程开发,不要相信第一次执行的结果
    NSLog(@"start = %@",[NSThread currentThread]);
    // detach:分离
    // 创建线程,并启动线程.
    // [self download:@"xxx"];
    // 创建线程本身是在主线程创建,
    [NSThread detachNewThreadSelector:@selector(download:) toTarget:self.person withObject:@"detach"];
    NSLog(@"over");

}

代码小结

detachNewThreadSelector 类方法不需要启动,会自动创建线程并执行 @selector 方法。

      1.3、分类方法:performSelectorInBackground
  • (void)threadDemo3 {
    NSLog(@"before %@", [NSThread currentThread]);
    [self performSelectorInBackground:@selector(longOperation:) withObject:@"PERFORM"];
    NSLog(@"after %@", [NSThread currentThread]);

}

代码小结
performSelectorInBackground 是 NSObject 的分类方法。
会自动在后台线程执行 @selector 方法。
没有 thread 字眼,隐式创建并启动线程。
所有 NSObject 都可以使用此方法,在其他线程执行方法

=====================================
创建和启动线程
一个NSThread对象就代表一条线程
创建、启动线程

NSThread*thread = [[NSThreadalloc] initWithTarget:selfselector:@selector(run) object:nil];
[thread start];
// 线程一启动,就会在线程thread中执行self的run方法
主线程相关用法

  • (NSThread*)mainThread;// 获得主线程
  • (BOOL)isMainThread;// 是否为主线程
  • (BOOL)isMainThread;// 是否为主线程

其他用法
获得当前线程

NSThread*current = [NSThreadcurrentThread];
线程的调度优先级

  • (double)threadPriority;
  • (BOOL)setThreadPriority:(double)p;
  • (double)threadPriority;

  • (BOOL)setThreadPriority:(double)p;
    调度优先级的取值范围是0.0~1.0,默认0.5,值越大,优先级越高
    线程的名字

  • (void)setName:(NSString*)n;

  • (NSString*)name;

其他创建线程方式
创建线程后自动启动线程

[NSThreaddetachNewThreadSelector:@selector(run) toTarget:selfwithObject:nil];
隐式创建并启动线程

[selfperformSelectorInBackground:@selector(run) withObject:nil];
上述2种创建线程方式的优缺点
优点:简单快捷
缺点:无法对线程进行更详细的设置

=====================================

2、NSThread的Target
NSThread 的实例化方法中的 target 指的是开启线程后,在线程中执行 哪一个对象 的 @selector 方法。

代码演练
准备对象

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;

@end

@implementation Person

  • (instancetype)personWithDict:(NSDictionary *)dict {

    id obj = [[self alloc] init];

    [obj setValuesForKeysWithDictionary:dict];

    return obj;

}

  • (void)longOperation:(id)obj {

    NSLog(@"%@ - %@ - %@", [NSThreadcurrentThread], self.name, obj);

}

@end

定义属性 :@property (nonatomic, strong) Person *person;
懒加载

  • (Person *)person {

    if (_person == nil) {

      _person = [Person personWithDict:@{@"name": @"zhangsan"}];
    

    }

    return _person;

}

三种线程调度方法
1、alloc / init

NSThread *thread = [[NSThread alloc] initWithTarget:self.person(调用者) selector:@selector(longOperation:)(调用者调用此方法) object(参数):   @"THREAD"];

    [thread start];

2、Detach (分离)

[NSThread detachNewThreadSelector:@selector(longOperation:) toTarget:self.person withObject:@"DETACH"];

3、分类方法(创建一个后台子线程并运行)

      [self.person  performSelectorInBackground:@selector(longOperation:) withObject:@"PERFORM"];

代码小结
通过指定不同的 target 会在后台线程执行该对象的 @selector 方法
提示:不要看见 target 就写 self
performSelectorInBackground 可以让方便地在后台线程执行任意 NSObject 对象的方法

3、线程状态

图片 4

新建
实例化线程对象

就绪
向线程对象发送 start 消息,线程对象被加入 可调度线程池 等待 CPU 调度
detach 方法和 performSelectorInBackground 方法会直接实例化一个线程对象并加入 可调度线程池

运行
CPU 负责调度可调度线程池中线程的执行
线程执行完成之前,状态可能会在就绪和运行之间来回切换
就绪和运行之间的状态变化由 CPU 负责,程序员不能干预

阻塞
当满足某个预定条件时,可以使用休眠或锁阻塞线程执行
sleepForTimeInterval:休眠指定时长
sleepUntilDate:休眠到指定日期
@synchronized(self):互斥锁

死亡
正常死亡
线程执行完毕

非正常死亡
当满足某个条件后,在线程内部中止执行。
当满足某个条件后,在主线程中止线程对象。

[NSThread exit];
一旦强行终止线程,后续的所有代码都不会被执行
注意:在终止线程之前,应该注意释放之前分配的对象!

控制线程状态

启动线程

  • (void)start;

// 进入就绪状态 ->运行状态。当线程任务执行完毕,自动进入死亡状态

阻塞(暂停)线程

  • (void)sleepUntilDate:(NSDate*)date;

  • (void)sleepForTimeInterval:(NSTimeInterval)ti;

// 进入阻塞状态

强制停止线程

  • (void)exit;

// 进入死亡状态

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

3.1、示例代码

  • (void)statusDemo {
    NSLog(@"先睡会");
    [NSThread sleepForTimeInterval:1.0];
    for (int i = 0; i < 20; i ) {
    if (i == 9) {
    NSLog(@"再睡会");
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
    }
    NSLog(@"%d %@", i, [NSThreadcurrentThread]);
    if (i == 16) {
    NSLog(@"88");
    // 终止线程之前,需要记住释放资源
    [NSThread exit];
    }
    }
    NSLog(@"over");
    }

  • (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // 注意不要在主线程上调用 exit 方法
    // [NSThread exit];

    // 实例化线程对象(新建)
    NSThread *t = [[NSThread alloc] initWithTarget:self selector:@selector(statusDemo) object:nil];

    // 线程就绪(被添加到可调度线程池中)
    [t start];
    }

3.2、取消线程

  • (void)download{
    NSThread *thread = [NSThread currentThread];
    // 判断线程是否取消
    if (thread.isCancelled) {
    NSLog(@"1...888");
    return;
    }
    // 睡0.2秒
    [NSThread sleepForTimeInterval:0.2];
    NSLog(@"睡会");
    for (int index = 0; index < 10; index ) {
    if (thread.isCancelled) {
    NSLog(@"2...888");
    return;
    }
    NSLog(@"%@",[NSThread currentThread]);
    }
    }

  • (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // 创建
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(download) object:nil];
    // 就绪 -> 进入 CPU 的可调用线程池
    [thread start];

    // 休眠一会
    [NSThread sleepForTimeInterval:0.2];
    // 取消线程 cancel 是给线程发送一个取消的消息。设置线程的状态为取消。
    // 但是:如果要线程终止,需要在线程内部判断。
    [thread cancel];

}

3.3、代码小结
阻塞

方法执行过程,符合某一条件时,可以利用 sleep 方法让线程进入 阻塞 状态
sleepForTimeInterval 从现在起睡多少秒
sleepUntilDate 从现在起睡到指定的日期

死亡

[NSThread exit];
一旦强行终止线程,后续的所有代码都不会被执行
注意:在终止线程之前,应该注意释放之前分配的对象!

注意:线程从就绪和运行状态之间的切换是由 CPU 负责的,程序员无法干预

4、线程的属性
1)name - 线程名称(需要设置)
在大的商业项目中,通常需要在程序崩溃时,获取程序准确执行所在的线程。

2)threadPriority - 线程优先级
优先级,是一个浮点数,取值范围从 0~1.0
1.0表示优先级最高
0.0表示优先级最低
默认优先级是0.5

优先级高只是保证 CPU 调度频率的可能性会高
醒哥个人建议:在开发的时候,不要修改优先级,调度频率快慢由 CPU决定。
多线程的目的:是将耗时的操作放在后台,不阻塞主线程和用户的交互!
多线程开发的原则:简单

3)stackSize - 栈区大小
默认情况下,无论是主线程还是子线程,栈区大小都是 512K
栈区大小可以设置

[NSThread currentThread].stackSize = 1024 * 1024;

4)isMainThread - 是否主线程

4.1、示例代码

// MARK: - 线程属性

  • (void)threadProperty {
    NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];

    // 1. 线程名称
    t1.name = @"Thread AAA";
    // 2. 优先级
    t1.threadPriority = 0;
    [t1 start];
    NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
    // 1. 线程名称
    t2.name = @"Thread BBB";
    // 2. 优先级
    t2.threadPriority = 1;
    [t2 start];
    }

  • (void)demo {
    for (int i = 0; i < 10; i) {
    // 堆栈大小
    NSLog(@"%@ 堆栈大小:%tuK", [NSThreadcurrentThread], [NSThread currentThread].stackSize / 1024);
    }
    // 判断是否是主线程
    if (![NSThread currentThread].isMainThread) {
    }

}

5、资源共享(掌握)

5.1、安全隐患介绍--PPT

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

图片 5

图片 6

图片 7

图片 8

安全隐患解决 – 互斥锁

互斥锁使用格式

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

注意:锁定1份代码只用1把锁,用多把锁是无效的

互斥锁的优缺点
优点:能有效防止因多线程抢夺资源造成的数据安全问题
缺点:需要消耗大量的CPU资源

互斥锁的使用前提:多条线程抢夺同一块资源

相关专业术语:线程同步
线程同步的意思是:多条线程在同一条线上执行(按顺序地执行任务)
互斥锁,就是使用了线程同步技术

5.2、资源共享-卖票
多线程开发的复杂度相对较高,在开发时可以按照以下套路编写代码:
首先确保单个线程执行正确
添加线程

卖票逻辑

  • (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    self.tickets = 20;
    [self saleTickets];
    }
    /// 卖票逻辑 - 每一个售票逻辑(窗口)应该把所有的票卖完
  • (void)saleTickets {
    while (YES) {
    if (self.tickets > 0) {
    self.tickets--;
    NSLog(@"剩余票数 %d %@", self.tickets, [NSThread currentThread]);
    } else {
    NSLog(@"没票了 %@", [NSThreadcurrentThread]);
    break;
    }
    }
    }
    添加线程
  • (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    self.tickets = 20;
    NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
    t1.name = @"售票员 A";
    [t1 start];
    NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
    t2.name = @"售票员 B";
    [t2 start];
    }
    添加休眠
  • (void)saleTickets {
    while (YES) {
    // 模拟休眠
    [NSThreadsleepForTimeInterval:1.0];
    if (self.tickets > 0) {
    self.tickets--;
    NSLog(@"剩余票数 %d %@", self.tickets, [NSThread currentThread]);
    } else {
    NSLog(@"没票了 %@", [NSThreadcurrentThread]);
    break;
    }
    }
    }

运行测试结果

5.3、互斥锁

  • (void)saleTickets {
    while (YES) {
    [NSThread sleepForTimeInterval:1.0];
    @synchronized(self) {
    if (self.tickets > 0) {
    self.tickets--;
    NSLog(@"剩余票数 %d %@", self.tickets, [NSThread currentThread]);
    continue;
    }
    }
    NSLog(@"没票了 %@", [NSThreadcurrentThread]);
    break;
    }
    }
    互斥锁小结
    保证锁内的代码,同一时间,只有一条线程能够执行!
    互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差!
    速记技巧 [[NSUserDefaults standardUserDefaults] synchronize];

互斥锁参数
能够加锁的任意 NSObject 对象
注意:锁对象一定要保证所有的线程都能够访问
如果代码中只有一个地方需要加锁,大多都使用 self,这样可以避免单独再创建一个锁对象

6、原子属性
原子属性(线程安全),是针对多线程设计的,是默认属性
多个线程在写入原子属性时(调用 setter 方法),能够保证同一时间只有一个线程执行写入操作
原子属性是一种单(线程)写多(线程)读的多线程技术
原子属性的效率比互斥锁高,不过可能会出现脏数据
在定义属性时,必须显示地指定 nonatomic,否则默认为atomic

6.1、代码演练

1、定义属性

@property (nonatomic, strong) NSObject *obj1;

@property (atomic, strong) NSObject *obj2;

// 模拟原子属性

@property (atomic, strong) NSObject *obj3;

2、模拟原子属性

/**

如果重写了 atomic 属性的 setter方法,就必须重写 getter 方法。

  • 如果同时重写了 setter 和 getter 方法,苹果就不再提供_成员变量

  • @synthesize 合成指令,用处就是指定属性的 成员变量。

*/

@synthesize obj3 = _obj3;

  • (void)setObj3:(NSObject *)obj3 {

    @synchronized(self) {

      _obj3 = obj3;
    

    }

}

  • (NSObject *)obj3 {

    return _obj3;

}

3、性能测试

  • (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    int largeNumber = 1000 * 1000;

    NSLog(@"非原子属性");

    CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();

    for (int i = 0; i < largeNumber; i ) {

      self.obj1 = [[NSObject alloc] init];
    

    }

    NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start);

    NSLog(@"原子属性");

    start = CFAbsoluteTimeGetCurrent();

    for (int i = 0; i < largeNumber; i ) {

      self.obj2 = [[NSObject alloc] init];
    

    }

    NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start);

    NSLog(@"模拟原子属性");

    start = CFAbsoluteTimeGetCurrent();

    for (int i = 0; i < largeNumber; i ) {

      self.obj3 = [[NSObject alloc] init];
    

    }

    NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start);

}

原子属性内部的锁是自旋锁,自旋锁的执行效率比互斥锁高

// atomic:原子属性.内部也会有一把锁,叫做自旋锁. 效率比互斥锁高

6.2、自旋锁&互斥锁
1、共同点
都能够保证同一时间,只有一条线程执行锁定范围的代码

2、不同点
互斥锁:如果发现有其他线程正在执行锁定的代码,线程会进入休眠状态,等待其他线程执行完毕,打开锁之后,线程会被唤醒
自旋锁:如果发现有其他线程正在执行锁定的代码,线程会以死循环的方式,一直等待锁定代码执行完成。

3、结论
自旋锁更适合执行非常短的代码
无论什么锁,都是要付出代价

7、线程安全
多个线程进行读写操作时,仍然能够得到正确结果,被称为线程安全
要实现线程安全,必须要用到锁
为了得到更佳的用户体验,UIKit 不是线程安全的

约定:所有更新 UI 的操作都必须主线程上执行!因此,主线程又被称为UI 线程。

iOS 开发建议
所有属性都声明为 nonatomic
尽量避免多线程抢夺同一块资源
尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力

8、线程间通讯(掌握)
主线程实现
1、定义属性

/// 根视图是滚动视图
@property (nonatomic, strong) UIScrollView*scrollView;

/// 图像视图
@property (nonatomic, weak) UIImageView *imageView;

/// 网络下载的图像
@property (nonatomic, weak) UIImage *image;

2、loadView 方法
加载视图层次结构
用纯代码开发应用程序时使用
功能和 Storyboard & XIB 是等价的

  • (void)loadView {

    _scrollView = [[UIScrollView alloc] init];

    _scrollView.backgroundColor = [UIColor orangeColor];

    self.view = _scrollView;

    UIImageView *iv = [[UIImageView alloc] init];

    [self.view addSubview:iv];

    _imageView = iv;

}

3、viewDidLoad 方法
视图加载完成后执行
可以做一些数据初始化的工作
如果用纯代码开发,不要在此方法中设置界面 UI

  • (void)viewDidLoad {
    [super viewDidLoad];
    // 下载图像
    [self downloadImage];
    }

4、下载网络图片

  • (void)downloadImage{
    // 1. 网络图片资源路径
    NSURL *url = [NSURL URLWithString:@"http://c.hiphotos.baidu.com/image/pic/item/4afbfbedab64034f42b14da1aec379310a551d1c.jpg"];
    // 2. 从网络资源路径实例化二进制数据(网络访问)
    NSData *data = [NSData dataWithContentsOfURL:url];
    // 3. 将二进制数据转换成图像
    UIImage *image = [UIImage imageWithData:data];
    // 4. 设置图像
    self.image = image;
    }
    5、设置图片
  • (void)setImage:(UIImage *)image {
    // 1. 设置图像视图的图像
    self.imageView.image = image;
    // 2. 按照图像大小设置图像视图的大小
    [self.imageView sizeToFit];
    // 3. 设置滚动视图的 contentSize
    self.scrollView.contentSize = image.size;
    }

6、设置滚动视图的缩放
设置滚动视图缩放属性

// 1> 最小缩放比例
self.scrollView.minimumZoomScale = 0.5;
// 2> 最大缩放比例
self.scrollView.maximumZoomScale = 2.0;
// 3> 设置代理
self.scrollView.delegate = self;

实现代理方法 - 告诉滚动视图缩放哪一个视图

 // 1.创建线程 NSThread * threadA = [[NSThread alloc]initWithTarget:self selector:@selector object:@"ABC"]; // 设置名称 threadA.name = @"子线程A"; // 设置线程优先级 取值范围 0.0 ~ 1.0 之间 最高1.0 默认优先级是0.5 threadA.threadPriority = 0.1; // 2.执行线程 [threadA start];

pragma mark - UIScrollViewDelegate 代理方法

  • (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
    return self.imageView;
    }

7、线程间通讯
在后台线程下载图像

[self performSelectorInBackground:@selector(downloadImage) withObject:nil];
在主线程设置图像

// waitUntilDone:是否等待主线程执行完毕 setImage:方法。
// YES:等待 NO:不等待
// 一般不用等待,直接设置 NO 即可
[self performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];

  1. 分离子线程 自动启动线程
 [NSThread detachNewThreadSelector:@selector toTarget:self withObject:@"分离子线程"];
  1. 开启后台线程
 [self performSelectorInBackground:@selector withObject:@"开启后台线程"];

-  run: (NSString *) param{ NSLog(@"--------run---------%@---------%@",[NSThread currentThread],param);}

// 阻塞线程 阻塞时间完成后才会释放线程 // 方法1.// [NSThread sleepForTimeInterval:2.0]; // 方法2. [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:3.0]]; NSLog(@"end-----");

-  task{ for(NSInteger i = 0; i<100; i  ){ NSLog(@"%zd--------%@",i,[NSThread currentThread]); if{ // 强制退出线程 [NSThread exit]; break; } }}

NSThread的生命周期:当线程执行完成后会自动释放

@synchronized (<#token#>) { <#statements#> }

互斥锁优缺点优点:能有效防止因多线程抢夺资源造成的数据安全问题缺点:需要消耗大量的CPU资源使用互斥锁的前提:多线程抢夺同一块资源相关专业术语:线程同步线程同步:多条线程在同一条线上执行

我们模拟了三个售票员同时出售同100张机票,方法效果如下

/** 售票员A */@property (nonatomic, strong) NSThread * threadA;/** 售票员B */@property (nonatomic, strong) NSThread * threadB;/** 售票员C */@property (nonatomic, strong) NSThread * threadC;/** 总票数 */@property (nonatomic, assign) NSInteger totalCount;

 // 设置总票数 self.totalCount = 100; // 创建线程 self.threadA = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil]; self.threadB = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil]; self.threadC = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil]; self.threadA.name = @"售票员A"; self.threadB.name = @"售票员B"; self.threadC.name = @"售票员C"; // 执行线程 [self.threadA start]; [self.threadB start]; [self.threadC start];

如果没有加互斥锁的话会导致同一张票被三个售票员同时售出,这种情况我们当然是不允许的

-  saleTicket{ while  { // 锁:必须是全局唯一的 一般使用 self // 1.注意加锁位置 // 2.注意加锁的前提条件,多线程共享一块资源 // 3.注意加锁是需要代价的,需要耗费性能和时间 // 4.加锁的结果:线程同步 当线程A执行的时候进行加锁 线程B在外等着线程A结束 锁开了执行线程B @synchronized  { NSInteger count = self.totalCount; if(count > 0){ // 耗时操作 for(NSInteger i = 0; i<100000; i  ){ } self.totalCount = count - 1; NSLog(@"%@卖出一张票,还剩%zd张票",[NSThread currentThread].name,self.totalCount); }else{ NSLog; break; } } } }

当子线程任务执行完成之后如何返回值给主线程,再由主线程去刷新UI,下面我们由在网上下载一张图片为栗子

/** * 参数1. 调用方法 参数2. 方法传递值 参数3. 是否完成该方法后执行下一步 */

1.直接回到主线程

 // 方法1. [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];

2.回到指定NSThread线程

 // 方法2. [self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];

3.省去显示图片函数,直接在方法中实现

 // 方法3. [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];

注:方法3.是因为 UIImageView 继承的最底层也是NSObject,而方法- performSelectorOnMainThread:aSelector withObject:(nullable id)arg waitUntilDone:wait;``- performSelector:aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:wait;都是继承NSObject,所以可以直接通过 UIImageView 去调用方法,从而设置 UIImageView 的 image --> setImage:

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

-  download{ // 1.获取图片rul NSURL * url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1502704595&di=77a802f956215c509727a13dc7176b7a&imgtype=jpg&er=1&src=http://att.bbs.duowan.com/forum/201306/08/220236m63ppvbxbevgrbrg.jpg"]; // 2.根据url下载图片二进制数据到本地 NSData * imageData = [NSData dataWithContentsOfURL:url]; // 3.转换图片格式 UIImage * image = [UIImage imageWithData:imageData]; NSLog(@"download---%@",[NSThread currentThread]); // 4.回到主线程显示UI /** * 参数1. 调用方法 参数2. 方法传递值 参数3. 是否完成该方法后执行下一步 */ // 方法1.// [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES]; // 方法2.// [self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES]; // 方法3. [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES]; NSLog(@"----end----");}

-  showImage: (UIImage *) image{ self.imageView.image = image; NSLog(@"showImage---%@",[NSThread currentThread]);}

我是Renjiee 我要做最骚的程序猿‍‍‍‍‍‍

本文由星彩网app下载发布于计算机编程,转载请注明出处:iOS的多线程问题一丶NSThread简介,数据安全和通信

TAG标签: 星彩网app下载
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。