我正在参与创作者训练营第4期,点击了解活动详情,一同学习吧!

前语

最近在参与创作者训练营,听了1节课,收益良多,其间印象比较深入的一句话是10篇水文不如1篇高质量文章。豁然大悟,往自己前面写的一些文章,的确是有点水。有时分忧虑写的过长,会看的累,有时分是自己没有那么多时刻,就分开成几部分写。

现在想想,我自己写文章终究想干啥,是为了加薪吗,是为了让咱们点赞有自豪感吗,好像是有点。总的来说无非便是想把自己学习了东西进行好好总结,让自己成长,当然过程中能帮到他人更好,总而言之不要违背初心就好。

NSThread

NSThread是一个对pthread目标化的封装,是苹果官方提供面向目标操作线程的技术,简略易用,能够直接操作线程目标,不过需求咱们自己办理线程的生命周期。

NSThread线程创立

咱们直接经过initWithBlock进行初始化。其间[thread start]便是发动线程。

- (void)nsthreadDemo
{
  NSThread *thread = [[NSThread alloc] initWithBlock:^{
        // 打印当时线程
    NSLog(@"%@",[NSThread currentThread]);
  }];
  [thread start];
}

实际上除了这种,咱们还有其它初始化办法。

- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument;

还有类办法也能够进行线程创立,并且不需求start

+ (void)detachNewThreadWithBlock:(void (^)(void))block;
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;

线程睡觉

sleepUntilDate办法是指睡觉当某个date

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

sleepForTimeInterval是指让线程睡觉几秒。

+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

线程控制

线程发动,咱们用start办法。

- (void)start;

线程撤销,咱们用cancel办法。

- (void)cancel;

获取当时线程,咱们用currentThread办法。

[NSThread currentThread]

主线程履行

咱们能够直接用NSObject的办法。用performSelectorOnMainThread回到主线程。

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

子线程履行,回到主线程改写UI

这儿咱们用个例子来运用NSThread

- (void)nsthreadDemo
{
  NSThread *thread = [[NSThread alloc] initWithBlock:^{
    // 打印当时线程
    NSLog(@"%@",[NSThread currentThread]);
    //让当时线程睡2秒
    [NSThread sleepForTimeInterval:2.0];
    // 回到主线程
    [self performSelectorOnMainThread:@selector(updateUI) withObject:nil waitUntilDone:NO];
  }];
  [thread start];
}
- (void)updateUI
{
  NSLog(@"%@",[NSThread currentThread]);
  self.view.backgroundColor = [UIColor systemRedColor];
}

NSThread平常仍是很少用这个来操作线程的,那咱们就看下面的。

NSOperation

NSOperation 是基于 GCD 更高一层的封装,运用更加面向目标。比 GCD 多了一些更简略的功能。主动办理生命周期,这个我喜欢。那下面咱们就一同来看看吧。

NSOperation和NSOperationQueue运用

在 GCD 中,咱们创立一个行列,然后把使命增加到 Block 上。然后由行列进行调度使命。

既然是基于 GCD ,那 NSOperation 大体上逻辑也会和 GCD 相同。

其间 NSOperationQueue 是操作行列,NSOperation 是操作使命。

具体步骤如下:

  1. 创立操作:先将需求履行的操作封装到一个 NSOperation 目标中。
  2. 创立行列:创立 NSOperationQueue 目标。
  3. 将操作加入到行列中:将 NSOperation 目标增加到 NSOperationQueue 目标中。
- (void)operationdemo
{
  // 创立行列
  NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  //创立操作使命
  NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"%@",[NSThread currentThread]);
  }];
  //行列增加操作使命
  [queue addOperation:operation];
}

行列控制

cancelAllOperations:撤销当时行列的一切操作使命。

- (void)cancelAllOperations;

suspended:行列挂起。

@property (getter=isSuspended) BOOL suspended;

mainQueue:主行列。

[NSOperationQueue mainQueue]

currentQueue:当时行列。

[NSOperationQueue currentQueue]

waitUntilAllOperationsAreFinished:会等当时行列履行完一切使命,才会持续走,会堵塞当时线程。

- (void)waitUntilAllOperationsAreFinished;

