iOS多线程一览

2016-12-01  本文已影响8人  rayChow

iOS中目前有4套多线程方案,他们分别是:

  • Pthread
  • NSThread
  • GCD
  • NSOperation & NSOperationQueue

Pthreads

定义了创建和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作系统的线程。简单地说,这是一套在很多操作系统上都通用的多线程API,所以移植性很强,在iOS中也是可以的。但是是基于C语言框架的,使用起来这酸爽。。。仅作了解。

OBJECTIVE-C

首先要包含头文件

#import<pthread.h>

然后创建线程,并执行任务

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
pthread_t thread;
//创建一个线程并自动执行
pthread_create(&thread, NULL, start, NULL);
}
void *start(void *data) {
NSLog(@"%@", [NSThread currentThread]);
return NULL;
}

看代码会发现它需要C语言函数,这就比较蛋疼,更蛋疼的是需要手动处理现成的各个状态的转换,即管理生命周期,比如,这段代码随谈创建了一个线程,但是并没有销毁。


NSThread

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

用法:

创建并启动

  1. 先创建线程类,再启动
OBJECTIVE-C
 //创建
 NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:nil];
 //启动
 [thread start];
SWIFT
  //创建
  let thread = NSThread(target: self, selector: "run:", object: nil)
  //启动
  thread.start()
  1. 创建并自动启动
OBJECTIVE-C
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
SWIFT
  NSThread.detachNewThreadSelector("run:", toTarget: self, withObject: nil)
  1. 使用NSObject的方法创建并自动启动
OBJECTIVE-C
 [self performSelectorInBackground:@selector(run:) withObject:nil];
SWIFT

苹果认为performSelector:不安全,所以在Swift中去掉了这个方法。

其他用法

除了创建启动以外,NSThread还有很多用法,常见的用法如下:

OBJECTIVE-C
//取消线程
- (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;
SWIFT

Swift的方法名字和OC的方法名都一样,我就不浪费空间列举出来了。

其实,NSThread 用起来也挺简单的,因为它就那几种方法。同时,我们也只有在一些非常简单的场景才会用 NSThread, 毕竟它还不够智能,不能优雅地处理多线程中的其他高级概念。所以接下来要说的内容才是重点。


GCD

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

任务和队列

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

创建队列

OBJECTIVE-C
dispatch_queue_t queue = ispatch_get_main_queue();
SWIFT
 let queue = ispatch_get_main_queue()
OBJECTIVE-C
 //串行队列
 dispatch_queue_t queue
 dispatch_queue_create("tk.bourne.testQueue", NULL);
 dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);
 //并行队列
 dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);
SWIFT
 //串行队列
 let queue dispatch_queue_create("tk.bourne.testQueue", nil);
 let queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL)
 //并行队列
 let queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT)
OBJECTIVE-C
 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
SWIFT
 let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

创建任务

  1. 同步任务:会阻塞当前线程
OBJECTIVE-C
  dispatch_sync(<#queue#>, ^{
      //code here
      NSLog(@"%@", [NSThread currentThread]);
  });
SWIFT
  dispatch_sync(<#queue#>, { () -> Void in
      //code here
      println(NSThread.currentThread())
  })
  1. 异步任务:不会阻塞当前线程
OBJECTIVE-C
  dispatch_async(<#queue#>, ^{
      //code here
      NSLog(@"%@", [NSThread currentThread]);
  });
SWIFT
  dispatch_async(<#queue#>, { () -> Void in
      //code here
      println(NSThread.currentThread())
  })

为了更好的理解同步和异步,和各种队列的使用,下面看两个示例:
示例一:
以下代码在主线程调用,结果是什么?

NSLog("之前 - %@", NSThread.currentThread())
dispatch_sync(dispatch_get_main_queue(), { () -> Void in 
        NSLog("sync - %@", NSThread.currentThread())
})
NSLog("之后 - %@", NSThread.currentThread())

答案:只会打印第一句:之前 - <NSThread: 0x7fb3a9e16470>{number = 1, name = main}
,然后主线程就卡死了,你可以在界面上放一个按钮,你就会发现点不了了。
解释:同步任务会阻塞当前线程,然后把 Block 中的任务放到指定的队列中执行,只有等到 Block 中的任务完成后才会让当前线程继续往下运行。那么这里的步骤就是:打印完第一句后,dispatch_sync
立即阻塞当前的主线程,然后把 Block 中的任务放到main_queue中,可是main_queue中的任务会被取出来放到主线程中执行,但主线程这个时候已经被阻塞了,所以 Block 中的任务就不能完成,它不完成,dispatch_sync就会一直阻塞主线程,这就是死锁现象。导致主线程一直卡死。

示例二:
以下代码会产生什么结果?

let queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL)
   NSLog("之前 - %@", NSThread.currentThread())
    dispatch_async(queue, { () -> Void in
        NSLog("sync之前 - %@", NSThread.currentThread())
        dispatch_sync(queue, { () -> Void in
             NSLog("sync - %@", NSThread.currentThread())
        })
        NSLog("sync之后 - %@", NSThread.currentThread())
   })
  NSLog("之后 - %@", NSThread.currentThread())

