iOS 多线程(线程的生命周期)

2,155 阅读8分钟

多线程的基本概念

进程的定义

  • 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
  • 进程是指在系统中正在运行的一个应用程序
  • 每个进程相互是独立的,每个进程均运行在其专用的且受保护的内存

线程的定义

  • 线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。
  • 在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。
  • 线程是进程的基本执行单位,一个进程的所有任务都在线程中执行
  • 进程要想执行任务,必须得有线程,进程至少要有一条线程
  • 程序启动会默认开启一条线程,这条线程被称为主线程或UI线程

进程与线程的关系

  • 地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间
  • 资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。
  • 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
  • 进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程
  • 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
  • 线程是处理器调度的基本单位,但是进程不是。
  • 两者均可并发执行。

多线程的意义

  • 优点
    • 使用线程可以把占据时间长的程序中的任务放到后台去处理
    • 用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度
    • 程序的运行速度可能加快
    • 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下可以释放一些珍贵的资源如内存占用等等。
  • 缺点
    • 创建子线程是有开销的,iOS下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512K、主线程1MB,也可以使用-setStackSize:设置,但必须是4K的倍数,而且最小是16K),创建线程大约需要90毫秒的创建时间
    • 如果开启大量线程,会降低程序的性能
    • 线程越多,CPU在调度线程的开销就越大
    • 程序设计更加复杂:比如线程之间的通信、多线程的数据共享

多线程的原理

  • 同一时间,CPU只能处理一条线程,只有一条线程在工作(执行)
  • 多线程并发(同时)执行,其实就是CPU执行快速地在多条线程之间调度(切换)
  • 下图是多线程的图解,CPU在线程1、线程2、线程3、线程4中不断的切换,这样就可以达到一个并发的效果

多线程的生命周期

线程的生命周期是:新建 - 就绪 - 运行 - 阻塞 - 死亡

  • 新建:实例化线程对象
  • 就绪:向线程对象发送start消息,线程对象被加入可调度线程池等待CPU调度。
  • 运行:CPU 负责调度可调度线程池中线程的执行。线程执行完成之前,状态可能会在就绪和运行之间来回切换。就绪和运行之间的状态变化由CPU负责,程序员不能干预。
  • 阻塞:当满足某个预定条件时,可以使用休眠或锁,阻塞线程执行。sleepForTimeInterval(休眠指定时长),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥锁)。
  • 死亡:正常死亡,线程执行完毕。非正常死亡,当满足某个条件后,在线程内部中止执行/在主线程中止线程对象

还有线程的exit和cancel [NSThread exit]:一旦强行终止线程,后续的所有代码都不会被执行。
[thread cancel]取消:并不会直接取消线程,只是给线程对象添加 isCancelled 标记。

下图表示线程的生命周期

线程的执行流程如下:

  • 新建 - 就绪(在可调度线程池中,等待被CPU的调度执行) - 运行
  • 运行 - CPU切换到其他的线程 - 就绪
  • 运行 - 调用的sleep方法 - 阻塞 - sleep的时间到了 - 就绪
  • 运行 - 任务执行完成 - 死亡
  • 运行 - exit - 死亡

线程加入到可调度线程池过程

多线程实现方案

iOS中实现多线程的方案有以下四种

pthread 开启线程

pthread_t tid;
int tret = pthread_create(&tid, NULL, thread_run, NULL);

NSThread 开启线程

//继承自NSObject的类的对象 都可以调用这个方法 只不过拿不到线程对象
[self performSelectorInBackground:@selector(NSThreadDemo:) withObject:@"NSObjectCategory"];

// 通过类方法创建 分离出来一个线程 不需要手动开启线程  自动开启线程并且执行方法
[NSThread detachNewThreadSelector:@selector(NSThreadDemo:) toTarget:self withObject:@"classMethodThread"];

// 通过对象方法来创建线程 并且需要手动启动线程
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(NSThreadDemo:) object:@"objectMethodThread"];
//手动启动线程
[thread start];

GCD 开启线程

// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);

// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);

// 同步执行任务创建方法
dispatch_sync(queue, ^{
    // 这里放同步执行任务代码
});

// 异步执行任务创建方法
dispatch_async(queue, ^{
    // 这里放异步执行任务代码
});

NSOperation 开启线程

//NSInvocationOperation   封装操作
NSInvocationOperation *operation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];

//执行操作
[operation start];

//创建NSBlockOperation操作对象
NSBlockOperation *operation=[NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"NSBlockOperation------%@",[NSThread currentThread]);
}];

//添加操作
[operation addExecutionBlock:^{
    NSLog(@"NSBlockOperation1------%@",[NSThread currentThread]);
}];

[operation addExecutionBlock:^{
    NSLog(@"NSBlockOperation2------%@",[NSThread currentThread]);
}];

//开启执行操作
[operation start];

iOS 中的锁

锁的概念定义

  • 临界区:指的是一块对公共资源进行访问的代码,并非一种机制或是算法。
  • 自旋锁:是用于多线程同步的一种锁,线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁。自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。
  • 互斥锁(Mutex):是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。该目的通过将代码切片成一个一个的临界区而达成。
  • 读写锁:是计算机程序的并发控制的一种同步机制,也称“共享-互斥锁”、“多读者-单写者锁”,用于解决多线程对公共资源读写问题。读操作可并发重入,写操作是互斥的。读写锁通常用互斥锁、条件变量、信号量实现。
  • 信号量(semaphore):是一种更高级的同步机制,互斥锁可以说是semaphore在仅取值0/1时的特例。信号量可以有更多的取值空间,用来实现更加复杂的同步,而不单单是线程间互斥。
  • 条件锁:就是条件变量,当进程的某些资源要求不满足时就进入休眠,也就是锁住了。当资源被分配到了,条件锁打开,进程继续运行。

性能比较

互斥锁

  • NSLock:是Foundation框架中以对象形式暴露给开发者的一种锁,(Foundation框架同时提供了NSConditionLock,NSRecursiveLock,NSCondition)NSLock定义如图,tryLock 和 lock 方法都会请求加锁,唯一不同的是trylock在没有获得锁的时候可以继续做一些任务和处理。lockBeforeDate方法也比较简单,就是在limit时间点之前获得锁,没有拿到返回NO。
  • pthread_mutex:实际运用项目如下
  • @synchronize:实际项目中:AFNetworking中 isNetworkActivityOccurring属性的getter方法

参考文章

iOS多线程全套:线程生命周期,多线程的四种解决方案,线程安全问题,GCD的使用,NSOperation的使用