美文网首页
OC之并发编程

OC之并发编程

作者: 苏沫离 | 来源:发表于2018-08-02 09:06 被阅读0次

    1、什么是并发编程?

    并发编程是指在一台处理器上“同时”处理多个任务。它的目标是充分的利用处理器的每一个核,以达到更高的处理性能。

    2、并发处理 与 顺序处理

    • 顺序处理:按照先后顺序处理逻辑控制流(即逐个处理)
    • 并发处理:同时处理多个逻辑控制流
    以顺序方式处理的控制流 以并发方式处理的控制流

    并发处理相对于顺序处理的优势:

    • 增加应用程序的吞吐量:应用程序的吞吐量是指在一段时间内应用程序能够完成的任务数;并发程序同时处理多个任务,会比顺序处理完成更多任务。
    • 提高系统的利用率:以并发方式执行多个任务,可以更集中、更高效的利用系统资源。
    • 提高应用程序的整体响应性:如果某个任务正在等待,使用并发模式,可以让其它任务继续进行,减少程序的整体闲置时间,提高程序的响应性。
    • 更好的与问题领域契合:在处理某些问题时,可以将这些任务创建为同时处理的任务集合,以并发方式处理。

    并发处理意味着同时执行多个任务。但实际上,利用并发机制的程序是否真的并发执行多个任务,取决于运行程序的计算机系统。这里引出了两条概念:并发计算(concurrent computing)与 并行计算(Parallel Computing)。

    3、并发计算与 并行计算

    • 并行:当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行。
    • 并发:当有多个线程在操作时,如果系统只有一个CPU,则它不能同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状态。

    从广义上来说:并发计算是软件层面的,与设计相关;并行计算是硬件层面的,与多核CPU相关。

    • 并行计算 :指计算机同时执行多个操作或任务。执行并行计算的能力直接取决于计算机硬件,如多核 CPU 同时执行多条指令
    • 并发计算:指一个程序被设计同时执行多个操作或任务。如果程序使用并发编程设计并实现的,那么它就会根据计算机硬件的能力,以并发的方式运行。

    要发挥并发处理的优势,必须以并发编程设计并实现程序,并在能够支持并行处理的硬件运行它。

    4、实现并发处理

    在了解了并发处理的优势后,我们要学会如何利用它。在计算机系统中,实现并发处理的方式有很多:

    • 分布式计算:多个任务被分给多台网络相连的计算机执行,这些计算机通过消息传递来实现通信;
    • 并行编程:由多核CPU和可编程GPU进行大量的并行计算;
    • 多进程:多个任务被分给一台计算机的多个进程,每个进程都拥有由操作系统管理的独立资源和地址空间;
    • 多线程:多个任务与多个线程对应,这些线程被配置为并发方式执行。这些线程是在单个进程的环境中执行的,它们共享地址空间和内存。

    5、 CPU 与 GPU

    上文提到 CPU 与 GPU ,那么什么是 CPU ?什么是 GPU ?CPU 与 GPU 的区别是什么?

    • CPU 是中央处理器,所做的工作都在软件层面;
    • GPU 是图形处理器,所做的工作都在硬件层面;

    CPU 擅长处理具有复杂计算步骤和复杂数据依赖的计算任务,如分布式计算,数据压缩,人工智能,物理模拟,以及其他很多很多计算任务等。

    区别: CUP 和 GPU 之所以大不相同,是由于其设计目的的不同,它们分别针对了两种不同的应用场景:

    • CPU 需要很强的通用性来处理各种不同的类型数据,同时又要逻辑判断又会引入大量的分支跳转和中断处理。这些都使得CPU的内部结构异常复杂。
    • GPU面对的则是类型高度统一的、相互无依赖的大规模数据和不需要被打断纯净的计算环境。

    6、进程 与 线程

    进程与进程都是操作系统的基本概念:

    • 进程:正在运行的、拥有独立地址空间和系统资源的计算机程序;是系统进行资源分配和调度的基本单位,是操作系统结构的基础,主要管理资源。
    • 线程:在某个进程环境中执行的逻辑控制流,它是可以独立执行的指令序列。线程也被称为轻量级进程,它是进程的基本执行单元,多个线程共享一个地址空间;
    • 多线程:在同一时刻,一个 CPU 只能处理 1 条线程,但 CPU 可以在多条线程之间快速的切换,只要切换的足够快,就造成了多线程一同执行的假象。多线程通过提高资源使用率来提高系统总体的效率。但是也不要开启太多线程,当线程过多时,会消耗大量 CPU 资源,且每开一条线程本身也是有开销的(在iOS中,主线程站 1 MB 的内存空间,子线程占用 512 KB,可以使用 stackSize 设置线程占用空间,最小是 16 KB,线程创建时间是 90 ms),可能导致卡顿问题
    • 协程:一串比函数粒度还要小的可手动控制的过程;并不是 线程,不会参与 CPU 时间调度,并没有均衡分配到时间。

    进程可能包含多个线程,这些线程可以按顺序执行、以并发方式执行,或者混合使用这两种方式执行。
    在 Objective-C 语言中,使用 NSTaskNSThread 管理进程与线程。

    6.1、NSTask 创建并管理进程

    使用NSTask 可以在 Objective-C 运行时系统中创建并管理进程,NSTask 作为独立进程进行操作,不与其它进程共享内存,包括创建它的进程。一个NSTask 对象只能运行一次,而且其环境需要在它运行以前配置好。

    NSTask *task = [[NSTask alloc] init];//使用 init 初始化一个 task
    [task setLaunchPath:@"/Users/longlong/Library/Developer/CoreSimulator/Devices/DD8DCEB3-16B7-412A-9F9E-0315A427A638/data/Containers/Bundle/Application/2CDEA0BD-BCB1-40A9-957A-EDE732AE1072/Demo.app/Demo"];//设置启动的程序目录
    [task setArguments:@[@"/Users/longlong/Library/Developer/CoreSimulator/Devices/DD8DCEB3-16B7-412A-9F9E-0315A427A638/data/Containers/Bundle/Application/2CDEA0BD-BCB1-40A9-957A-EDE732AE1072/Demo.app/Demo"]];//设置启动需要的参数
    [task launch];//启动该进程
    NSLog(@"isRunning == %d",[task isRunning]);//使用 isRunning 查询 task 的状态
    
    6.2、NSThread 创建并管理线程

    线程是一种操作系统机制,用以并行方式执行多个指令序列。同一个进程中的线程可以共享计算机的内存和其他资源。
    使用NSThread 可以创建和控制线程:

    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(startChildThread) object:nil];
    thread.stackSize = 16;//设置线程占用空间,最小是 16 KB,
    thread.name = @"demo.thread2";//设置线程名字
    thread.threadPriority = 1.0;//设置线程优先级
    [thread start];//开启线程
    

    7、并发处理带来的困难:

    虽然并发处理有如此多优点,但要正确实现它并不容易,它主要有以下几个难点:

    • 共享信息:在控制不同线程中的相关操作时需要实现同步,而在线程之间进行通信就必须实现信息共享;
    • 因为要同时执行多个线程,所以整个程序的执行顺序是不确定的,使用不同的执行顺序执行同一程序得到不同的结果,这会导致并发程序中的bug 难以检测和修复。

    我们可以使用共享内存消息传递 来处理上述问题。共享内存编程模式会实现共享状态,多个线程都可以访问某些数据。

    8、共享内存 与 消息传递

    8.1、共享内存

    共享内存模式需要一种机制来协调多个线程共用的数据,通常使用同步机制来实现这一目标,如 锁或者判定条件

    • 锁:是一种控制多线程间数据同步访问和资源共享的一种机制。
    • 条件:它使线程一直处于等待状态直到指定条件出现,条件变量通常用锁来实现。

    锁实施是一种互斥策略,避免受保护的数据和资源被多个线程同时访问。使用锁协调对共享数据的访问时,很有可能引发死锁、活锁、资源匮乏等问题,这些问题导致程序中断:

    • 死锁:指两个或多个线程互相阻塞的情况,每个线程都等待其他线程释放锁,导致所有线程都处于等待状态。典型的例子就是循环等待;
    • 活锁:指一个线程因为要回应其它线程,而导致自身无法执行的情况。活锁的线程没有被阻塞,它将所有的计算时间用于回应其它线程,以恢复正常的操作。
    • 资源匮乏:指线程无法正常访问共享资源的情况,通常是共享资源被其它线程占用。当一个或多个线程占用共享资源的时间过长,就会引发这种问题。活锁也是资源匮乏的一种形式。
    8.2、消息传递

    线程能够通过交换消息进行同步和通信,消息传递避免了互斥问题,并与多核、多处理器系统契合。使用消息传递既可以执行同步通信,也可以执行异步通信。在进行同步消息传递时,发送者和接收者会直接连接;消息传递操作完成后,发送者和接收者会断开连接。异步消息传递通过队列传输消息,如图所示:

    使用队列传递消息

    消息不是在队列之间直接传递,而是通过 消息队列 进行交换。因此,发送者和接收者并不会配对,发送者将消息发给队列后也无需断开连接。使用异步消息传递可以实现并发编程。

    框架Foundation 中的NSObject 含有许多方法,这些方法使用消息传递模式,通过线程调用对象的方法。该线程可以是主线程,也可以是分线程

    @interface NSObject (NSThreadPerformAdditions)
    //waitUntilDone: 参数设置了同步/异步操作
    - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
    - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
        // equivalent to the first method with kCFRunLoopCommonModes
    
    - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
    - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
        // equivalent to the first method with kCFRunLoopCommonModes
    - (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
    
    @end
    

    上述方法,参数 waitUntilDone: 设置了同步YES/异步NO操作。

    9、运行循环RunLoop

    RunLoop 是一种基于线程的机制,用于调度任务和协调收到的输入事件。如果程序的线程需要回应入栈事件,就必须将之附到运行循环中,以在新事件出现时唤醒该线程。

    10、在 Objective-C 中实现并发编程

    在Objective-C中实现并发编程有以下几种方式:

    • 线程 NSThread
    • 操作队列 NSOperationQueue:基于Objective-C的消息传递机制,通过亦不涉及方法实现并发编程;
    • 分派队列 GCD:基于 C 语言的一系列语言特性和运行时服务,用于通过异步和并发执行任务;

    参考文章:
    并发编程:API 及挑战
    iOS 渲染原理解析
    iOS离屏渲染的深入研究
    协程

    相关文章

      网友评论

          本文标题:OC之并发编程

          本文链接:https://www.haomeiwen.com/subject/zyngyftx.html