iOS_多线程四:NSThread + performSelector + 总结

2022-07-20 14:10:00 浏览数 (1)

目录

一、NSThread

1、一些类方法

2、创建方式

(1)、alloc init创建,但是需要手动开启

(2)、初始化一个子线程,特点:自动开启,是类方法

(3)、performSelector隐式创建 (顺便说一下performSelector其他方法)

二、performSelector

1、afterDelay在子线程中未执行

2、实现:多次点击, 只执行最后一次

三、需要手动加锁(线程同步)(缺点)

三、多线程总结

Prioritize Work with Quality of Service Classes

一、NSThread

是iOS中轻量级得多线程,一个NSThread对象对应一条线程

1、一些类方法

代码语言:javascript复制
[NSThread mainThread]; // 获取主线程
[NSThread currentThread]; // 获取当前线程
// 阻塞当前线程,设置休眠时间,两种方式实现:
[NSThread sleepForTimeInterval:3];
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
[NSThread exit]; // 立即终止主线程之外的所有线程(包括正在执行任务的)
// 注意:需要在掌控所有线程状态的情况下调用此方法,否则可能会导致内存问题。
//   threadPriority相关的都已禁用,改用qualityOfService(枚举)代替
[NSThread threadPriority]; // 获取当前线程优先级
[NSThread setThreadPriority:0.5]; // 设置优先级:0.0~1.0;1.0优先级最高

2、创建方式

(1)、alloc init创建,但是需要手动开启

代码语言:javascript复制
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(network:) object:@{@"name":@"moxiaohui"}];
[thread start];

[thread setName:@"moxiaoyan"]; // 线程名称
thread.qualityOfService = NSQualityOfServiceUserInteractive;
//  NSQualityOfServiceUserInteractive = 0x21, // 最高优先级, 用于处理 UI 相关的任务
//  NSQualityOfServiceUserInitiated = 0x19, // 次高优先级, 用于执行需要立即返回的任务
//  NSQualityOfServiceUtility = 0x11, // 普通优先级,主要用于不需要立即返回的任务
//  NSQualityOfServiceBackground = 0x09, // 后台优先级,用于处理一些用户不会感知的任务
//  NSQualityOfServiceDefault = -1 // 默认优先级,当没有设置优先级的时候,线程默认优先级
thread.stackSize = 8192; // 更改堆栈的大小: 必须 是4KB(1024)的倍数 && 启动线程之前设置 (创建线程是会有开销的)
NSUInteger size = thread.stackSize / 1024; // 所占内存大小
[thread cancel]; // 不会马上退出,做了需要退出的标记
[thread isMainThread];  // 是否是主线程
[thread isFinished];  // 是否已经完成
[thread isCancelled]; // 是否已经取消
[thread isExecuting]; // 是否正在执行中


- (void)network:(NSDictionary *)info {
  NSLog(@"执行 %@", [NSThread currentThread]);
  NSLog(@"info: %@", info);
  sleep(2);
  NSLog(@"完成");
}

(2)、初始化一个子线程,特点:自动开启,是类方法

代码语言:javascript复制
@autoreleasepool {
  [NSThread detachNewThreadSelector:@selector(network:) toTarget:self withObject:@{@"name":@"moxiaohui"}];
}

(3)、performSelector隐式创建 (顺便说一下performSelector其他方法)

二、performSelector

代码语言:javascript复制
  // 当前线程中执行
  [self performSelector:@selector(network:) withObject:@{@"name":@"moxiaohui"}]; // 同步
  [self performSelector:@selector(network:) withObject:@{@"name":@"moxiaoyan"} withObject:@{@"name":@"moxiaohui"}];  // 同步

  // 子线程中执行:(耗时操作)
  [self performSelectorInBackground:@selector(network:) withObject:@{@"name":@"moxiaohui"}]; // 异步
  // 主线程中执行:(执行更新UI之类得操作)
  [self performSelectorOnMainThread:@selector(complete) withObject:nil waitUntilDone:YES];
  // waitUntilDone: 表示后面代码是否需要等待当前方法执行完毕
  // YES: 同步,test执行完,后面的代码才执行
  // NO: 异步,后面的代码先执行(哪怕比较费时),test后执行
  NSLog(@"sleep 2 s");
  sleep(2);
  NSLog(@"3");
  // 指定线程中执行
  [self performSelector:@selector(network:) onThread:[NSThread mainThread] withObject:@{@"name":@"moxiaohui"} waitUntilDone:YES];

  // cancel 某一个方法
  [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(afterDelay:) object:@{@"name":@"moxiaoyan"}];
  // cancel 当前对象所有perform方法
  [NSObject cancelPreviousPerformRequestsWithTarget:self];


- (void)afterDelay:(NSDictionary *)info {
  NSLog(@"afterDelay info:%@", info);
}

- (void)network:(NSDictionary *)info {
  NSLog(@"执行 %@", [NSThread currentThread]);
  NSLog(@"info: %@", info);
  sleep(2);
  NSLog(@"完成");
}

1、afterDelay在子线程中未执行

代码语言:javascript复制
- (void)afterDelayNowork {
  // 模拟子线程里执行 afterDelay 方法
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"1");
    // 解决不执行 方法1
    [self performSelector:@selector(complete) withObject:nil afterDelay:0];
    NSLog(@"2");
    // 在子线程里获取一下runloop
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; // 捕获取就不会主动创建
    // 解决后面代码不执行 方法1.1
    [runLoop run]; // 如果直接用run,在执行完任务后需要用CF框架的方法结束当前loop
    // 解决后面代码不执行 方法2
    // [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
    NSLog(@"3"); // 在loop结束之后才执行
  });
}