NSOperationQueue控制并发数

这儿咱们就直接用addOperationWithBlock来完成行列增加操作使命。

咱们设置最大并发为1,当时行列便是串行行列了。

- (void)operationdemo
{
  // 创立行列
  NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  //创立串行行列
  queue.maxConcurrentOperationCount = 1;
  for (int i = 0; i < 5; i++) {
    //行列增加操作使命
    [queue addOperationWithBlock:^{
      NSLog(@"%d: %@",i,[NSThread currentThread]);
    }];
  }
}

打印成果:

2022-02-17 16:17:38.379723+0800 多线程[80881:1209674] 0: <NSThread: 0x600003108380>{number = 6, name = (null)}
2022-02-17 16:17:38.380011+0800 多线程[80881:1209674] 1: <NSThread: 0x600003108380>{number = 6, name = (null)}
2022-02-17 16:17:38.380219+0800 多线程[80881:1209674] 2: <NSThread: 0x600003108380>{number = 6, name = (null)}
2022-02-17 16:17:38.380460+0800 多线程[80881:1209672] 3: <NSThread: 0x6000031023c0>{number = 3, name = (null)}
2022-02-17 16:17:38.380838+0800 多线程[80881:1209672] 4: <NSThread: 0x6000031023c0>{number = 3, name = (null)}

成果剖析:打印成果的确是按次序履行,也便是说操作使命是1个1个履行的。

咱们设置最大并发数大于1,当时行列便是并发行列了。

- (void)operationdemo
{
  // 创立行列
  NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  //创立并发行列
  queue.maxConcurrentOperationCount = 2;
  for (int i = 0; i < 5; i++) {
    //行列增加操作使命
    [queue addOperationWithBlock:^{
      NSLog(@"%d: %@",i,[NSThread currentThread]);
    }];
  }
}

看下打印成果:

iOS多线程之三:NSThread,NSOperation,GCD超详细总结

能够看到是履行次序是无序。也记住一点便是最大并发数不是敞开了多少条线程,敞开线程数量是由系统决定的,不需求咱们来办理。

操作依靠和优先级

使命之间能够相互依靠,没有依靠关系,那就看谁的优先级高,就先履行谁。

- (void)operationdemo1
{
  // 创立行列
  NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  //创立并发行列
  queue.maxConcurrentOperationCount = 2;
  NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"%d: %@",1,[NSThread currentThread]);
  }];
  //设置一般优先级
  [operation1 setQueuePriority:NSOperationQueuePriorityNormal];
  NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"%d: %@",2,[NSThread currentThread]);
  }];
  //2依靠1
  [operation2 addDependency:operation1];
  NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"%d: %@",3,[NSThread currentThread]);
  }];
  //设置高的优先级
  [operation3 setQueuePriority:NSOperationQueuePriorityHigh];
  NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"%d: %@",4,[NSThread currentThread]);
  }];
  //4依靠3
  [operation4 addDependency:operation3];
  // 行列增加操作
  [queue addOperation:operation1];
  [queue addOperation:operation2];
  [queue addOperation:operation3];
  [queue addOperation:operation4];
}

咱们先剖析一波:创立了1个行列,最大并发数是2,操作使命2依靠操作使命1,操作使命4依靠操作使命3。所以履行后,2是排在1后边,4是排在3后边。1的优先级是一般,3的优先级是高。所以3会先履行,1会在3后边。

咱们看下打印成果:

iOS多线程之三:NSThread,NSOperation,GCD超详细总结

3 -> 1 -> 4 -> 2 ,是不是和咱们剖析的相同样啊。

子线程履行,回到主线程改写UI

最终,咱们也来下子线程履行使命,然后回到主线程履行代码。

- (void)operationdemo2
{
  // 创立行列
  NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  // 行列增加使命
  [queue addOperationWithBlock:^{
    NSLog(@"%@",[NSThread currentThread]);
    sleep(2);
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
      NSLog(@"%@",[NSThread currentThread]);
    }];
  }];
}

看起来也还OK吧,那下面咱们再看下GCD吧。

GCD

