多线程的那些事儿

在 iOS 中其实目前有 3 套多线程方案,他们分别是:

NSThread

GCD

NSOperation & NSOperationQueue

NSThread

这套方案是经过苹果封装后的,并且完全面向对象的。所以你可以直接操控线程对象,非常直观和方便。但是,它的生命周期还是需要我们手动管理,所以这套方案也是偶尔用用,比如 [NSThread currentThread],它可以获取当前线程类,你就可以知道当前线程的各种属性,用于调试十分方便。

  // 创建

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

  // 启动

  [thread start];

//创建自动启动

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

//取消线程

- (void)cancel;

//启动线程

- (void)start;

//判断某个线程的状态的属性

@property (readonly, getter=isExecuting) BOOL executing;

@property (readonly, getter=isFinished) BOOL finished;

@property (readonly, getter=isCancelled) BOOL cancelled;

//设置和获取线程名字

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

-(NSString *)name;

//获取当前线程信息

+ (NSThread *)currentThread;

//获取主线程信息

+ (NSThread *)mainThread;

//使当前线程暂停一段时间,或者暂停到某个时刻

+ (void)sleepForTimeInterval:(NSTimeInterval)time;

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

GCD

Grand Central Dispatch。它是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们管理,我们只需要告诉干什么就行。同时它使用的也是 c语言,不过由于使用了 Block(Swift里叫做闭包),使得使用起来更加方便,而且灵活。所以基本上大家都使用 GCD 这套方案。

在 GCD 中,加入了两个非常重要的概念: 任务 和 队列

任务:

即操作,你想要干什么,说白了就是一段代码,在 GCD 中就是一个 Block,所以添加任务十分方便。任务有两种执行方式: 同步执行 和 异步执行,他们之间的区别是 是否会创建新的线程。

同步(sync) 和 异步(async) 的主要区别在于会不会阻塞当前线程,直到 Block 中的任务执行完毕!

如果是 同步(sync) 操作,它会阻塞当前线程并等待 Block 中的任务执行完毕,然后当前线程才会继续往下运行。

如果是 异步(async)操作,当前线程会直接往下执行,它不会阻塞当前线程。

队列:

用于存放任务。一共有两种队列, 串行队列 和 并行队列。

串行队列 中的任务会根据队列的定义 FIFO 的执行,一个接一个的先进先出的进行执行。

串行同步(当前线程逐个进行)串行异步(其他线程逐步进行)

并行队列 中的任务根据同步或异步有不同的执行方式。

并行同步(当前线程逐个进行)并行异步(多个线程,一起进行)

创建队列

主队列:这是一个特殊的 串行队列。什么是主队列,大家都知道吧,它用于刷新 UI,任何需要刷新 UI 的工作都要在主队列执行,所以一般耗时的任务都要放到别的线程执行。

dispatch_queue_t queue = dispatch_get_main_queue();

自己创建的队列:凡是自己创建的队列都是 串行队列。其中第一个参数是标识符,用于 DEBUG 的时候标识唯一的队列,可以为空。大家可以看xcode的文档查看参数意义。

第二个参数用来表示创建的队列是串行的还是并行的,传入 DISPATCH_QUEUE_SERIAL 或 NULL 表示创建串行队列。传入 DISPATCH_QUEUE_CONCURRENT 表示创建并行队列。

  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", NULL);

全局并行队列:这应该是唯一一个并行队列,只要是并行任务一般都加入到这个队列。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

创建任务

同步任务:不会另开线程 (SYNC)

  dispatch_sync(, ^{

      //code here

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

  });

异步任务:会另开线程 (ASYNC)

  dispatch_async(, ^{

      //code here

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

  });

队列组

队列组可以将很多队列添加到一个组里,这样做的好处是,当这个组里所有的任务都执行完了,队列组会通过一个方法通知我们。

//1.创建队列组

dispatch_group_t group = dispatch_group_create();

//2.创建队列

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//3.多次使用队列组的方法执行任务, 只有异步方法

//3.1.执行3次循环

dispatch_group_async(group, queue, ^{

    for (NSInteger i = 0; i < 3; i++) {

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

    }

});

//3.2.主队列执行8次循环

dispatch_group_async(group, dispatch_get_main_queue(), ^{

    for (NSInteger i = 0; i < 8; i++) {

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

    }

});

//3.3.执行5次循环

dispatch_group_async(group, queue, ^{

    for (NSInteger i = 0; i < 5; i++) {

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

    }

});

//4.都完成后会自动通知