- (void)complete {
  NSLog(@"4");
  // 解决后面代码不执行 方法1.2
  CFRunLoopStop(CFRunLoopGetCurrent()); // 需要手动管理`为子线程创建的RunLoop`的生命周期
}

总结:

performSelector: withObject: afterDelay:

1. 在子线程中不work:

因为默认是在当前RunLoop中添加计时器延时执行,而子线程的RunLoop默认不开启,因此不work

2. 会让当前函数后面的代码先执行:

因为该方法是异步的,会先入栈,等线程空闲了才执行

3. runloop run方法后代码不执行:

解决方法1:在执行完任务后需要用CF框架的方法结束当前loop

解决方法2:用runUntilDate方法,在后续时间结束当前loop

2、实现:多次点击, 只执行最后一次

代码语言:javascript复制
- (void)testClickAction {
  // 实现:多次点击, 只执行最后一次
  [self clickAction];
  [self clickAction];
  [self clickAction];
  [self clickAction];
}
- (void)clickAction {
  [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(afterDelay:) object:nil];
  [self performSelector:@selector(afterDelay:) withObject:nil afterDelay:2]; // 2s后执行
}

三、需要手动加锁(线程同步)(缺点)

很多大神举的例子哈,我借鉴一下:多窗口买票的情况,不加锁,数据会错乱

代码语言:javascript复制
#pragma mark - 多窗口买票
- (void)MultiWindowTicket {
  self.totalTicketCount = 20;
  _thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
  _thread1.name = @"窗口1";
  _thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
  _thread2.name = @"窗口2";
  _thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
  _thread3.name = @"窗口3";
  [_thread1 start];
  [_thread2 start];
  [_thread3 start];
}

- (void)saleTicket {
  while (YES) { // 模拟还有票会持续`-1`的操作
//    @synchronized (self) { // 互斥锁:swift 用 objc_sync_enter(self) 和 objc_sync_exit(self)
      if (self.totalTicketCount > 0) {
        self.totalTicketCount--;
        NSLog(@"买了一张,还剩:%ld %@", (long)self.totalTicketCount, [NSThread currentThread]);
        [NSThread sleepForTimeInterval:0.5];
      } else {
        NSLog(@"票买完了");
        break;
      }
//    }
  }
}
// 执行结果:
// 买了一张,还剩:7 <NSThread: 0x600003a41a00>{number = 7, name = 窗口1}
// 买了一张,还剩:5 <NSThread: 0x600003a41a80>{number = 9, name = 窗口3}
// 买了一张,还剩:6 <NSThread: 0x600003a41a40>{number = 8, name = 窗口2}
// 买了一张,还剩:3 <NSThread: 0x600003a41a40>{number = 8, name = 窗口2}
// 买了一张,还剩:4 <NSThread: 0x600003a41a80>{number = 9, name = 窗口3}
// 买了一张,还剩:4 <NSThread: 0x600003a41a00>{number = 7, name = 窗口1}
// 买了一张,还剩:2 <NSThread: 0x600003a41a40>{number = 8, name = 窗口2}
// 买了一张,还剩:2 <NSThread: 0x600003a41a80>{number = 9, name = 窗口3}
// 买了一张,还剩:1 <NSThread: 0x600003a41a00>{number = 7, name = 窗口1}
// 买了一张,还剩:0 <NSThread: 0x600003a41a40>{number = 8, name = 窗口2}
// 票买完了 <NSThread: 0x600003a41a80>{number = 9, name = 窗口3}
// 票买完了 <NSThread: 0x600003a41a00>{number = 7, name = 窗口1}
// 票买完了 <NSThread: 0x600003a41a40>{number = 8, name = 窗口2}

从执行结果可以看的出来,会有多个窗口在售出一张票后,结算的剩余票数是一样的(也就是说他们把同一张票卖给了多个人)

所以NSThread是线程不安全的,需要程序猿自己手动加锁,保持线程同步!!!

三、多线程总结

GCD、NSOperation、NSThread的优缺点

GCD

NSOperation

NSThread

实现

C

OC

OC(Pthread基于C实现)

线程安全

安全

安全

不安全(需要手动加锁,导致性能低!!!)

生命周期

自动管理

自动管理

程序猿管理

轻量级别

性能

其他

跟Block结合代码简洁

多了些实用功能 (如:顺序设置、未执行前取消...)

简单易用(无需做过多设置), 更直观操作线程对象

Quality of Service:

NSOperation

GCD

说明

User-Interactive

Main thread

用户交互:刷新页面、动画...

User-Initiated

High

用户启动:打开/保存文档、点击...

Default

Default

GCD全局队列

Utilize

Low

不需要立即得到结果的, 通常有进度条:下载、导入...

Background

Background

后台运行,用户不可见:同步、备份

每个QoS类型都存在一个全局并发队列,获取方式如下:

代码语言:javascript复制
// 第一个参数传入Quality of service的类型,获得对应的Queue队列
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
  • GCD 和 NSOperation的区别 

底层实现、依赖关系、KVO、优先级、继承、效率

GCD设置的优先级是queue的,NSOperation设置的是自身的

(GCD无法设置在执行的block的优先级)

参考官网:

Prioritize Work with Quality of Service Classes

Demo github 地址

0 人点赞