iOS多线程学习笔记

2018-11-19  本文已影响0人  羊非鱼丶

iOS当中关于并发操作的实现方式有三种:

NSThread

NSThread 是 Objective-C 对pthread的封装。它可以直接操作线程对象,最大限度的掌控线程的生命周期。它足够轻量级,但是需要手动管理线程的生命周期,包括创建、启动、暂停、取消等。
另外使用NSThread操作多线程的数据同步时,为了保证数据安全需要手动加锁,这不仅会占用更多的系统资源,在较为复杂的环境中对开发人员也是一项不小的挑战。

NSThread的使用场景比较少,但是它提供了几个我们会经常用到的方法,比如:

NSThread提供了多个创建线程的方式,比如:- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument

值得注意的是,NSThread有一种隐式创建线程的方式performSelectorInBackground:withObject:,它会隐式地创建一个线程,如:

 [self performSelectorInBackground:@selector(testThread) withObject:nil];

这条线程会调用testThread方法

- (void)testThread {
    NSLog(@"%@",[NSThread currentThread]);
}

我们在testThread打印当前执行的线程可以看到控制台输出如下信息:
<NSThread: 0x6000000ab900>{number = 4, name = (null)
主线程 number = 1, 此处线程 number = 4,可见不是主线程,而是另外创建了新的线程。

GCD

GCD (Grand Central Dispatch)是基于队列的并发编程API,只支持FIFO(先入先出)队列;GCD队列内部使用的就是线程。它可以充分的利用当下CPU的多核特性,并且自动对线程进行管理,开发者只需要定义每个队列需要执行的工作即可。GCD可以高效处理大量并发数据,执行操作的block都能以极快的速度运行。

GCD是基于iOS和macOS的XNU内核实现的,它底层用来处理事务的Dispatch Source对资源的占用很少。GCD是纯C语言的API,不属于Cocoa框架,API都在libdispatch库中。GCD的底层除了用来处理事件(比如线程间通信)的Dispatch Source 外,还有一个用来管理block操作的Dispatch QueueDispatch Queue通过结构体和链表,被实现为FIFO队列。FIFO队列管理则是通过dispatch_async函数所追加的block。 相对于Dispatch Source来说,我们平时的使用中对Dispatch Queue的操作更多。

GCD中的常用方法

GCD 中全局(global)队列中的优先级

NSOperation

NSOperation 是 iOS 中的又一种实现并发编程的方式。它将单个任务算做一个NSOperation,然后放在 NSOperationQueue 队列中进行管理和运行。它是GCD的封装,执行效率上要低于GCD,不过它也有很多值得重视的优势:

NSOperation的使用

NSOperation指代一系列工作或者任务。它本身是一个抽象类,一般使用它的子类NSBlockOperation和NSOperationQueue来完成自定义的工作(当然也可以通过继承它来自定义operation)。每个单独的 NSOperation 有四种状态:准备就绪(Ready)、执行中(Executing)、暂停(Cancelled)、完成(Finished)。

NSBlockOperation

系统定义的一个 Operation 的子类。它本身在默认权限的全局队列上运行,负责并执行多个任务。其中任务之间互不依赖,同时 BlockOperation 也可以像 dispatch_group 一样同步管理多个任务。

NSOperationQueue

负责安排和运行多个 Operation 的队列。但是它并不局限于先进先出的队列操作。它提供了多个接口可以实现暂停、继续、终止、优先顺序、依赖等复杂操作。同时,还可以通过设置 maxConcurrentOperationCount 属性来区分其是串行的还是并行的。

线程安全问题

并发编程当中,由于涉及到多线程的读写操作,当具有多个线程的时候很容易就会出现问题

竞态条件

竞态条件是指两个或两个以上线程对共享数据进行读写操作时,最终的数据结果不确定的情况,就好像同一时间多个程序员修改同一个项目当中的同一个文件,那么很容易就会引发代码冲突一样。

对于多线程操作的数据读写问题的处理一般有三种方式:

  1. 使用串行队列,无论读操作还是写操作,同时只能有一个进行,这样就保证了队列的安全。但是缺点也很明显:速度慢。尤其是在有大量读/写操作的时候,每次只能进行单个读操作或写操作,效率很低。
  2. 使用并行队列配合异步操作完成。异步操作由于会直接返回结果,所以必须配合逃逸闭包来保证后续操作的合法性。
  3. 使用并行队列,在进行读操作时,用 sync 直接返回结果;在进行写操作时,用 barrier flag 来保证此时并行队列只进行当前的写操作(类似于将并行队列暂时转换为串行队列),而无视其他操作。

优先倒置

指低优先级的任务由于各种原因先于高优先级的任务执行。

死锁问题

指两个或两个以上线程,它们之间互相等待彼此停止执行,以获得某个资源,但是没有一方会提前退出的情况。

iOS开发当中的死锁案例:

 NSOperation *operationA = [[NSOperation alloc] init];
 NSOperation *operationB = [[NSOperation alloc] init];
 
 [operationA addDependency:operationB];
 [operationB addDependency:operationA];
dispatch_async(queue, ^{
    dispatch_sync(queue, ^{
            
    });
});

参考资源

《iOS面试之道》
《Objective-C 高级编程iOS 与OS X多线程和内存管理》

上一篇下一篇

猜你喜欢

热点阅读