IOS中通常一个APP就一个进程,绝大部分情况都是使用多线程进行通信。
线程分主线程和其他线程,主线程在APP存活时一直存在,通常就是UI操作,所以也被称为UI线程。IOS最核心的需求就是要保证用户操作的流畅,所以可能会影响UI体验的动作,例如网络操作,读取本地I/O等,都应该放到其他线程里,避免影响主线程。
现在几乎所有的GUI框架都是单线程的,原因可以点击这里。虽然是篇年代比较久远的文章,但原理不过时。很多人都尝试用多线程来渲染UI,让界面显示的速度更快,但均告失败,实在无法处理渲染时线程间的冲突。最终所有框架都宁可牺牲渲染效率,采用单线程来渲染UI,iOS也不例外。
- NSThread
- Runloop
- 线程间通信
- 线程间同步
- GCD
- NSOperation
NSThread
用于创建线程,线程创建并执行完操作后会自动销毁。有两种方式创建NSThread:
- 使用detachNewThreadSelector来启动一个线程
- 自己创建一个NSThread对象,然后调用它的start方法
// 方式一:使用detachNewThreadSelector来启动一个线程 - (void)createThread1 { [NSThread detachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:self withObject:nil]; } // 方式二:自己创建一个NSThread对象,然后调用它的start方法 - (void)createThread2 { NSThread* myThread = [[NSThread alloc] initWithTarget:self selector:@selector(myThreadMainMethod:) object:nil]; [myThread start]; } - (void)myThreadMainMethod:(id)arg { NSLog(@"%@", @"multiple thread"); }
创建NSThread后,可以修改NSThread的默认配置项:
- 设置线程的堆栈大小:setStackSize
- 配置线程本地变量:threadDictionary
- 设置线程优先级:setThreadPriority:设置0~1间的浮点数
- (void)myThreadMainMethod { self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadWithStackSize) object:nil]; [self.thread setStackSize:204800000]; // 没这行会报错。每个线程的默认栈大小是有上限的,如果超过限制,需要手动提高上限 [self.thread start]; } - (void)threadWithStackSize { int array[10240020]; array[1] = 100; }
创建NSThread后,可以用Runloop管理线程,Runloop是不能脱离线程独立存在的。
Runloop
创建NSThread后,系统为每个线程都提供了Runloop,包括主线程。
Runloop用于管理线程(/source/timer source),是不能脱离线程独立存在的。当线程启动runloop后,没有处理消息时休眠线程以避免占用资源,当有消息来时唤醒线程。
主线程默认启动runloop,其他线程默认是不会常驻内存的,需要启动runloop才能常驻内存。所以当我们不操作APP时,主线程因为有Runloop,不会被销毁而是进入睡眠等待。当用户有手势触发APP时,主线程会重新开始执行。
源码就是个简单的do-while循环:
要让线程常驻内存,以便再次执行任务,需要启动runloop,并且runloop中有source或timer,如果runloop里没有source或timer的话,runloop会立即关闭。
- (void)startRunloopHandleTaskInThread { self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; [self.thread start]; } - (void)run { // 默认线程是不会常驻的,要让线程常驻内存,需要启动runloop,并且runloop中有source或timer NSRunLoop *rl = [NSRunLoop currentRunLoop]; // 如果runloop里没有source或timer的话,runloop会立即关闭。在runLoop中添加一个timer NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(timerRun) userInfo:nil repeats:YES]; [rl addTimer:timer forMode:NSRunLoopCommonModes]; // 启动runloop [rl run]; // 如果线程成为了常驻线程,你会发现,不会执行到这行代码,即这个方法不会执行完 NSLog(@"end"); } -(void)timerRun { NSLog(@"%s",__func__); }
创建和销毁线程也是有开销的,通常我们不会创建个只执行一次的线程,常见的第三方库(如AFNetworking)会在创建线程执行任务时,在线程内启用runloop常驻内存。
可以使用:CFRunLoopGetMain获取主线程的runloop,CFRunLoopGetCurrent获取当前线程的runloop。
线程间通信
线程间通信方式有以下几种:
- 直接通信
- 条件变量
- 全局变量,共享内存
- Run loop sources
- Ports and sockets
- 消息队列
- Cocoa分布式对象
直接通信:
- (void)testDirectMessaging { self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(startThreadWithRunloop) object:nil]; [self.thread start]; // 创建并启动线程,并启动runloop [self performSelector:@selector(printInOtherThread) onThread:self.thread withObject:nil waitUntilDone:YES]; } - (void)startThreadWithRunloop { NSPort* workPort = [NSMachPort port]; [[NSRunLoop currentRunLoop] addPort:workPort forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] run]; } - (void)printInOtherThread { NSLog(@"%@", @"printInOtherThread"); }
条件变量(NSCondition)(lock,wait,waitUntilDate,signal,broadcast,unlock)主要解决生产者/消费者的问题。两个线程间用条件变量的同步线程的方式,一图胜千言:
@property (strong, nonatomic) NSMutableArray *products; @property (strong, nonatomic) NSCondition *condition; -(void)testCondition { NSLog(@"开始生产者消费者模式"); //创建一个生产者 [NSThread detachNewThreadSelector:@selector(createProducer) toTarget:self withObject:nil]; //创建三个消费者 for (int i = 0; i < 3; i++) { NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(createConsuser) object:nil]; thread.name = [NSString stringWithFormat:@"第%d个消费者",i]; [thread start]; } } - (void)createProducer { for (int i = 0; i < 9; i++) { [NSThread sleepForTimeInterval:arc4random() % 5]; [self.condition lock]; NSObject *product = [[NSObject alloc] init]; [self.products addObject:product]; NSLog(@"生产了一个产品"); [self.condition signal]; // 发送信号通知消费者 [self.condition unlock]; } } - (void)createConsuser { while (true) { [self.condition lock]; while (self.products.count == 0) { [self.condition wait]; } [self.products removeObjectAtIndex:0]; NSLog(@"%@消费了一个产品",[NSThread currentThread].name); [self.condition unlock]; } }
线程间同步
线程同步方式有以下几种:
- 原子操作(Atomic Operations)
- 内存栅栏(Memory Barriers and Volatile Variables)
- 锁(NSLocking)
- 条件变量(NSCondition)
- Ports and sockets
- Perform Selector Routines
锁(NSLocking)是最常见的同步方式。包括互斥量(Mutex),读写锁(Read-Write lock),重入锁(Recursive lock),分布锁(Distributed lock),自旋锁(Spin lock)等。
因为NSLocking是个协议,开发中常用NSLock,NSRecursiveLock,NSConditionLock,NSDistributedLock等。也可以直接使用@synchronized关键字来加锁。
NSLock:常规锁
- (void)testNSLock { self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadWithNSLock) object:nil]; [self.thread start]; } - (void)threadWithNSLock { static int count = 0; if (count == 5) { return; } if (!self.lock) { self.lock = [NSLock new]; } [self.lock lock]; [NSThread sleepForTimeInterval:1]; NSLog(@"%@-%d", @"threadWithNSLock-", count); // 只执行1次,count为1 count++; [self threadWithNSLock]; // 递归调用自己,因为未解锁,所以count永远为1,不会自增 [self.lock unlock]; // 因为上面递归的存在,这行永远不会被执行到 }
NSRecursiveLock:重入锁
- (void)testNSRecursiveLock { self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadWithNSRecursiveLock) object:nil]; [self.thread start]; } - (void)threadWithNSRecursiveLock { static int count = 0; if (count == 5) { return; } if (!self.recursiveLock) { self.recursiveLock = [NSRecursiveLock new]; } [self.recursiveLock lock]; [NSThread sleepForTimeInterval:1]; NSLog(@"%@-%d", @"threadWithNSRecursiveLock-", count); count++; [self threadWithNSRecursiveLock]; // 同样是递归,同样未解锁,但NSRecursiveLock可以重入,所以count累加了5次,为5 [self.recursiveLock unlock]; // 锁重入5次后,即count累加5次后,这行解锁会被执行5次 }
@synchronized有以下特性:
- 只有传同样的对象给synchronized,才能起到加锁作用。传nil是无法起到加锁作用的。
- 可以重入
- synchronized不会持有传给它的对象,所以在synchronized里面销毁了这个对象,也不会有问题。
- synchronized内部有异常处理,所以不用担心异常产生的时候不会unlcok
@property (nonatomic) NSMutableArray *testArray; - (void)testSynchronize { NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(synchronizedAdd) object:nil]; NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(synchronizedRemove) object:nil]; [thread1 start]; [thread2 start]; // 依次打印出 thread 1 add: 0~4 // 再依次打印出thread remove count 5~1 } - (void)synchronizedAdd { for (NSInteger i = 0; i < 5; i++) { @synchronized (self) { NSLog(@"thread 1 add: %ld", (long)i); [self.testArray addObject:@(i)]; sleep(1); } } } - (void)synchronizedRemove { while (1) { @synchronized (self) { if (self.testArray.count > 0) { NSLog(@"thread remove count %lu", self.testArray.count); [self.testArray removeObjectAtIndex:(self.testArray.count-1)]; } sleep(1); } } }