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

第80题JAVA高级技术-多线程14(闭锁和栅栏)

Java多线程 5.栅栏

多线程(八同步计数器-CyclicBarrier)

转 多线程 闭锁(Latch) 栅栏(CyclicBarrier)

高并发多线程安全之信号量线程组守护线程线程栅栏等的分析

多线程之倒计时器CountDownLatch和循环栅栏CyclicBarrier