iOS多线程-NSThread简单介绍

线程是进程中的一条执行路径,在一个线程中任务的执行是串行的,同一时间内,一个线程只能执行一个任务

多线程
  • 在多线程开发中, 耗时操作我们一般放在子线程。耗时操作会卡住主线程,严重影响UI流畅度。
  • 一个进程中可以开启多条线程,多条线程可以并发(同时)执行不同的任务。
多线程特点
  • 同一时间,CPU(单核)只能处理1条线程,只有一条线程在工作(执行)。
  • 多线程并发(同时)执行,其实是CPU(单核)快速在多条线程之间调度(切换)。
  • 多核CPU是真正意义上的多线程。
  • 如果线程太多, CPU在多个线程之间调度,会消耗大量的CPU资源,每条线程被调度执行的频次会降低(线程执行效率会降低)。
多线程的优点:

能适当提高程序的执行效率。

缺点:
  • 创建线程是有开销的,iOS下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512KB、主线程1MB),大约需要90毫秒的创建时间。
  • 开启大量的线程,会降低程序的性能,CPU在调度线程上的开销很大。
  • 程序设计更加复杂:比如线程之间的通信、多线程的数据共享。
主线程
  • 一个iOS程序运行后,默认开启一个线程,称为主线程或UI线程。
  • 显示、刷新UI界面。
  • 处理UI事件(点击、滚动、拖拽事件等)。

1, NSThread 方法简单介绍

初始化NSThread对象
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
或者
NSThread *thread = [[NSThread alloc] initWithBlock:^{
        
}];

执行任务
[thread start];
把任务执行在分离新线程中
[NSThread detachNewThreadSelector:@selector(demo2:) toTarget:self withObject:nil];
或者
[NSThread detachNewThreadWithBlock:^{
        
}];
在后台线程执行任务
[self performSelectorInBackground:@selector(demo3:) withObject:@"backgrond"];

回到主线程执行任务
[self performSelectorOnMainThread:<#(nonnull SEL)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>]
线程的状态
  1. 创建线程之后,系统会把线程放在可调度线程池中,当调用 start 之后,线程就变成 Runnable (就绪)状态。
  2. 之后 CPU 调度当前线程运行, 之后再切换到其他线程。
  3. 通多sleep 方法或者等待同步锁,可以使线程处于阻塞状态, 阻塞的线程会移出可调度线程池,停止参与调度。
  4. 等 sleep 时间到或者等待同步锁打开,会被重新放在x可调度线程池中,继续参与调度(执行任务)。
  5. 当线程执行完毕或者强制退出,线程处于死亡状态。
// 1 创建线程
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(sleepStatus) object:nil];
    // 2,线程就绪状态
    [thread start];

- (void)sleepStatus {
    // 3, 阻塞-sleep,当运行满足某个条件,会让线程睡
    // 提示: sleep 是类方法,会直接休眠当前线程
    NSLog(@"睡一会");
    [NSThread sleepForTimeInterval:2];
    
    for (int i = 0; i < 20; i++) {
        NSLog(@"%d, %@", i, [NSThread currentThread]);
        // 4, exit -- 强行终止: 类方法,强制停止当前线程, 一旦强行终止,后续的代码都不会执行
        // exit -- 会杀掉主线程,但是 APP 不会崩溃
        if (i == 12) {
            [NSThread exit];
        }
    }
}
线程安全问题

问题: 当剩余 20 张火车票,两台机器同时售卖,A 机器读取剩余 20, 卖一张还剩 19, B机器读取剩余 20,卖一张还剩 19(相当于两条线程同时访问同一块内存)

测试代码

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
    self.tickets = 20;
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(threadDemo) object:nil];
    thread1.name = @"线程 A";
    [thread1 start];
    
    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(threadDemo) object:nil];
    thread2.name = @"线程 B";
    [thread2 start];
}

- (void)threadDemo {
    while (YES) {
        [NSThread sleepForTimeInterval:1.0];
            if (self.tickets > 0) {
                self.tickets--;
                NSLog(@"剩下: %ld 张票\n%@", self.tickets, [NSThread currentThread]);
            } else {
                NSLog(@"票已经卖完了!");
                break;
            }
    }
}

