iOS中多线程
首先看一道面试题
iOS中多线程有哪些实现方案?
技术方案 | 简介 | 语言 | 线程生命周期 | 使用频率 |
---|---|---|---|---|
pthread | 1. 一套通用的多线程API2. 跨平台/可移植3. 使用难度大 | C | 程序员管理 | 几乎不用 |
NSThread | 1.面向对象 2.简单易用直接操作线程对象 | OC | 程序员管理 | 偶尔使用 |
GCD | 1.旨在替代NSThread等线程技术 2.充分利用设备的多核 | C | 自动管理 | 经常使用 |
NSOperation | 1.基于GCD 2.比GCD多了一些更实用的函数 | OC | 自动管理 | 经常使用 |
iOS中,多线程一般有三种方案GCD
、NSOperation
和NSThread
。
一、GCD
GCD相关问题一般分为三个方面:首先,同步/异步
和串行/并发
问题;其次,dispatch_barrier_async
异步栅栏调用,解决多读单写问题;最后,dispatch_group
使用和理解。
GCD中有2种用来执行任务的方式:同步和异步;同时还有两种类型的队列:并发和串行队列。并发队列让多个任务并发执行,自动开启多个线程同时执行任务。并发功能只在异步函数下才生效。
并发队列 | 手动创建的串行队列 | 主队列 | |
---|---|---|---|
同步 | 没有开辟新线程 串行执行 | 没有开辟新线程 串行执行 | 没有开辟新线程 串行执行 |
异步 | 有开辟新线程 并发执行 | 有开辟新线程 串行执行 | 没有开辟新线程 串行执行 |
注意 :使用同步函数往当前串行队列中添加任务,会卡主当前的串行队列,产生死锁。
1.1 同步/异步
和串行/并发
存在四种组合方案:
// 同步 + 串行
dispatch_sync(serial_queue, ^{
//任务
});
// 异步 + 串行
dispatch_async(serial_queue, ^{
//任务
});
// 同步 + 并发
dispatch_sync(concurrent_queue, ^{
//任务
});
// 异步 + 并发
dispatch_async(concurrent_queue, ^{
//任务
});
1.1.1 同步 + 串行
- (void)viewDidLoad {
dispatch_sync(dispatch_get_main_queue(), ^{
[self doSomething];
});
}
上面这段代码,存在什么问题?
产生死锁。队列引起循环等待。因为,
viewDidLoad()
进入主队列,执行过程中会将block
添加到主队列中。viewDidLoad()
需要等待block
执行完成后才能结束,由于主队列先进先出的block
需要viewDidLoad()
执行完毕才能执行。因此导致队列循环等待的问题。上图中,首先向主队列中提交了一个 viewDidLoad 的任务,后续又提交了一个 Block 任务 。 现在分派 viewDidLoad
到主线程中去执行,在它执行过程中需要调用 Block ,等 Block 同步调用完成之后,这个 viewDidLoad 才能继续向下走,所以viewDidLoad 的调用结束依赖于 Block 方法执行完。 而队列是遵循先进先出(FIFO)原则的,Block 要想执行,就必须等待 viewDidLoad 调用完成。 由此就产生了队列的循环等待,造成死锁.
上面的问题理解,在来看一个问题。
- (void)viewDidLoad {
dispatch_sync(serial_queue, ^{
[self doSomething];
});
}
上面的代码,有什么问题?
没有问题。这里是将
block
添加到单独的串行队列。viewDidLoad()
在主队列中在主线程中执行,在其执行过程中调用block
添加到串行队列中,在主线程中执行。viewDidLoad 在主队列中,提交到主线程处理,在 viewDidLoad方法运行到某一时刻的时候,会提交一个任务到串行队列上。串行队列同步提交一个 Block任务,因为是同步的(同步提交就是在当前线程执行),所以串行队列中的任务也是提交到主线程中执行,当串行队列这个任务在主线程处理完成之后,再继续处理viewDidLoad 后续的代码逻辑.。同步方式提交任务,无论在串行队列还是并发队列都会在当前线程中执行。
1.1.2 同步 + 并发
int main(int argc, const char * argv[]) {
@autoreleasepool {
dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"1");
dispatch_sync(global_queue, ^{
NSLog(@"2");
dispatch_sync(global_queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
return 0;
}
上面这段代码的输出结果?
输出结果:12345。同步方式提交任务,无论在串行队列还是并发队列都会在当前线程中执行。因为是同步,所以都是在主线程执行。globaQueue 是并发队列,所以不会造成死锁。如果将 俩个globaQueue 都换成串行队列,就会造成死锁.
1.1.3 异步 + 串行
-(void)viewDidLoad
{
dispatch_async(dispatch_get_main_queue(), ^{
[self doSomething];
});
}
1.1.4 异步 + 并发
面试题
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(global_queue, ^{
NSLog(@"1");
[self performSelector:@selector(printLog) withObject:nil afterDelay:0];
NSLog(@"3");
});
}
- (void)printLog {
NSLog(@"2");
}
上述代码执行的结果?
结果:13。提交异步任务到并发队列,任务调用了
performSelector:withObject:afterDelay:
。由于GCD提交的任务是在某个子线程中执行,子线程没有RunLoop。由于performSelector:withObject:afterDelay:
需要RunLoop才可生效,所以方法不执行。这个问题,考察performSelector:withObject:afterDelay:
内部实现。
分析首先这是一个异步方式分配到全局并发队列当中的,这个 Block 会在 GCD 底层的线程池当中的某一个线程中执行。 GCD 底层所分派的这些线程默认是不开启 Runloop 的,而 performSelector 方法是需要创建相应的任务提交到 Runloop 上,所以在GCD 底层线程没有 Runloop 的情况下,这个方法就会失效 .也就是说 performSelector要想能够有效执行必须是它方法调用所属的当前线程有 Runloop 的。
任务和队列示例代码:任务和队列Demo包含面试题讲解
二、多读单写解决方案
pthread_rwlock: 读写锁
dispatch_barrier_async() 异步栅栏调用
怎么利用GCD实现多读单写?或者如何实现多读单写?
2.1 什么是多读单写?
读者和读者,并发。 读者和写者,互斥。 写者与写者,互斥。
2.2 解决方法
dispatch_async(global_queue, ^{
NSLog(@"读取1");
});
dispatch_async(global_queue, ^{
NSLog(@"读取2");
});
dispatch_barrier_async(global_queue, ^{
NSLog(@"写入1");
});
dispatch_async(global_queue, ^{
NSLog(@"读取3");
});
dispatch_async(global_queue, ^{
NSLog(@"读取4");
});
dispatch_barrier_async
函数会等待追加到并发队列上的并行执行的处理全部结束之后,在将指定的处理追加到该并发队列中。然后等dispatch_barrier_async
函数追加的处理执行完毕后,并发队列才恢复为一般的动作,追加到并发队列的处理又开始并行执行。
三、dispatch_group_async()
面试题:
如何用GCD 实现:A、B、C三个任务并发,完成后执行任务D?
实现追加到并发队列中的多个任务全部结束后再执行想执行的任务。无论向什么样的队列中追加处理,使用DispatchGroup都可监视这些处理执行的结束。一旦检测到所有处理执行结束,该Dispatch Group与队列相同。
dispatch_group_async() 同dispatch_async()函数相同,都追加Block到指定的DispatchQueue中。当组中所有任务都执行完成后,dispatch_group_notify()执行Block中的内容。
示例代码:
// dispatch_group_notify
- (void)test {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.lqq.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务1 - %@", [NSThread currentThread]);
}
});
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务2 - %@", [NSThread currentThread]);
}
});
//--------------示例1------------------- // 写法一, 等上面的任务执行完成后,才会在主队列中执行任务3
// dispatch_group_notify(group, queue, ^{
// dispatch_async(dispatch_get_main_queue(), ^{
// for (int i = 0; i < 5; i++) {
// NSLog(@"任务3 - %@", [NSThread currentThread]);
// }
// });
// });
//写法二:直接在主队列中执行
// dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// for (int i = 0; i < 5; i++) {
// NSLog(@"任务3 - %@", [NSThread currentThread]);
// }
// });
//--------------示例2------------------- // 如果有多个notify会怎么执行呢?
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务3 - %@", [NSThread currentThread]);
}
});
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务4 - %@", [NSThread currentThread]);
}
});
// 任务3和任务4是交替执行的
}
另外,也可以使用dispatch_group_wait
,如下:
// 监控任务是否完成,当完成时会返回0,不完成一直等待。
long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
if (result == 0) {
NSLog(@"全部任务执行完成");
}
线程组示例代码:线程组API使用Demo
四、NSOperation
需要和NSOperationQueue配合使用来实现多线程。优势和特点:添加任务依赖、任务执行状态控制、控制最大并发量。
任务状态的控制
isReady
isExecuting
isFinished
isCanceled
如果重写main方法,底层控制变更任务执行完成状态,以及任务退出。
如果重写start方法,自行控制任务状态。
- (void) start
{
NSAutoreleasePool *pool = [NSAutoreleasePool new];
double prio = [NSThread threadPriority];
AUTORELEASE(RETAIN(self)); // Make sure we exist while running.
[internal->lock lock];
NS_DURING
{
if (YES == [self isExecuting])
{
[NSException raise: NSInvalidArgumentException
format: @"[%@-%@] called on executing operation",
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
}
if (YES == [self isFinished])
{
[NSException raise: NSInvalidArgumentException
format: @"[%@-%@] called on finished operation",
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
}
if (NO == [self isReady])
{
[NSException raise: NSInvalidArgumentException
format: @"[%@-%@] called on operation which is not ready",
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
}
if (NO == internal->executing)
{
[self willChangeValueForKey: @"isExecuting"];
internal->executing = YES;
[self didChangeValueForKey: @"isExecuting"];
}
}
NS_HANDLER
{
[internal->lock unlock];
[localException raise];
}
NS_ENDHANDLER
[internal->lock unlock];
NS_DURING
{
if (NO == [self isCancelled])
{
[NSThread setThreadPriority: internal->threadPriority];
[self main];
}
}
NS_HANDLER
{
[NSThread setThreadPriority: prio];
[localException raise];
}
NS_ENDHANDLER;
[self _finish];
[pool release];
}
面试题
系统是怎样移除一个isFinished=YES的NSOperation的?答案:KVO
五、NSThread
启动流程
start() -> 创建pthread -> main() ->[target performSelector:selector] -> exit()
- (void) start
{
pthread_attr_t attr;
if (_active == YES)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-%@] called on active thread",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd)];
}
if (_cancelled == YES)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-%@] called on cancelled thread",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd)];
}
if (_finished == YES)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-%@] called on finished thread",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd)];
}
/* Make sure the notification is posted BEFORE the new thread starts.
*/
gnustep_base_thread_callback();
/* The thread must persist until it finishes executing.
*/
RETAIN(self);
/* Mark the thread as active while it's running.
*/
_active = YES;
errno = 0;
pthread_attr_init(&attr);
/* Create this thread detached, because we never use the return state from
* threads.
*/
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
/* Set the stack size when the thread is created. Unlike the old setrlimit
* code, this actually works.
*/
if (_stackSize > 0)
{
pthread_attr_setstacksize(&attr, _stackSize);
}
if (pthread_create(&pthreadID, &attr, nsthreadLauncher, self))
{
DESTROY(self);
[NSException raise: NSInternalInconsistencyException
format: @"Unable to detach thread (last error %@)",
[NSError _last]];
}
}
/**
* Trampoline function called to launch the thread
*/
static void *
nsthreadLauncher(void *thread)
{
NSThread *t = (NSThread*)thread;
setThreadForCurrentThread(t);
/*
* Let observers know a new thread is starting.
*/
if (nc == nil)
{
nc = RETAIN([NSNotificationCenter defaultCenter]);
}
[nc postNotificationName: NSThreadDidStartNotification
object: t
userInfo: nil];
[t _setName: [t name]];
[t main];
[NSThread exit];
// Not reached
return NULL;
}
- (void) main
{
if (_active == NO)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-%@] called on inactive thread",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd)];
}
[_target performSelector: _selector withObject: _arg];
}
如何实现常驻进程? 通过使用NSThread和RunLoop实现。
在iOS多线程中,经常会出现资源竞争和死锁的问题。本节将学习iOS中不同的锁。
线程同步方案
常见的两个问题:多线程买票和存取钱问题。
示例:存取钱问题
// 示例:存取钱问题
- (void)moneyTest {
self.moneyCount = 100;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saveMoney];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self takeMoney];
}
});
}
- (void)saveMoney {
int oldCount = self.moneyCount;
sleep(0.2);
oldCount += 50;
self.moneyCount = oldCount;
NSLog(@"存50,还剩%d钱", self.moneyCount);
}
- (void)takeMoney {
int oldCount = self.moneyCount;
sleep(0.2);
oldCount -= 20;
self.moneyCount = oldCount;
NSLog(@"取20,还剩%d钱", self.moneyCount);
}
示例:卖票问题
// 示例:买票
- (void)sellTest {
self.count = 15;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self printTest2];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self printTest2];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self printTest2];
}
});
}
- (void)printTest2 {
NSInteger oldCount = self.count;
sleep(0.2);
oldCount --;
self.count = oldCount;
NSLog(@"还剩%ld张票 - %@", (long)oldCount, [NSThread currentThread]);
}
解决上面这种资源共享问题,就需要使用线程同步技术。线程同步技术的核心是:锁。下面学习iOS中不同锁的使用,比较不同锁之间的优缺点。
示例代码:演示购票和存取钱问题:Demo
iOS当中有哪些锁?
@synchronized 常用于单例
atomic 原子性
OSSpinLock 自旋锁
NSRecursiveLock 递归锁
NSLock
dispatch_semaphore_t 信号量
NSCondition 条件
NSConditionLock 条件锁
简介:
@synchronized
使用场景:一般在创建单例对象时使用,保证对象在多线程中是唯一的。atomic
属性关键字原子性,保证赋值操作是线程安全的,读取操作不能保证线程安全。OSSpinLock
自旋锁。特点:循环等待访问,不释放当前资源。常用于轻量级数据访问,简单的int值+1/-1操作。 一般用于引用计数操作NSLock
某个线程A调用lock方法。这样,NSLock将被上锁。可以执行“关键部分”,完成后,调用unlock方法。如果,在线程A 调用unlock方法之前,另一个线程B调用了同一锁对象的lock方法。那么,线程B只有等待。直到线程A调用了unlock。
[lock lock]; //加锁
// 关键部分代码{}
[lock unlock]; // 解锁
NSLock使用的过程要注意因重复上锁产生的死锁
NSRecursiveLock
递归锁,特点:递归锁在被同一线程重复获取时不会产生死锁。dispatch_semaphore_t
信号量
// 创建信号量结构体对象,含有一个int成员
dispatch_semaphore_create(1);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);// 先对value减一,如果小于零表示没有资源可以访问。通过主动行为进行阻塞。
dispatch_semaphore_signal(semaphore);// value加1,小于等零表示有队列在排队,通过被动行为进行唤醒。
OSSpinLock
自旋锁,等待锁的线程会处于忙等状态,一直占用着CPU资源。
常用API:
导入头文件
#import
// 初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
// 尝试加锁
OSSpinLockTry(&lock);
// 加锁
OSSpinLockLock(&lock);
// 解锁
OSSpinLockUnlock(&lock);
使用OSSpinLock
解决卖票问题
// 自旋锁:#import
// 定义一个全局的自旋锁对象 lock 。
- (void)printTest2 {
// 加锁
OSSpinLockLock(&_lock);
NSInteger oldCount = self.count;
sleep(0.2);
oldCount --;
self.count = oldCount;
NSLog(@"还剩%ld张票 - %@", (long)oldCount, [NSThread currentThread]);
// 解锁
OSSpinLockUnlock(&_lock);
}
使用OSSpinLock
解决存取钱问题
- (void)saveMoney {
OSSpinLockLock(&_moneyLock);
int oldCount = self.moneyCount;
sleep(0.2);
oldCount += 50;
self.moneyCount = oldCount;
NSLog(@"存50,还剩%d钱", self.moneyCount);
OSSpinLockUnlock(&_moneyLock);
}
- (void)takeMoney {
OSSpinLockLock(&_moneyLock);
int oldCount = self.moneyCount;
sleep(0.2);
oldCount -= 20;
self.moneyCount = oldCount;
NSLog(@"取20,还剩%d钱", self.moneyCount);
OSSpinLockUnlock(&_moneyLock);
}
注意:卖票和取钱不要共用一把锁。这里创建了两把锁
sellLock
和moneyLock
。
自旋锁现在不再安全,因为可能出现优先级反转问题。如果等待锁的线程优先级较高,他会一直占用CPU资源,优先级低的线程就无法获取CPU资源完成任务并释放锁。可以查看这篇文章不再安全的OSSpinLock。
本节示例代码:线程同步解决方案Demo
os_unfair_lock
自旋锁已经不再安全,存在优先级反转问题。苹果在iOS10开始使用os_unfair_lock
取代了OSSpinLock
。从底层调用来看,自旋锁和os_unfair_lock
的区别,前者等待线程处于忙等,而后者等待线程处于休眠状态。
常用API:
导入头文件
#import
// 初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
// 尝试加锁
os_unfair_lock_trylock(&_lock);
// 加锁
os_unfair_lock_lock(&_lock);
// 解锁
os_unfair_lock_unlock(&_lock);
pthread_mutex
互斥锁,等待锁的线程处于休眠状态。
常用API:
// 头文件 #import
- (void)__initLock:(pthread_mutex_t *)lock {
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
// 设置为普通锁,PTHREAD_MUTEX_RECURSIVE表示递归锁
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
// 初始化锁
pthread_mutex_init(lock, &attr);
// 销毁属性
pthread_mutexattr_destroy(&attr);
}
// 加锁
pthread_mutex_lock(&lock);
// 解锁
pthread_mutex_unlock(&lock);
// 初始化条件
pthread_cond_init(&cond, NULL)
// 等待条件(进入休眠,放开锁;被唤醒后,会再次加锁)
pthread_cond_wait(&cond, &lock);
// 激活一个等待该条件的线程
pthread_cond_signal(&cond);
// 激活所有等待该条件的线程
pthread_cond_broadcast(&cond);
// 销毁资源
pthread_mutex_destory(&lock);
pthread_cond_destory(&cond);
其中PTHREAD_MUTEX_DEFAULT
设置的是锁的类型,还有另一种类型PTHREAD_MUTEX_RECURSIVE
表示递归锁。递归锁允许同一个线程对一把锁进行重复加锁。
NSLock&NSRecursiveLock&NSCondition
NSLock
是对mutex
普通锁的封装。
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
@interface NSLock : NSObject {
- (BOOL)tryLock; // 尝试加锁
- (BOOL)lockBeforeDate:(NSDate *)limit; //在时间之前获取锁并返回,YES表示成功。
}
@end
NSRecursiveLock
是对mutex
递归锁的封装,API同NSLock
相似。
NSCondition
是对mutex
条件的封装。
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
@interface NSCondition : NSObject {
- (void)wait; // 等待
- (BOOL)waitUntilDate:(NSDate *)limit; // 等待某一个时间段
- (void)signal; // 唤醒
- (void)broadcast; // 唤醒所有睡眠线程
}
以上可以查看pthread_mutex
使用。
atomic
atomic
用于保证属性setter
和getter
的原子性操作,相当于对setter
和getter
内部加了同步锁。它并不能保证使用属性的使用过程是线程安全的。
NSConditionLock
NSConditionLock
是对NSCondition
的进一步封装。可以设置具体的条件值。
// 遵循NSLocking协议。
@interface NSConditionLock : NSObject {
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER; // 初始化,传入一个条件值
@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition; // 条件值符合加锁
- (BOOL)tryLock; //尝试加锁
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
@end
示例代码:
// 删除
- (void)__one {
// 当锁内部条件值为1时,加锁。
// [self.condition lockWhenCondition:1];
[self.condition lock]; // 直接使用lock也可以
sleep(1);
NSLog(@"%s ①", __func__);
[self.condition unlockWithCondition:2]; // 解锁,并且条件设置为2
}
// 添加
- (void)__two {
[self.condition lockWhenCondition:2]; //条件值为2时,加锁。
sleep(1);
NSLog(@"%s ②", __func__);
[self.condition unlockWithCondition:3];
}
// 添加
- (void)__three {
[self.condition lockWhenCondition:3]; //条件值为3时,加锁。
sleep(1);
NSLog(@"%s ③", __func__);
[self.condition unlock];
}
- (void)otherTest {
// ①
[[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];
// ②
[[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
// ③
[[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start];
// 通过设置条件值,可以决定线程的执行顺序。
}
输出结果:
-[LENSConditionLock __one] ①
-[LENSConditionLock __two] ②
-[LENSConditionLock __three] ③
信号量
常用API:
// 初始化
dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
// 如果信号量的值<=0,当前线程就会进入休眠等待,直到信号量的值>0
// 如果信号量的值>0,就减1,然后往下执行后面的代码
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
// 让信号量的值增加1,信号量值不等于零时,前面的等待的代码会执行。
dispatch_semaphore_signal(self.semaphore);
dispatch_semaphore
信号量的初始值,控制线程的最大并发访问数量。 信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步。
示例代码:
// 设置信号量初始值为5。
- (void)otherTest {
for (int i = 0; i < 20; i ++) {
[[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start];
}
}
- (void)test {
// 如果信号量的值<=0,当前线程就会进入休眠等待,直到信号量的值>0
// 如果信号量的值>0,就减1,然后往下执行后面的代码
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
sleep(2);
NSLog(@"test - %@", [NSThread currentThread]);
// 让信号量的值增加1
dispatch_semaphore_signal(self.semaphore);
}
@synchronized
@synchronized
是对mutex
递归锁的封装。 不推荐使用,性能比较差。
// 源码:objc4中的objc-sync.mm
@synchronized (obj) {
}
性能比较
不再安全的OSSpinLock中对比了不同锁的性能。 推荐使用dispatch_semaphore
和pthread_mutex
两个。因为OSSpinLock
性能最好但是不安全,os_unfair_lock
在iOS10才出现低版本不支持不推荐。
自旋锁、互斥锁的选择
自旋锁预计线程等待锁的时间很短,加锁经常被调用但竞争情况很少出现。常用于多核处理器。 互斥锁预计等待锁的时间较长,单核处理器。临界区有IO操作,例如文件读写。
示例代码:锁实例代码-Github
小结
怎样用GCD实现多读单写?
iOS提供几种多线程技术各自的特点?
NSOperation对象在Finished之后是怎样从队列中移除的?
你都用过哪些锁?结合实际谈谈你是怎样使用的?
参考
小码哥底层班视频 正确使用多线程同步锁@synchronized() 深入理解iOS开发中的锁 Object-C 多线程中锁的使用-NSLock
附录
GCD API介绍:
dispatch_queue_create(名称,类型)
类型为NULL时,为串行队列;为DISPATCH_QUEUE_CONCUREENT,为并行队列.。
需要手动release释放队列
dispatch_release(队列)
主队列,串行
dispatch_get_main_queue()
dispatch_get_global_queue(优先级,0)
优先级有四种情况:
高,默认,低,后台
为自己创建的队列设置优先级
dispatch_set_target_queue(自定义队列,其他已知优先级的队列)
dispatch_after(时间,队列,Block)
时间:dispatch_time_t
dispatch_group 监听所有任务结束。
dispatch_group_create() //创建一个队列组,需要手动release
dispatch_group_async(组,队列,block)
dispatch_group_notify(组,队列,block) // 所有任务都结束后调用
也可以使用dispatch_group_wait()
dispatch_barrier_async() 一般用于数据库操作和文件读写。
同步任务
dispatch_sync(队列,block)
异步任务
dispatch_async(队列,block)
指定执行次数
dispatch_apply(次数,队列,block)
挂起
dispatch_suspend(队列)
恢复
dispatch_resume(队列)
信号量
Dispatch Semaphore是持有计数的信号,该计数是多线程编程中的计数类型信号。
dispatch_semaphore_create(技术值)
dispatch_semaphore_wait(semaphore, 时间)
dispatch_semaphore_signal(semaphore)
dispatch_reliease(semaphore)
// 示例
// 创建一个对象,默认的计数值为0,等待。当计数值大于1或者等于1时,减去1而不等待。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// 发送计数值加一信号
dispatch_semaphore_signal(semaphore);
// 等待计数值变化,第二个参数是等待时间,这里是永久等待。
// 当计数值大于0时,返回0。等于0不会返回任何值。
long result = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (result == 0) {
NSLog(@"执行排他性操作");
}
dispatch_once函数是保证在应用程序执行中只执行一次的API。
dispatch I/O 分割读取,提高读取效率。