GCD 是也是主动办理生命周期的,也是咱们日常处理线程用的最多的一个方式。 GCD 的核心便是行列+履行方式,首要创立一个行列,然后向行列中追加使命,系统会依据使命的类型履行使命。

在前面写的一篇 iOS多线程之一:进程,线程,行列的关系 ,就已经写了关于同步串行,同步并发,异步串行,异步并发了。所以这章节,我就写下GCD其它相关的内容。

子线程履行,回到主线程改写UI

看下面的代码,感觉和 NSOperation 的代码是差不多的。这个也是咱们常用的办法。

- (void)gcddemo1
{
  dispatch_async(dispatch_get_global_queue(0, 0), ^{
    sleep(2);
    NSLog(@"%@",[NSThread currentThread]);
    dispatch_async(dispatch_get_main_queue(), ^{
      NSLog(@"%@",[NSThread currentThread]);
    });
  });
}

这儿用的是异步并发履行使命,用的是大局行列,改成dispatch_queue_create("jj.com", DISPATCH_QUEUE_CONCURRENT);并发行列也是能够的。

dispatch_once

咱们能够用dispatch_once来创立1个单例。

@implementation GCDDemo
+ (instancetype)shareInstance {
  static GCDDemo *demo;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    demo = [GCDDemo new];
  });
  return demo;
}
@end

dispatch_once 下的代码,在整个程序运转过程中只履行一次。这个在多线程的时分也能够确保线程安全的。

dispatch_after

咱们能够运用dispatch_after来履行推迟办法。

- (void)gcddemo2 {
  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"2:%@",[NSThread currentThread]);
  });
  NSLog(@"1:%@",[NSThread currentThread]);
}

iOS多线程之三:NSThread,NSOperation,GCD超详细总结

能够看出来,dispatch_after是不会堵塞线程的,并且延时后,会回到主线程履行使命。

dispatch_barrier_async

dispatch_barrier_async是一个栅门函数,这儿咱们解说的是异步栅门,所以并不会堵塞当时线程。那它有什么用呢?

咱们看下这个例子:

- (void)dispatch_barrier_async_request
{
  dispatch_queue_t queue = dispatch_queue_create("jj.com", DISPATCH_QUEUE_CONCURRENT);
  dispatch_async(queue, ^{
    NSLog(@"2:%@", [NSThread currentThread]);
  });
  dispatch_async(queue, ^{
    NSLog(@"3:%@", [NSThread currentThread]);
  });
  dispatch_barrier_async(queue, ^{
    NSLog(@"4:%@", [NSThread currentThread]);
  });
  dispatch_async(queue, ^{
    NSLog(@"5:%@", [NSThread currentThread]);
  });
  NSLog(@"1:%@", [NSThread currentThread]);
}

既然是异步栅门函数,那也便是说需求等前面的行列使命履行完,再履行自己的,然后再履行后边的。咱们看下成果:

iOS多线程之三:NSThread,NSOperation,GCD超详细总结

看成果,的确是没有堵塞主线程,并且dispatch_barrier_async的确起到栅门作用。

除了这个用法,咱们还在多读单写方面起到重要作用。咱们看下代码

// 界说一个并发行列
@property (nonatomic, strong) dispatch_queue_t concurrent_queue;
// 多个线程需求数据拜访
@property (nonatomic, strong) NSMutableDictionary *dataCenterDic;

先界说一个并发行列和一个字典,字典是可能多个线程需求数据拜访。

- (instancetype)init{
  self = [super init];
  if (self){
    // 创立一个并发行列:
    self.concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
    // 创立数据字典:
    self.dataCenterDic = [NSMutableDictionary dictionary];
  }
  return self;
}

先初始化,咱们直接增加一个读和写办法。

#pragma mark - 读数据
- (id)jj_objectForKey:(NSString *)key{
  __block id obj;
  // 同步读取指定数据:
  dispatch_sync(self.concurrent_queue, ^{
    obj = [self.dataCenterDic objectForKey:key];
  });
  return obj;
}
#pragma mark - 写数据
- (void)jj_setObject:(id)obj forKey:(NSString *)key{
  // 异步栅门调用设置数据
  dispatch_barrier_async(self.concurrent_queue, ^{
    NSLog(@"写--%@",obj);
    [self.dataCenterDic setObject:obj forKey:key];
  });
}