答案:
2015-07-30 02:06:51.058 test[33329:8793087] 之前 - <NSThread: 0x7fe32050dbb0>{number = 1, name = main}
2015-07-30 02:06:51.059 test[33329:8793356] sync之前 - <NSThread: 0x7fe32062e9f0>{number = 2, name = (null)}
2015-07-30 02:06:51.059 test[33329:8793087] 之后 - <NSThread: 0x7fe32050dbb0>{number = 1, name = main}
很明显sync - %@sync之后 - %@没有被打印出来!这是为什么呢?我们再来分析一下:

分析:我们按执行顺序一步步来哦:

  1. 使用DISPATCH_QUEUE_SERIAL这个参数,创建了一个 串行队列
  2. 打印出之前 - %@这句。
  3. dispatch_async异步执行,所以当前线程不会被阻塞,于是有了两条线程,一条当前线程继续往下打印出之后 - %@这句, 另一台执行 Block 中的内容打印sync之前 - %@这句。因为这两条是并行的,所以打印的先后顺序无所谓。
  4. 注意,高潮来了。现在的情况和上一个例子一样了。dispatch_sync同步执行,于是它所在的线程会被阻塞,一直等到sync里的任务执行完才会继续往下。于是sync就高兴的把自己 Block 中的任务放到queue中,可谁想queue是一个串行队列,一次执行一个任务,所sync的 Block 必须等到前一个任务执行完毕,可万万没想到的是queue正在执行的任务就是sync阻塞了的那个。于是又发生了死锁。所以sync所在的线程被卡死了。剩下的两句代码自然不会打印。

队列组

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

OBJECTIVE-C
//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]);
});
SWIFT
//1.创建队列组
let group = dispatch_group_create()
//2.创建队列
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

//3.多次使用队列组的方法执行任务, 只有异步方法
//3.1.执行3次循环
dispatch_group_async(group, queue) { () -> Void in
    for _ in 0..<3 {
        NSLog("group-01 - %@", NSThread.currentThread())
    }
}

//3.2.主队列执行8次循环
dispatch_group_async(group, dispatch_get_main_queue()) { () -> Void in
    for _ in 0..<8 {
        NSLog("group-02 - %@", NSThread.currentThread())
    }
}

//3.3.执行5次循环
dispatch_group_async(group, queue) { () -> Void in
    for _ in 0..<5 {
        NSLog("group-03 - %@", NSThread.currentThread())
    }
}

//4.都完成后会自动通知
dispatch_group_notify(group, dispatch_get_main_queue()) { () -> Void in
    NSLog("完成 - %@", NSThread.currentThread())
}

注意:

  • func dispatch_barrier_async(_ queue: dispatch_queue_t, _ block: dispatch_block_t)
    :这个方法重点是你传入的 queue,当你传入的 queue 是通过DISPATCH_QUEUE_CONCURRENT参数自己创建的 queue 时,这个方法会阻塞这个 queue注意是阻塞 queue ,而不是阻塞当前线程),一直等到这个 queue 中排在它前面的任务都执行完成后才会开始执行自己,自己执行完毕后,再会取消阻塞,使这个 queue 中排在它后面的任务继续执行。如果你传入的是其他的 queue, 那么它就和dispatch_async一样了。
  • func dispatch_barrier_sync(_ queue: dispatch_queue_t, _ block: dispatch_block_t)
    :这个方法的使用和上一个一样,传入 自定义的并发队列(DISPATCH_QUEUE_CONCURRENT,它和上一个方法一样的阻塞 queue,不同的是 这个方法还会 阻塞当前线程。如果你传入的是其他的 queue, 那么它就和dispatch_sync
    一样了。

NSOperation和NSOperationQueue

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

  1. 将要执行的任务封装到一个NSOperation对象中。
  2. 将此任务添加到一个NSOperationQueue对象中。
    然后系统就会自动在执行任务。植物同步异步、串行并行继续往下看:

添加任务

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

OBJECTIVE-C
  //1.创建NSInvocationOperation对象
  NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
  //2.开始执行
  [operation start];
OBJECTIVE-C
  //1.创建NSBlockOperation对象
  NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"%@", [NSThread currentThread]);
  }];
  //2.开始任务
  [operation start];
SWIFT
  //1.创建NSBlockOperation对象
  let operation = NSBlockOperation { () -> Void in
      println(NSThread.currentThread())
  }
  //2.开始任务
  operation.start()
之前说这样的任务会默认在当前线程执行。但是```NSBlockOperation ```还有一个方法:```addExecutionBlock ```,通过这个方法可以给Operation添加多个执行Block。这样Operation中的任务会并发执行,他会在主线程和其他的多个线程执行这些任务。

并且,addExecutionBlock方法必须在start方法前调用,否则就会报错。

创建队列

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