dispatch_group_notify(group, dispatch_get_main_queue(), ^{

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

});

NSOperation和NSOperationQueue

NSOperation 是苹果公司对 GCD 的封装,完全面向对象,所以使用起来更好理解。 大家可以看到 NSOperation 和 NSOperationQueue 分别对应 GCD 的 任务 和 队列 。操作步骤也很好理解:

将要执行的任务封装到一个 NSOperation 对象中。

将此任务添加到一个 NSOperationQueue 对象中。

然后系统就会自动在执行任务。至于同步还是异步、串行还是并行请继续往下看:

添加任务

值得说明的是,NSOperation 只是一个抽象类,所以不能封装任务。但它有 2 个子类用于封装任务。分别是:NSInvocationOperation 和 NSBlockOperation 。创建一个 Operation 后,需要调用 start 方法来启动任务,它会 默认在当前队列同步执行。当然你也可以在中途取消一个任务,只需要调用其 cancel 方法即可。

NSInvocationOperation : 需要传入一个方法名。

  //1.创建NSInvocationOperation对象

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

  //2.开始执行

  [operation start];

NSBlockOperation

  //1.创建NSBlockOperation对象

  NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{

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

  }];

  //2.开始任务

  [operation start];

之前说过这样的任务,默认会在当前线程执行。但是 NSBlockOperation 还有一个方法:addExecutionBlock: ,通过这个方法可以给 Operation 添加多个执行 Block。这样 Operation 中的任务 会并发执行,它会 在主线程和其它的多个线程 执行这些任务。

      //1.创建NSBlockOperation对象

      NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{

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

      }];

      //添加多个Block

      for (NSInteger i = 0; i < 5; i++) {

          [operation addExecutionBlock:^{

              NSLog(@"第%ld次:%@", i, [NSThread currentThread]);

          }];

      }

      //2.开始任务

      [operation start];

自定义Operation

除了上面的两种 Operation 以外,我们还可以自定义 Operation。自定义 Operation 需要继承 NSOperation 类,并实现其 main() 方法,因为在调用 start() 方法的时候,内部会调用 main() 方法完成相关逻辑。所以如果以上的两个类无法满足你的欲望的时候,你就需要自定义了。你想要实现什么功能都可以写在里面。除此之外,你还需要实现 cancel() 在内的各种方法。所以这个功能提供给高级玩家,我在这里就不说了,等我需要用到时在研究它,到时候可能会再做更新。

创建队列

看过上面的内容就知道,我们可以调用一个 NSOperation 对象的 start() 方法来启动这个任务,但是这样做他们默认是 同步执行 的。就算是 addExecutionBlock 方法,也会在 当前线程和其他线程 中执行,也就是说还是会占用当前线程。这是就要用到队列 NSOperationQueue 了。而且,按类型来说的话一共有两种类型:主队列、其他队列。只要添加到队列,会自动调用任务的 start() 方法

主队列

每套多线程方案都会有一个主线程。这是一个特殊的线程,必须串行。所以添加到主队列的任务都会一个接一个地排着队在主线程处理。

//OBJECTIVE-C

NSOperationQueue *queue = [NSOperationQueue mainQueue];

其他队列

因为主队列比较特殊,所以会单独有一个类方法来获得主队列。那么通过初始化产生的队列就是其他队列了,因为只有这两种队列,除了主队列,其他队列就不需要名字了。

注意:其他队列的任务会在其他线程并行执行。

//1.创建一个其他队列    

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

//2.创建NSBlockOperation对象

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{

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

}];

//3.添加多个Block

for (NSInteger i = 0; i < 5; i++) {

    [operation addExecutionBlock:^{

        NSLog(@"第%ld次:%@", i, [NSThread currentThread]);

    }];

}

//4.队列添加任务

[queue addOperation:operation];

一些常用方法:

BOOL executing; //判断任务是否正在执行

BOOL finished; //判断任务是否完成

void (^completionBlock)(void); //用来设置完成后需要执行的操作

- (void)cancel; //取消任务

- (void)waitUntilFinished; //阻塞当前线程直到此任务执行完毕

NSUInteger operationCount; //获取队列的任务数

- (void)cancelAllOperations; //取消队列中所有的任务

- (void)waitUntilAllOperationsAreFinished; //阻塞当前线程直到此队列中的所有任务执行完毕

[queue setSuspended:YES]; // 暂停queue

[queue setSuspended:NO]; // 继续queue

这就是多线程的一些大概的基本东西了。希望大家可以收获一些东西,有不足和错误的地方也希望指出。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容