咱们外部开端调用写读和写办法。

- (void)readWriteLock {
  dispatch_queue_t queue = dispatch_queue_create("jj.com", DISPATCH_QUEUE_CONCURRENT);
  for (int i = 0; i < 5; i++) {
    dispatch_async(queue, ^{
      [self.rwLock jj_setObject:[NSString stringWithFormat:@"jj---%d",i] forKey:@"key"];
    });
  }
  for (int i = 0; i < 5; i++) {
    dispatch_async(queue, ^{
      NSLog(@"读1--%@",[self.rwLock jj_objectForKey:@"key"]);
    });
  }
 }

咱们看下打印成果:

iOS多线程之三:NSThread,NSOperation,GCD超详细总结

读和写的输出并没有问题,这个就相当于起到读写锁的作用,除了这个还能够用pthread_rwlock_rdlock,这个咱们下一篇会详细说明。

dispatch_group

一般来说,行列组适用于在恳求几个异步使命,然后等使命履行完后,再到dispatch_group_notify履行地点的使命。

- (void)dispatch_group_request
{
    // 创立1个行列组
  dispatch_group_t group = dispatch_group_create();
    // 创立1个并发行列
  dispatch_queue_t queue = dispatch_queue_create("jj.com", DISPATCH_QUEUE_CONCURRENT);
    // 异步增加1个并发行列
  dispatch_group_async(group, queue, ^{
    // 推迟模仿
    sleep(1);
    NSLog(@"1--%@",[NSThread currentThread]);
  });
  dispatch_group_async(group, queue, ^{
    // 推迟模仿
    sleep(1);
    NSLog(@"2--%@",[NSThread currentThread]);
  });
 // 上面的使命履行完后会来到这儿
  dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"3--%@",[NSThread currentThread]);
  });
  NSLog(@"0--%@",[NSThread currentThread]);
}

咱们看下输出成果:

iOS多线程之三:NSThread,NSOperation,GCD超详细总结

很明显能够看出来,并没有堵塞当时线程,并且是等前面2个使命履行结束后,再履行dispatch_group_notify的使命。

很多人会想,我 dispatch_group_async 里边要是用的是第三方网络结构调取异步网络恳求,异步网络恳求是在其结构的并发行列中,这时分数据还没恳求返回,dispatch_group_async就走完了,这时分就履行了dispatch_group_notify,达不到想要的作用。

那咱们难道用信号量去控制吗,既然要用信号量的话,咱们干嘛还要用dispatch_group_t去做呢,这儿咱们直接用 dispatch_group_enterdispatch_group_leave 就好了。

dispatch_group_enterdispatch_group_leave是等同于dispatch_group_async这个作用的,但用法便是这么美妙。

- (void)dispatch_group_request1
{
    // 创立1个行列组
  dispatch_group_t group = dispatch_group_create(); 
    // 创立1个并发行列
  dispatch_queue_t queue = dispatch_queue_create("jj.com", DISPATCH_QUEUE_CONCURRENT);
    // 进组
  dispatch_group_enter(group);
    //异步网络恳求
  dispatch_async(queue, ^{
        //模仿网络恳求
    sleep(1);
    NSLog(@"1--%@",[NSThread currentThread]);
        //出组
    dispatch_group_leave(group);
  });
    // 进组
  dispatch_group_enter(group);
     //异步网络恳求
  dispatch_async(queue, ^{
    //模仿网络恳求
    sleep(1);
    NSLog(@"2--%@",[NSThread currentThread]);
        //出组
    dispatch_group_leave(group);
  });
  dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"3--%@",[NSThread currentThread]);
  });
  NSLog(@"0--%@",[NSThread currentThread]);
}

咱们先看下打印成果:

iOS多线程之三:NSThread,NSOperation,GCD超详细总结

看到作用了吗,dispatch_group_enterdispatch_group_leave 有必要是成对出现的,一旦有进组了,dispatch_group_notify就不会调用,直到dispatch_group_leave调用后,才会调取。和信号量其实是差不多的作用的。

