iOS多线程之NSThread

iOS多线程开发基础概念

进程 VS 线程

  • 进程:程序的一次执行,是正在执行的程序的实例,它是Unix的一个基本概念,进程通过Process ID来唯一标识。进程依然以一个或者多个线程的容器形式保存下来。每个进程之间相互独立,每个进程均运行在其专用且受到保护的范围内。

  • 线程:最小调度单元,一个进程可以存在多个线程,一个进程内的所有线程都共享虚拟和内存空间,文件描述和各种句柄。线程表示的是底层的机器寄存器状态以及各种调度统计数据,线程从设计上提供了调度所需要的大量信息,同时又尽可能地维持最小开销。UI线程和main函数都是主线程

并行 VS 并发 VS 串行

  • 并行:线程在同一时刻发生
  • 并发:多个线程在同一时间段内执行,但在同一时间点只有一个在执行
  • 串行:线程执行只能按照逐一先后有序执行

同步 VS 异步

  • 同步:只能在当前线程按照先后顺序一次执行,不开启新线程
  • 异步:可以在当前线程开启多个新线程执行,可不按顺序执行

任务 VS 队列

  • 任务:即操作,在GCD中就是一个dispatch_block_t,NSOperation则是dispatch_block_t的高级封装,添加任务也十分方便。任务的执行有两种方式,同步和异步,区别就在于是否会创建新线程。
  • 队列:用于存放任务,一共两种,分别是串行队列和并行队列,串行队列会按照FIFO的方式执行。GCD中就是dispatch_queue_t,NSOperationQueue则是dispatch_queue_t的高级封装。

线程同步

当执行多线程的时候,会发生同一个资源被不同的线程同时访问或者修改的情况,造成资源抢夺,这个过程中如果没有锁机制就会造成数据混乱的情况。解决临界区资源访问的问题的方法就是加锁,常用的方法有以下几种

  • @synchronized:@synchronized中的代码执行时先检查同步对象是否被另一个线程占用,如果占用该线程就会处于等待状态,直到同步对象被释放。

  • NSLock:使用同步锁可以用来解决资源抢占的问题,使用时把需要加锁的代码放到NSLock的lock和unlock之间,线程A进入加锁代码之后由于已经加锁,线程B就无法访问,只有当线程A执行完加锁代码后解锁,B线程才能访问加锁的代码,注意加锁代码只局限为临界区资源的修改,不要放入过多不相关代码。

  • pthread_mutex_t:同步锁,基于C语言的同步锁机制,使用方法与其他同步锁机制类似。

  • dispatch_semaphore_t:Dispatch Semaphore是持有计数的信号,该计数是多线程编程中的计数类型信号。在Dispatch Semaphore中,使用计数来实现等待执行的功能,每次发送一个信号量,则信号量+1,发送一个等待信号时,信号量-1,如果信号量为0则处于等待状态,直到信号量大于0时才开始执行。最开始的时候创建一个dispatch_semaphore_create(1)的操作,创建一个初始值为1的信号量。每次进入临界区代码时候,就调用dispatch_semaphore_wait等待信号量(此时信号量为0)开始等待,此时其他线程无法进入,执行完后调用dispatch_semaphore_signal发送信号通知(此时信号量为1),其他线程开始进入执行,这样也能够达到线程同步目的。

  • NSRecursiveLock:递归锁,有时候“加锁代码”中存在递归调用,递归开始前加锁,递归调用开始后会重复执行此方法以至于反复执行加锁代码最终造成死锁,这个时候可以使用递归锁来解决。使用递归锁可以在一个线程中反复获取锁而不造成死锁,这个过程中会记录获取锁和释放锁的次数,只有最后两者平衡锁才被最终释放。

  • NSDistributedLock:分布锁,它本身是一个互斥锁,基于文件方式实现锁机制,可以跨进程访问

死锁

提到了锁另外还需要说到死锁,产生死锁的必要条件有四个:互斥条件,不可抢占条件,占有且申请条件,循环等待条件。这四个条件必须同时满足才会发生死锁,GCD中有一个经典的死锁案例,代码如下

dispatch_sync(dispatch_get_main_queue(), ^(void){
  NSLog(@"这里死锁了");
});

原因dispatch_sync是同步线程执行代码 要阻塞当前的主线程,问题是使用的queue是main queue,主线程本来就被阻塞 所以造成了自己等待自己的循环等待,于是死锁。

NSThread

NSThread是轻量级的多线程开发,使用起来也并不复杂,但是使用NSThread需要自己管理线程生命周期。

NSThread方法介绍

  • 动态创建
NSThread * pThread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
  • 静态创建
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
  • 线程开启
[pThread start];
  • 线程暂停
[NSThread sleepForTimeInterval:5.0]; (以暂停5秒为例)
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.0]];
  • 线程取消
[pTherad cancel]; // 取消线程并不会马上停止并退出线程,仅仅好似先标记一个记录状态
  • 线程停止
[NSThread exit]; //exit方法会立即终止除主线程之外的所有线程并退出
  • 获取当前线程
[NSThread currentThread];
  • 获取主线程
[NSThread mainThread];
  • 设置优先级
[NSThread setThreadPriority:1.0];

线程间通信

  • 指定当前线程执行操作
[self performSelector:@selector(run)];
[self performSelector:@selector(run) 
           withObject:nil];
[self performSelector:@selector(run) 
           withObject:nil
           afterDelay:2.0];

这里注意一下:performSelector:withObject:afterDelay:是通过调度定时器实现的,会在下一个RunLoop执行.

  • 在子线程指定主线程执行操作
[self performSelectorOnMainThread:@selector(threadRun) 
                       withObject:nil 
                    waitUntilDone:YES];
  • 指定其他线程执行操作
[self performSelector:@selector(threadRun) 
             onThread:newThread 
           withObject:nil
        waitUntilDone:YES]; //这里指定为某个线程
[self performSelectorInBackground:@selector(threadRun) 
                       withObject:nil];//这里指定为后台线程

推荐阅读更多精彩内容