iOS多线程详解:实践篇

本文深入探讨了iOS中多线程的实现,包括Pthreads、NSThread、GCD和NSOperation的使用方法。Pthreads作为跨平台的线程API,具有高度可移植性,但在iOS开发中较少使用。NSThread是面向对象的轻量级线程实现,需要手动管理线程生命周期。GCD是基于C语言的并行计算框架,简化了线程管理和任务调度。NSOperation是基于GCD的抽象类,提供了更高级别的线程控制,如操作依赖和优先级设置。文章通过实例展示了四种线程技术的使用场景和交互,以及如何确保线程安全。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

iOS多线程实践中,常用的就是子线程执行耗时操作,然后回到主线程刷新UI。在iOS中每个进程启动后都会建立一个主线程(UI线程),这个线程是其他线程的父线程。由于在iOS中除了主线程,其他子线程是独立于Cocoa Touch的,所以只有主线程可以更新UI界面。iOS多线程开发实践方式有4种,分别为Pthreads、NSThread、GCD、NSOperation,下面分别讲一讲各自的使用方式,以及优缺点。

pthread: 跨平台,适用于多种操作系统,可移植性强,是一套纯C语言的通用API,且线程的生命周期需要程序员自己管理,使用难度较大,所以在实际开发中通常不使用。 NSThread: 基于OC语言的API,使得其简单易用,面向对象操作。线程的声明周期由程序员管理,在实际开发中偶尔使用。 GCD: 基于C语言的API,充分利用设备的多核,旨在替换NSThread等线程技术。线程的生命周期由系统自动管理,在实际开发中经常使用。 NSOperation: 基于OC语言API,底层是GCD,增加了一些更加简单易用的功能,使用更加面向对象。线程生命周期由系统自动管理,在实际开发中经常使用。

Pthreads

引自 维基百科 实现POSIX 线程标准的库常被称作Pthreads,一般用于Unix-like POSIX 系统,如Linux、 Solaris。但是Microsoft Windows上的实现也存在,例如直接使用Windows API实现的第三方库pthreads-w32;而利用Windows的SFU/SUA子系统,则可以使用微软提供的一部分原生POSIX API。

其实,这就是一套在很多操作系统上都通用的多线程API,所以移植性很强,基于C封装的一套线程框架,iOS上也是适用的。

Pthreads创建线程
- (void)onThread {// 1. 创建线程: 定义一个pthread_t类型变量pthread_t thread;// 2. 开启线程: 执行任务pthread_create(&thread, NULL, run, NULL);// 3. 设置子线程的状态设置为detached,该线程运行结束后会自动释放所有资源pthread_detach(thread);
}

void * run(void *param) {NSLog(@"%@", [NSThread currentThread]);return NULL;
} 

打印结果: 2018-03-16 11:06:12.298115+0800 ocgcd[13744:5710531] <NSThread: 0x1c026f100>{number = 4, name = (null)}

如果出现'pthread_create' is invalid in C99报错,原因是没有导入#import <pthread.h>

——pthread_create(&thread, NULL, run, NULL); 中各项参数含义:——

  • 第一个参数&thread是线程对象,指向线程标识符的指针
  • 第二个是线程属性,可赋值NULL
  • 第三个run表示指向函数的指针(run对应函数里是需要在新线程中执行的任务)
  • 第四个是运行函数的参数,可赋值NULL
Pthreads其他相关方法
  • pthread_create():创建一个线程
  • pthread_exit():终止当前线程
  • pthread_cancel():中断另外一个线程的运行
  • pthread_join():阻塞当前的线程,直到另外一个线程运行结束
  • pthread_attr_init():初始化线程的属性
  • pthread_attr_setdetachstate():设置脱离状态的属性(决定这个线程在终止时是否可以被结合)
  • pthread_attr_getdetachstate():获取脱离状态的属性
  • pthread_attr_destroy():删除线程的属性
  • pthread_kill():向线程发送一个信号
Pthreads常用函数与功能
  • pthread_t

pthread_t用于表示Thread ID,具体内容根据实现的不同而不同,有可能是一个Structure,因此不能将其看作为整数。

  • pthread_equal

pthread_equal函数用于比较两个pthread_t是否相等。

int pthread_equal(pthread_t tid1, pthread_t tid2) 
  • pthread_self

pthread_self函数用于获得本线程的thread id。

pthread _t pthread_self(void); 
Pthreads实现互斥锁

锁可以被动态或静态创建,可以用宏PTHREAD_MUTEX_INITIALIZER来静态的初始化锁,采用这种方式比较容易理解,互斥锁是pthread_mutex_t的结构体,而这个宏是一个结构常量,如下可以完成静态的初始化锁:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;也可以用pthread_mutex_init函数动态的创建,函数原型如:int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t * attr)