dispatch_semaphore

dispatch_semaphore用来初始化信号量,经过信号量的值能够控制线程哪个履行,哪个需求等候。并且设置GCD的最大并发数。值为1的时分,还能达到同步锁的作用,这个下一篇文章会详细说明。

  • dispatch_semaphore_create:创立一个 Semaphore 并初始化信号的总量。

  • dispatch_semaphore_signal:发送一个信号,让信号总量加 1。

  • dispatch_semaphore_wait:如果信号量大于0,则正常履行,并且信号量会减 1 ;如果信号量为 0 ,则会一直等候,等接收到通知信号量大于 0 后才能够正常履行。如果等候的时分会起到堵塞当时线程的作用。

咱们看个例子,如何完成2个使命履行完后,才履行第三个使命。

- (void)dispatch_semaphore_t_request
{
  //创立1个信号量,且信号量的值为0
  dispatch_semaphore_t sema = dispatch_semaphore_create(0);
  // 并发行列
  dispatch_queue_t queue = dispatch_queue_create("jj.com", DISPATCH_QUEUE_CONCURRENT);
  dispatch_async(queue, ^{
    sleep(1);
    NSLog(@"1--%@",[NSThread currentThread]);
    //信号值+1
    dispatch_semaphore_signal(sema);
  });
  dispatch_async(queue, ^{
    sleep(1);
    NSLog(@"2--%@",[NSThread currentThread]);
    //信号值+1
    dispatch_semaphore_signal(sema);
  });
  dispatch_async(dispatch_get_main_queue(), ^{
    // 等候2个
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    NSLog(@"0--%@",[NSThread currentThread]);
  });
}

咱们先看下打印成果:

iOS多线程之三:NSThread,NSOperation,GCD超详细总结

的确是完成了2个使命履行完,才走后边的使命。

咱们简略的剖析一下:3个都是异步函数调用,所以3个都是并发进行的,使命1进去后,睡觉1s,使命2进去后,也睡觉1s,使命0进去后,按理其它2个都睡觉,它就直接走dispatch_semaphore_wait了。

这时分,咱们创立的信号量的值为0,只能等候了。 等使命2睡醒了,履行了dispatch_semaphore_signal,信号值为1了,所以第一个dispatch_semaphore_wait就能够走了,且信号量的值减1。这时分又遇到第二个dispatch_semaphore_wait,也只能等。直到使命1的履行dispatch_semaphore_signal后,信号量加1了,能够持续履行使命0。

咱们不管使命1和使命2谁先履行完,咱们最终的使命都需求等候他们履行完才能够履行,由于dispatch_semaphore_waitdispatch_semaphore_signal是一一对应的。

Dispatch_source

一般咱们用Dispatch_source,用的做多便是计时器了。dispatch_source_t的定时器不受RunLoop影响,并且dispatch_source_t是系统等级的源事情,精度很高,系统主动触发。

- (void)dispatch_source_request
{
  if (_timer) {
    //计时器在运转,不动
    return;
  }
  // 创立定时器
  self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
  // 设置定时器时刻
  dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
  // 设置事情触发的回调
  dispatch_source_set_event_handler(_timer, ^{
    if (self.timeout <= 0) {
      dispatch_source_cancel(self.timer);
      self.timer = nil;
    }else {
      dispatch_async(dispatch_get_main_queue(), ^{
        self.timeout--;
        NSLog(@"计时--%ld", self.timeout);
      });
    }
  });
  // 开端履行
  dispatch_resume(_timer);
  // 暂停
//  dispatch_suspend(timer);
}

总结

  • NSThread:运用更加面向目标,简略易用,可直接操作线程目标,需求手动办理生命周期。
  • NSOperation:基于 GCD 封装,运用更加面向目标,可操作依靠关系,优先级,以及最大并发数,主动办理生命周期。
  • GCD:旨在替换 NSTread 等线程技术,可灵敏操作线程和行列,附有其它强大功能,主动办理生命周期。

参考资料

  • iOS进阶之多线程–NSThread详解
  • iOS多线程:『NSOperation、NSOperationQueue』详尽总结
  • iOS多线程:『GCD』详尽总结