iOS 多线程

Posted xiaoxiaobukuang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS 多线程相关的知识,希望对你有一定的参考价值。

多线程
GCD
NSOperation
NSThread
多线程与锁

一、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 多线程的主要内容,如果未能解决你的问题,请参考以下文章

多个用户访问同一段代码

线程学习知识点总结

多个请求是多线程吗

python小白学习记录 多线程爬取ts片段

多线程编程

多线程编程