iOS 多线程
Posted xiaoxiaobukuang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS 多线程相关的知识,希望对你有一定的参考价值。
一、GCD
- 同步/异步 和 串行/并发
- dispatch_barrier_async
- dispatch_group
1、 同步/异步 和 串行/并发
- dispatch_sync(serial_queue, ^{//任务}); 同步分派到一个串行队列上面
- dispatch_async(serial_queue, ^{//任务}); 异步分派到一个串行队列上面
- dispatch_sync(concurrent_queue, ^{//任务}); 同步分派到一个并发队列上面
- dispatch_async(concurrent_queue, ^{//任务}); 异步分派到一个并发队列上面
2、同步串行 & 死锁
问题(1):
该代码容易造成死锁
现象
死锁原因:
问题(2):
讲解:
3、同步并发
- (void)viewDidLoad {
[super viewDidLoad];
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");
}
答案:12345
4、异步串行
5、异步并发
- (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
原因:通过异步方式分派到一个全局并发队列之后,我们本身这个block会在这个GCD底层所维护的线程池当中的某一个线程上面进行执行处理,关于GCD底层所分派的线程默认情况下是不开启对应的RunLoop
,而- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
这个方法内部是产生一个定时器,及时是延迟0秒。所以该定时器是不起作用的。如果想要该方法执行,一定是该线程的RunLoop是有效的。
6、dispatch_barrier_async
多读单写实现:
dispatch_barrier_async(concurrent_queue,^{//写操作});
#import "UserCenter.h"
@interface UserCenter()
{
// 定义一个并发队列
dispatch_queue_t concurrent_queue;
// 用户数据中心,可能多个线程需要数据访问
NSMutableDictionary *userCenterDic;
}
@end
@implementation UserCenter
- (instancetype)init {
self = [super init];
if (self) {
// 通过宏定义 DISPATCH_QUEUE_CONCURRENT 创建一个并发队列
concurrent_queue = dispatch_queue_create("read_write_qeue", DISPATCH_QUEUE_CONCURRENT);
// 创建数据容器
userCenterDic = [NSMutableDictionary dictionary];
}
return self;
}
- (id)objectForKey:(NSString *)key {
__block id obj;
// 同步读取指定数据
dispatch_sync(concurrent_queue, ^{
obj = [userCenterDic objectForKey:key];
});
return obj;
}
- (void)setObject:(id)obj forKey:(NSString *)key {
// 异步栅栏调用设置数据
dispatch_barrier_async(concurrent_queue, ^{
[userCenterDic setObject:obj forKey:key];
});
}
@end
7、dispatch_group_async()
使用GCD实现这个需求:A、B、C三个任务并发,完成后执行任务D
?
#import "GroupObject.h"
@interface GroupObject()
{
dispatch_queue_t concurrent_queue;
NSMutableArray <NSURL *> *arrayURLs;
}
@end
@implementation GroupObject
- (instancetype)init {
self = [super init];
if (self) {
// 创建并发队列
concurrent_queue = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
arrayURLs = [NSMutableArray array];
}
return self;
}
- (void)handle {
// 创建一个group
dispatch_group_t group = dispatch_group_create();
// for循环遍历各个元素执行操作
for (NSURL *url in arrayURLs) {
// 异步组分派到并发队列当中
dispatch_group_async(group, concurrent_queue, ^{
// 根据url去下载图片
NSLog(@"url is %@",url);
});
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 当添加到组中的所有任务执行完成之后调用该Block
NSLog(@"所有图片已全部下载完成");
});
}
@end
二、NSOperation
需要和NSOperationQueue配合使用来实现多线程方案;
- 添加任务依赖
任务执行状态控制
- 最大并发量
1、任务执行状态
任务执行状态
- isReady 当前任务是否处于就绪状态
- isExecuting 当前任务是否处于正在执行中状态
- isFinished 当前任务是否处于已执行完成
- isCancelled 当前任务是否处于已取消
状态控制
- 如果只重写
main
方法,底层控制变更任务执行完成状态,以及任务退出。 - 如果重写了
start
方法,自行控制任务状态
NSOperation.m源码解析:(gnustep-base-1.26.0)
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];
}
2、系统是怎么样移除一个isFinished=YES的NSOperation
答案:通过KVO
finish方法
- (void) _finish
{
/* retain while finishing so that we don't get deallocated when our
* queue removes and releases us.
*/
[self retain];
[internal->lock lock];
if (NO == internal->finished)
{
if (YES == internal->executing)
{
[self willChangeValueForKey: @"isExecuting"];
[self willChangeValueForKey: @"isFinished"];
internal->executing = NO;
internal->finished = YES;
[self didChangeValueForKey: @"isFinished"];
[self didChangeValueForKey: @"isExecuting"];
}
else
{
[self willChangeValueForKey: @"isFinished"];
internal->finished = YES;
[self didChangeValueForKey: @"isFinished"];
}
if (NULL != internal->completionBlock)
{
CALL_BLOCK_NO_ARGS(internal->completionBlock);
}
}
[internal->lock unlock];
[self release];
}
三、NSThread
启动流程
NSThread.m
源码分析(gnustep-base-1.26.0)
:
位置:
- (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]];
}
}
启动函数nsthreadLauncher
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;
}
main
函数
- (void) main
{
if (_active == NO)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-%@] called on inactive thread",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd)];
}
[_target performSelector: _selector withObject: _arg];
}
四、锁
- @synchronized
- atomic
- OSSpinLock
- NSRecursiveLock
- NSLock
- dispatch_semaphore_t
1、@synchronized
一般在创建单例对象的时候使用
2、atomic
- 修饰属性的关键字
- 对被修饰对象进行原子操作(不负责使用)
注意:
- 赋值时可以保证线程安全
- 使用时无法保证线程安全
3、OOSpinLock
循环等待
询问,不释放当前资源- 用于轻量级数据访问,简单的int值 +1/-1操作
4、NSLock
5、NSRecursiveLock(递归锁)
6、dispatch_semaphore_t
dispatch_semaphore_create(1);
dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(semaphore)
以上是关于iOS 多线程的主要内容,如果未能解决你的问题,请参考以下文章