NSOperationQueueGCD的队列 相比较就会发现,这里没有串行队列,那如果我想要10个任务在其他线程串行的执行怎么办?
这就是苹果封装的妙处,你不用管串行、并行、同步、异步这些名词。NSOperationQueue 有一个参数 maxConcurrentOperationCount 最大并发数,用来设置最多可以让多少个任务同时执行。当你把它设置为 1 的时候,他不就是串行了嘛!
NSOperationQueue 还有一个添加任务的方法,- (void)addOperationWithBlock:(void (^)(void))block; ,这是不是和 GCD 差不多?这样就可以添加一个任务到队列中了,十分方便。

NSOperation 有一个非常实用的功能,那就是添加依赖。比如有 3 个任务:A: 从服务器上下载一张图片,B:给这张图片加个水印,C:把图片返回给服务器。这时就可以用到依赖了:

OBJECTIVE-C
//1.任务一:下载图片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"下载图片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//2.任务二:打水印
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"打水印   - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//3.任务三:上传图片
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"上传图片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//4.设置依赖
[operation2 addDependency:operation1];      //任务二依赖任务一
[operation3 addDependency:operation2];      //任务三依赖任务二

//5.创建队列并加入任务
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];
SWIFT
//1.任务一:下载图片
let operation1 = NSBlockOperation { () -> Void in
    NSLog("下载图片 - %@", NSThread.currentThread())
    NSThread.sleepForTimeInterval(1.0)
}

//2.任务二:打水印
let operation2 = NSBlockOperation { () -> Void in
    NSLog("打水印   - %@", NSThread.currentThread())
    NSThread.sleepForTimeInterval(1.0)
}

//3.任务三:上传图片
let operation3 = NSBlockOperation { () -> Void in
    NSLog("上传图片 - %@", NSThread.currentThread())
    NSThread.sleepForTimeInterval(1.0)
}

//4.设置依赖
operation2.addDependency(operation1)    //任务二依赖任务一
operation3.addDependency(operation2)    //任务三依赖任务二

//5.创建队列并加入任务
let queue = NSOperationQueue()
queue.addOperations([operation3, operation2, operation1], waitUntilFinished: false)

注意:

其他方法

以上是主要方法,下面还有一些常用方法:

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

线程同步

所谓县城同步就是为了防止多线程抢夺同一个资源造成的数据安全问题,所采取的一种措施。

OBJECTIVE-C
@synchronized(self) {
    //需要执行的代码块
}
SWIFT
objc_sync_enter(self)
//需要执行的代码块
objc_sync_exit(self)
OBJECTIVE-C
 //GCD
 //需要一个全局变量queue,要让所有线程的这个操作都加到一个queue中
 dispatch_sync(queue, ^{
     NSInteger ticket = lastTicket;
     [NSThread sleepForTimeInterval:0.1];
     NSLog(@"%ld - %@",ticket, [NSThread currentThread]);
     ticket -= 1;
     lastTicket = ticket;
 });


 //NSOperation & NSOperationQueue
 //重点:1. 全局的 NSOperationQueue, 所有的操作添加到同一个queue中
 //       2. 设置 queue 的 maxConcurrentOperationCount 为 1
 //       3. 如果后续操作需要Block中的结果,就需要调用每个操作的waitUntilFinished,阻塞当前线程,一直等到当前操作完成,才允许执行后面的。waitUntilFinished 要在添加到队列之后!

 NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
     NSInteger ticket = lastTicket;
     [NSThread sleepForTimeInterval:1];
     NSLog(@"%ld - %@",ticket, [NSThread currentThread]);
     ticket -= 1;
     lastTicket = ticket;
 }];

 [queue addOperation:operation];

 [operation waitUntilFinished];

 //后续要做的事

延迟执行

所谓延迟执行就是延迟一段时间再执行某段代码。

  // 3秒后自动调用self的run:方法,并且传递参数:@"abc"
  [self performSelector:@selector(run:) withObject:@"abc" afterDelay:3];
  // 创建队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 设置延时,单位秒
double delay = 3; 

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), queue, ^{
    // 3秒后需要执行的任务
});
[NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(run:) userInfo:@"abc" repeats:NO];

单例模式

OBJECTIVE-C
@interface Tool : NSObject <NSCopying>

+ (instancetype)sharedTool;

@end

@implementation Tool

static id _instance;

+ (instancetype)sharedTool {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[Tool alloc] init];
    });

    return _instance;
}

@end
SWIFT
class Tool: NSObject {
    static let sharedTool = Tool()

    // 私有化构造方法,阻止其他对象使用这个类的默认的'()'构造方法
    private override init() {}
}

从其他线程回到主线程的方法

在其他线程操作完成后必须到主线程更新UI。

//Objective-C
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO];

//Swift
//swift 取消了 performSelector 方法。
//Objective-C
dispatch_async(dispatch_get_main_queue(), ^{

});

//Swift
dispatch_async(dispatch_get_main_queue(), { () -> Void in

})
//Objective-C
[[NSOperationQueue mainQueue] addOperationWithBlock:^{

}];

//Swift
NSOperationQueue.mainQueue().addOperationWithBlock { () -> Void in

}
上一篇下一篇

猜你喜欢

热点阅读