总共有100张火车票,开启两个线程,北京和上海两个窗口同时卖票,卖一张票就减去库存,使用锁,保证北京和上海卖票的库存是一致的。实现如下。

#import "ViewController.h"
#include <pthread.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view, typically from a nib.[self onThread];
}

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
NSMutableArray *tickets;

- (void)onThread {tickets = [NSMutableArray array];//生成100张票for (int i = 0; i < 100; i++) {[tickets addObject:[NSNumber numberWithInt:i]];}//线程1 北京卖票窗口// 1. 创建线程1: 定义一个pthread_t类型变量pthread_t thread1;// 2. 开启线程1: 执行任务pthread_create(&thread1, NULL, run, NULL);// 3. 设置子线程1的状态设置为detached,该线程运行结束后会自动释放所有资源pthread_detach(thread1);//线程2 上海卖票窗口// 1. 创建线程2: 定义一个pthread_t类型变量pthread_t thread2;// 2. 开启线程2: 执行任务pthread_create(&thread2, NULL, run, NULL);// 3. 设置子线程2的状态设置为detached,该线程运行结束后会自动释放所有资源pthread_detach(thread2);

}

void * run(void *param) {while (true) {//锁门,执行任务pthread_mutex_lock(&mutex);if (tickets.count > 0) {NSLog(@"剩余票数%ld, 卖票窗口%@", tickets.count, [NSThread currentThread]);[tickets removeLastObject];[NSThread sleepForTimeInterval:0.2];}else {NSLog(@"票已经卖完了");//开门,让其他任务可以执行pthread_mutex_unlock(&mutex);break;}//开门,让其他任务可以执行pthread_mutex_unlock(&mutex);}return NULL;
}

@end 

打印结果: 2018-03-16 11:47:01.069412+0800 ocgcd[13758:5723862] 剩余票数100, 卖票窗口<NSThread: 0x1c0667600>{number = 5, name = (null)} 2018-03-16 11:47:01.272654+0800 ocgcd[13758:5723863] 剩余票数99, 卖票窗口<NSThread: 0x1c0672b40>{number = 6, name = (null)} 2018-03-16 11:47:01.488456+0800 ocgcd[13758:5723862] 剩余票数98, 卖票窗口<NSThread: 0x1c0667600>{number = 5, name = (null)} 2018-03-16 11:47:01.691334+0800 ocgcd[13758:5723863] 剩余票数97, 卖票窗口<NSThread: 0x1c0672b40>{number = 6, name = (null)} … 2018-03-16 11:47:12.110962+0800 ocgcd[13758:5723862] 剩余票数46, 卖票窗口<NSThread: 0x1c0667600>{number = 5, name = (null)} 2018-03-16 11:47:12.316060+0800 ocgcd[13758:5723863] 剩余票数45, 卖票窗口<NSThread: 0x1c0672b40>{number = 6, name = (null)} 2018-03-16 11:47:12.529002+0800 ocgcd[13758:5723862] 剩余票数44, 卖票窗口<NSThread: 0x1c0667600>{number = 5, name = (null)} 2018-03-16 11:47:12.731459+0800 ocgcd[13758:5723863] 剩余票数43, 卖票窗口<NSThread: 0x1c0672b40>{number = 6, name = (null)} … 2018-03-16 11:47:21.103237+0800 ocgcd[13758:5723862] 剩余票数2, 卖票窗口<NSThread: 0x1c0667600>{number = 5, name = (null)} 2018-03-16 11:47:21.308605+0800 ocgcd[13758:5723863] 剩余票数1, 卖票窗口<NSThread: 0x1c0672b40>{number = 6, name = (null)} 2018-03-16 11:47:21.511062+0800 ocgcd[13758:5723862] 票已经卖完了 2018-03-16 11:47:21.511505+0800 ocgcd[13758:5723863] 票已经卖完了

对锁的操作主要包括加锁pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁pthread_mutex_trylock()三个。 pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。

NSThread

NSThread是面向对象的,封装程度最小最轻量级的,使用更灵活,但要手动管理线程的生命周期、线程同步和线程加锁等,开销较大。 NSThread的基本使用比较简单,可以动态创建初始化NSThread对象,对其进行设置然后启动;也可以通过NSThread的静态方法快速创建并启动新线程;此外NSObject基类对象还提供了隐式快速创建NSThread线程的performSelector系列类别扩展工具方法;NSThread还提供了一些静态工具接口来控制当前线程以及获取当前线程的一些信息。

NSThread创建线程

NSThread有三种创建方式:

  • initWithTarget方式,先创建线程对象,再启动
- (void)onThread {// 创建并启动NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];// 设置线程名[thread setName:@"thread1"];// 设置优先级,优先级从0到1,1最高[thread setThreadPriority:0.9];// 启动[thread start];
}

- (void)run {NSLog(@"当前线程%@", [NSThread currentThread]);
} 

打印结果: 2018-03-16 13:47:25.133244+0800 ocgcd[13811:5776836] 当前线程<NSThread: 0x1c0264480>{number = 4, name = thread1}

  • detachNewThreadSelector显式创建并启动线程
- (void)onThread {// 使用类方法创建线程并自动启动线程[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
}

- (void)run {NSLog(@"当前线程%@", [NSThread currentThread]);
} 

打印结果: 2018-03-16 13:49:34.620546+0800 ocgcd[13814:5777803] 当前线程<NSThread: 0x1c026a940>{number = 5, name = (null)}

  • performSelectorInBackground隐式创建并启动线程
- (void)onThread {// 使用NSObject的方法隐式创建并自动启动[self performSelectorInBackground:@selector(run) withObject:nil];
}

- (void)run {NSLog(@"当前线程%@", [NSThread currentThread]);
} 

打印结果: 2018-03-16 13:54:33.451895+0800 ocgcd[13820:5780922] 当前线程<NSThread: 0x1c4460280>{number = 4, name = (null)}

NSThread方法
//获取当前线程
 +(NSThread *)currentThread; 
//创建线程后自动启动线程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;
//是否是多线程
+ (BOOL)isMultiThreaded;
//线程字典
- (NSMutableDictionary *)threadDictionary;
//线程休眠到什么时间
+ (void)sleepUntilDate:(NSDate *)date;
//线程休眠多久
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
//取消线程
- (void)cancel;
//启动线程
- (void)start;
//退出线程
+ (void)exit;
//线程优先级
+ (double)threadPriority;
+ (BOOL)setThreadPriority:(double)p;
- (double)threadPriority NS_AVAILABLE(10_6, 4_0);
- (void)setThreadPriority:(double)p NS_AVAILABLE(10_6, 4_0);
//调用栈返回地址
+ (NSArray *)callStackReturnAddresses NS_AVAILABLE(10_5, 2_0);
+ (NSArray *)callStackSymbols NS_AVAILABLE(10_6, 4_0);
//设置线程名字
- (void)setName:(NSString *)n NS_AVAILABLE(10_5, 2_0);
- (NSString *)name NS_AVAILABLE(10_5, 2_0);
//获取栈的大小
- (NSUInteger)stackSize NS_AVAILABLE(10_5, 2_0);
- (void)setStackSize:(NSUInteger)s NS_AVAILABLE(10_5, 2_0);
// 获得主线程
+ (NSThread *)mainThread;
//是否是主线程
- (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0);
+ (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0); // reports whether current thread is main
+ (NSThread *)mainThread NS_AVAILABLE(10_5, 2_0);
//初始化方法
- (id)init NS_AVAILABLE(10_5, 2_0); // designated initializer
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument NS_AVAILABLE(10_5, 2_0);
//是否正在执行
- (BOOL)isExecuting NS_AVAILABLE(10_5, 2_0);
//是否执行完成
- (BOOL)isFinished NS_AVAILABLE(10_5, 2_0);
//是否取消线程
- (BOOL)isCancelled NS_AVAILABLE(10_5, 2_0);
- (void)cancel NS_AVAILABLE(10_5, 2_0);
//线程启动
- (void)start NS_AVAILABLE(10_5, 2_0);
- (void)main NS_AVAILABLE(10_5, 2_0); // thread body method
@end
//多线程通知
FOUNDATION_EXPORT NSString * const NSWillBecomeMultiThreadedNotification;
FOUNDATION_EXPORT NSString * const NSDidBecomeSingleThreadedNotification;
FOUNDATION_EXPORT NSString * const NSThreadWillExitNotification;

@interface NSObject (NSThreadPerformAdditions)
//与主线程通信
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;// equivalent to the first method with kCFRunLoopCommonModes
//与其他子线程通信
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);// equivalent to the first method with kCFRunLoopCommonModes
//隐式创建并启动线程
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg NS_AVAILABLE(10_5, 2_0); 
NSThread线程状态
  • 启动线程
// 线程启动
- (void)start; 
  • 阻塞线程
// 线程休眠到某一时刻
+ (void)sleepUntilDate:(NSDate *)date;
// 线程休眠多久
+ (void)sleepForTimeInterval:(NSTimeInterval)ti; 
  • 结束线程
// 结束线程
+ (void)exit; 

关于cancel的疑问

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值