打印结果:
2019-07-02 15:52:18.353072+0800 005--NSThread资源共享[1766:741375] 剩下: 19 张票
{number = 4, name = 线程 A}
2019-07-02 15:52:18.353080+0800 005--NSThread资源共享[1766:741376] 剩下: 18 张票
{number = 5, name = 线程 B}
2019-07-02 15:52:19.358301+0800 005--NSThread资源共享[1766:741375] 剩下: 17 张票
{number = 4, name = 线程 A}
2019-07-02 15:52:19.358352+0800 005--NSThread资源共享[1766:741376] 剩下: 16 张票
{number = 5, name = 线程 B}
2019-07-02 15:52:20.360759+0800 005--NSThread资源共享[1766:741376] 剩下: 15 张票
{number = 5, name = 线程 B}
2019-07-02 15:52:20.363214+0800 005--NSThread资源共享[1766:741375] 剩下: 14 张票
{number = 4, name = 线程 A}
2019-07-02 15:52:21.361613+0800 005--NSThread资源共享[1766:741376] 剩下: 13 张票
{number = 5, name = 线程 B}
2019-07-02 15:52:21.367609+0800 005--NSThread资源共享[1766:741375] 剩下: 12 张票
{number = 4, name = 线程 A}
2019-07-02 15:52:22.367059+0800 005--NSThread资源共享[1766:741376] 剩下: 11 张票
{number = 5, name = 线程 B}
2019-07-02 15:52:22.372847+0800 005--NSThread资源共享[1766:741375] 剩下: 10 张票
{number = 4, name = 线程 A}
2019-07-02 15:52:23.372406+0800 005--NSThread资源共享[1766:741376] 剩下: 9 张票
{number = 5, name = 线程 B}
2019-07-02 15:52:23.378088+0800 005--NSThread资源共享[1766:741375] 剩下: 8 张票
{number = 4, name = 线程 A}
2019-07-02 15:52:24.377758+0800 005--NSThread资源共享[1766:741376] 剩下: 7 张票
{number = 5, name = 线程 B}
2019-07-02 15:52:24.380684+0800 005--NSThread资源共享[1766:741375] 剩下: 6 张票
{number = 4, name = 线程 A}
2019-07-02 15:52:25.383125+0800 005--NSThread资源共享[1766:741376] 剩下: 5 张票
{number = 5, name = 线程 B}
2019-07-02 15:52:25.384562+0800 005--NSThread资源共享[1766:741375] 剩下: 4 张票
{number = 4, name = 线程 A}
2019-07-02 15:52:26.388494+0800 005--NSThread资源共享[1766:741376] 剩下: 3 张票
{number = 5, name = 线程 B}
2019-07-02 15:52:26.388533+0800 005--NSThread资源共享[1766:741375] 剩下: 2 张票
{number = 4, name = 线程 A}
2019-07-02 15:52:27.393722+0800 005--NSThread资源共享[1766:741376] 剩下: 1 张票
{number = 5, name = 线程 B}
2019-07-02 15:52:27.393722+0800 005--NSThread资源共享[1766:741375] 剩下: 1 张票
{number = 4, name = 线程 A}
2019-07-02 15:52:28.398984+0800 005--NSThread资源共享[1766:741375] 剩下: 0 张票
{number = 4, name = 线程 A}
2019-07-02 15:52:28.398984+0800 005--NSThread资源共享[1766:741376] 剩下: 0 张票
{number = 5, name = 线程 B}
2019-07-02 15:52:29.400585+0800 005--NSThread资源共享[1766:741375] 票已经卖完了!
2019-07-02 15:52:29.404393+0800 005--NSThread资源共享[1766:741376] 票已经卖完了!

打印结果的最后几行出现剩余相同张数的情况(每次执行结果都可能不一样,在多线程的开发中不要相信一次的运行结果)。

通过添加互斥锁解决问题:

把改成
// 互斥锁 -- 保证锁内的代码,同一时间只有一条线程执行
// 互斥锁的范围,应该尽量小,范围大了,效率就低
@synchronized (self) {
      if (self.tickets > 0) {
         self.tickets--;
         NSLog(@"剩下: %ld 张票\n%@", self.tickets, [NSThread currentThread]);
      } else {
          NSLog(@"票已经卖完了!");
          break;
      }
}

多线程卖票的问题就解决了。自己动手试试吧!!!

你可能感兴趣的:(iOS多线程-NSThread简单介绍)