[精通Objective-C]三种实现并发编程的方式

[精通Objective-C]三种实现并发编程的方式

参考书籍:《精通Objective-C》【美】 Keith Lee

目录

  • 精通Objective-C三种实现并发编程的方式

    • 目录
    • 线程
      • 隐式创建并启动线程
      • 显示创建并启动线程
    • 操作和操作队列
      • 用操作类实现并发
      • 用操作队列实现并发
    • 分派队列GCD
    • 三种方式的比较

线程

隐式创建并启动线程

使用NSObject类中的performSelectorInBackground: withObject:方法可以隐式地创建和启动用于执行对象中方法的新线程。该线程会作为后台次要进程立刻启动,而当前进程会立刻返回。下面是一个使用该方法的实例:

首先创建一个继承于NSObject类的,含有将由独立线程执行的方法的类:

#import <Foundation/Foundation.h>

@interface ConcurrentProcessor : NSObject

@property(readwrite) BOOL isFinished;
@property(readonly) NSInteger computeResult;

-(void)computeTask:(id)data;

@end
#import "ConcurrentProcessor.h"

@interface ConcurrentProcessor()
@property(readwrite)NSInteger computeResult;
@end

@implementation ConcurrentProcessor
{
    NSString *computeID;     // @synchronized指令锁定的唯一对象
    NSUInteger computeTasks; // 并行计算任务的计数
    NSLock *computeLock;     // 锁对象
}

-(id)init{
    if ((self = [super init])) {
        _isFinished = NO;
        _computeResult = 0;
        computeLock = [NSLock new];
        computeID = @"1";
        computeTasks = 0;
    }
    return self;
}

-(void)computeTask:(id)data{
    NSAssert(([data isKindOfClass:[NSNumber class]]), @"Not an NSNumber instance");
    NSUInteger computations = [data unsignedIntegerValue];
    // 配置线程环境时应在线程入口点创建自动释放池与异常处理程序
    @autoreleasepool {
        @try {
            if ([[NSThread currentThread] isCancelled]) {
                return;
            }
            // @synchronized指令括号内为唯一标识符,标识符保护的代码块对象只能同时被最多一个线程访问
            @synchronized (computeID) {
                // 增加活动任务的计数
                computeTasks++;
            }

            // 获取锁并执行关键代码部分中的计算操作
            [computeLock lock];
            if ([[NSThread currentThread] isCancelled]) {
                [computeLock unlock];
                return;
            }
            NSLog(@"Performing computations %lu",computations);
            for (int ii = 0; ii < computations; ii++) {
                self.computeResult = self.computeResult + 1;
            }
            // 完成计算并解除锁
            [computeLock unlock];
            // 模拟额外的处理时间
            [NSThread sleepForTimeInterval:1.0];

            // 减少活动任务数,如果数量为0,则更新标志位
            @synchronized (computeID) {
                computeTasks--;
                if (!computeTasks) {
                    self.isFinished = YES;
                }
            }
        } @catch (NSException *exception) {}
    }
}
@end

最后在main.m中进行测试:

#import <Foundation/Foundation.h>
#import "ConcurrentProcessor.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ConcurrentProcessor *processor = [ConcurrentProcessor new];
        // 隐式地创建并启动3个线程
        [processor performSelectorInBackground:@selector(computeTask:) withObject:[NSNumber numberWithInt:5]];
        [processor performSelectorInBackground:@selector(computeTask:) withObject:[NSNumber numberWithInt:10]];
        [processor performSelectorInBackground:@selector(computeTask:) withObject:[NSNumber numberWithInt:20]];

        while (!processor.isFinished);
        NSLog(@"Computation result = %ld",processor.computeResult);
    }
    return 0;
}

运行结果:

2016-07-19 15:15:47.700 ConcurrentThreads[17142:161246] Performing computations 10
2016-07-19 15:15:47.700 ConcurrentThreads[17142:161247] Performing computations 20
2016-07-19 15:15:47.701 ConcurrentThreads[17142:161245] Performing computations 5
2016-07-19 15:15:48.774 ConcurrentThreads[17142:161222] Computation result = 35

显示创建并启动线程

可以直接使用NSThread类中的API显示地创建并管理线程,与隐式方法等价。

#import <Foundation/Foundation.h>
#import "ConcurrentProcessor.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ConcurrentProcessor *processor = [ConcurrentProcessor new];
        // 隐式地创建并启动1个线程
        [processor performSelectorInBackground:@selector(computeTask:) withObject:[NSNumber numberWithInt:5]];

        // 显示创建并启动1个线程
        [NSThread detachNewThreadSelector:@selector(computeTask:) toTarget:processor withObject:[NSNumber numberWithInt:10]];

        // 显示创建1个线程,需要用start方法手动启动
        NSThread *computeThread = [[NSThread alloc] initWithTarget:processor selector:@selector(computeTask:) object:[NSNumber numberWithInt:20]];
        [computeThread setThreadPriority:0.5];
        [computeThread start];

        while (!processor.isFinished);
        NSLog(@"Computation result = %ld",processor.computeResult);
    }
    return 0;
}

运行结果:

2016-07-19 15:18:12.607 ConcurrentThreads[17269:162710] Performing computations 10
2016-07-19 15:18:12.608 ConcurrentThreads[17269:162711] Performing computations 20
2016-07-19 15:18:12.608 ConcurrentThreads[17269:162709] Performing computations 5
2016-07-19 15:18:13.681 ConcurrentThreads[17269:162677] Computation result = 35

操作和操作队列

用操作类实现并发

NSOperation类(及其子类)的实例可以为单个任务封装代码,在处理非并发任务时,具体子类通常只需要重写main方法。而在处理并发任务时,至少必须重写start、isConcurrent、isExecuting、isFinishied方法。后面3个方法必须返回与操作状态有关的值,而且这3个方法必须具备线程安全性。当这些值改变时,这些方法还必须生成适当的键值观察(KVO)通知。下面是用NSOperation类的子类实现并发的示例。

首先创建NSOperation类的子类GreetingOperation类:

#import <Foundation/Foundation.h>

@interface GreetingOperation : NSOperation

@end
#import "GreetingOperation.h"

@implementation GreetingOperation
{
    BOOL finished;
    BOOL executing;
}

-(id)init{
    if ((self = [super init])) {
        executing = NO;
        finished = NO;
    }
    return self;
}

-(void)start{
    // 如果操作被取消了就返回结果
    if ([self isCancelled]) {
        // 修改isFinished方法返回值时必须发送KVO通知
        [self willChangeValueForKey:@"isFinished"];
        finished = YES;
        [self didChangeValueForKey:@"isFinished"];
        return;
    }

    // 修改isExecuting方法返回值时必须发送KVO通知
    [self willChangeValueForKey:@"isExecuting"];
    // 使用独立线程执行main方法中的操作
    [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
    executing = YES;
    [self didChangeValueForKey:@"isExecuting"];
}

-(void)main{
    @autoreleasepool {
        @try {
            if (![self isCancelled]) {
                NSLog(@"Hello,World");
                // 暂停,以便模拟执行任务的过程
                [NSThread sleepForTimeInterval:3.0];
                NSLog(@"Goodbye,World");
                [self willChangeValueForKey:@"isFinished"];
                [self willChangeValueForKey:@"isExecuting"];
                executing = NO;
                finished = YES;
                [self didChangeValueForKey:@"isExecuting"];
                [self didChangeValueForKey:@"isFinished"];
            }
        } @catch (NSException *exception) {
        }
    }
}

-(BOOL)isConcurrent{
    return YES;
}

-(BOOL)isExecuting{
    return executing;
}

-(BOOL)isFinished{
    return finished;
}
@end

最后在main.m中进行测试:

#import <Foundation/Foundation.h>
#import "GreetingOperation.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        GreetingOperation *greetingOp = [GreetingOperation new];
        [greetingOp start];
        GreetingOperation *greetingOp2 = [GreetingOperation new];
        [greetingOp2 start];
        // 如果没有这行代码,main函数会在操作中的main方法执行完成之前就返回
        while (![greetingOp isFinished] || ![greetingOp2 isFinished]);
    }
    return 0;
}

运行结果:

2016-07-19 16:15:45.642 GreetingOperation[20255:195915] Hello,World
2016-07-19 16:15:45.642 GreetingOperation[20255:195914] Hello,World
2016-07-19 16:15:48.647 GreetingOperation[20255:195914] Goodbye,World
2016-07-19 16:15:48.647 GreetingOperation[20255:195915] Goodbye,World

用操作队列实现并发

操作队列是一种提供并发执行操作能力的机制。可以将NSOperation类或其子类的对象添加到NSOperationQueue对象中。下面是操作队列的使用示例。

首先创建一个NSOperation类的子类ConcurrentProcessor类:

#import <Foundation/Foundation.h>

@interface ConcurrentProcessor : NSOperation

@property(readonly) NSUInteger computations;

-(id)initWithData:(NSInteger *)result computations:(NSUInteger)computations;

@end
#import "ConcurrentProcessor.h"

@implementation ConcurrentProcessor
{
    NSInteger *computeResult;
}

-(id)initWithData:(NSInteger *)result computations:(NSUInteger)computations{
    if ((self = [super init])) {
        _computations = computations;
        computeResult = result;
    }
    return self;
}

-(void)main{
    @autoreleasepool {
        @try {
            if (![self isCancelled]) {
                NSLog(@"Performing %ld computations", self.computations);
                [NSThread sleepForTimeInterval:1.0];
                for (int ii = 0; ii < self.computations; ii++) {
                    // computeResult为操作数的地址
                    *computeResult = *computeResult + 1;
                }
            }
        } @catch (NSException *exception) {}
    }
}
@end

最后在main.m中进行测试:

#import <Foundation/Foundation.h>
#import "ConcurrentProcessor.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 创建操作队列类对象
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        NSInteger result = 0;

        // 创建3个操作类对象
        ConcurrentProcessor *proc1 = [[ConcurrentProcessor alloc] initWithData:&result computations:5];
        ConcurrentProcessor *proc2 = [[ConcurrentProcessor alloc] initWithData:&result computations:10];
        ConcurrentProcessor *proc3 = [[ConcurrentProcessor alloc] initWithData:&result computations:20];

        NSArray *operations = @[proc1, proc2, proc3];

        // 添加操作间的依赖性,proc1必须先于proc2
        [proc2 addDependency:proc1];

        // 将操作对象添加到操作队列中执行
        [queue addOperations:operations waitUntilFinished:NO];

        // 等待,当所有操作完成时显示结果
        [queue waitUntilAllOperationsAreFinished];
        NSLog(@"Computation result = %ld", result);
    }
    return 0;
}

运行结果:

2016-07-19 16:20:28.006 ConcurrentOperations[20508:198875] Performing 5 computations
2016-07-19 16:20:28.006 ConcurrentOperations[20508:198877] Performing 20 computations
2016-07-19 16:20:29.011 ConcurrentOperations[20508:198877] Performing 10 computations
2016-07-19 16:20:30.016 ConcurrentOperations[20508:198815] Computation result = 32

可以看到,proc1与proc3是并发执行的,而proc2必须等proc1执行完成后才能执行。

分派队列GCD

Grand Central Dispatch(GCD)是一个集合,它含有语言特性、基于C语言API,以及支持使用分派队列执行任务的系统增强功能。下面是使用GCD的示例。

GCD的使用非常方便,不需要创建其他类:

#import <Foundation/Foundation.h>
typedef void (^ComputeTask)(void);

// 函数返回用于累加的语句块
ComputeTask getComputeTask(NSInteger *result, NSUInteger computations){
    NSInteger *computeResult = result;
    return ^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"Performing %ld computations", computations);
        for (int ii = 0; ii < computations; ii++) {
            *computeResult = *computeResult + 1;
        }
    };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSInteger computeResult;
        NSInteger computeResult2;

        // 创建顺序队列和分组
        dispatch_queue_t serialQueue = dispatch_queue_create("MySerialQueue", DISPATCH_QUEUE_SERIAL);
        dispatch_queue_t serialQueue2 = dispatch_queue_create("MySerialQueue2", DISPATCH_QUEUE_SERIAL);
        dispatch_group_t group = dispatch_group_create();

        // 分别向两个队列中添加任务
        dispatch_group_async(group, serialQueue, getComputeTask(&computeResult, 1));
        dispatch_group_async(group, serialQueue, getComputeTask(&computeResult, 3));
        dispatch_group_async(group, serialQueue, getComputeTask(&computeResult, 5));

        dispatch_group_async(group, serialQueue2, getComputeTask(&computeResult2, 2));
        dispatch_group_async(group, serialQueue2, getComputeTask(&computeResult2, 4));
        dispatch_group_async(group, serialQueue2, getComputeTask(&computeResult2, 6));

        // 等待,当分组中的所有任务都完成时显示结果
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        NSLog(@"Computation result = %ld", computeResult);
        NSLog(@"Computation result2 = %ld", computeResult2);
    }
    return 0;
}

运行结果:

2016-07-19 16:38:35.074 ConcurrentDispatch[21460:209103] Performing 1 computations
2016-07-19 16:38:35.074 ConcurrentDispatch[21460:209104] Performing 2 computations
2016-07-19 16:38:36.080 ConcurrentDispatch[21460:209103] Performing 3 computations
2016-07-19 16:38:36.080 ConcurrentDispatch[21460:209104] Performing 4 computations
2016-07-19 16:38:37.083 ConcurrentDispatch[21460:209104] Performing 6 computations
2016-07-19 16:38:37.083 ConcurrentDispatch[21460:209103] Performing 5 computations
2016-07-19 16:38:37.083 ConcurrentDispatch[21460:209079] Computation result = 9
2016-07-19 16:38:37.083 ConcurrentDispatch[21460:209079] Computation result2 = 12

分析结果可以看出,2个队列是并发执行的,而同一个队列中的任务是异步执行的。

三种方式的比较

操作队列和GCD提供了异步、基于队列的机制,不仅替代了低级线程管理,并且与基于线程的编程方式相比,最大化了系统利用率和效率。操作队列是基于对象的,与GCD相比,会有更多的系统开销,占用更多的资源,不过它的面向对象的高级API与Objective-C平台一致,因而更易于使用,而且支持复杂的操作间的依赖关系、基于约束的执行和操作对象管理。

GCD提供了低级API(基于C语言)并且是轻量级的。所以性能更好,且需要使用的代码更少。

由于操作队列和GCD都不处理实时约束,所以在实时系统中,线程仍旧是合适的并发编程机制。

除此之外,Foundation框架还提供了多种执行异步处理的API,如NSFileHandle类和NSPort类,在可以使用异步API时应该尽量使用。

时间: 2024-08-07 09:37:41

[精通Objective-C]三种实现并发编程的方式的相关文章

Js之Dom学习-三种获取页面元素的方式、事件、innerText和innerHTML的异同

一.三种获取页面元素的方式: getElementById:通过id来获取 <body> <input type="text" value="请输入一个值:" id="txt"/> <input type="button" value="按钮" id="btn"/> <script> var txt=document.getElementB

三种呈现错误页面的方式

三种呈现错误页面的方式 由于ASP.NET Core应用是一个同时处理多个请求的服务器应用,所以在处理某个请求过程中抛出的异常并不会导致整个应用的终止.出于安全方面的考量,为了避免敏感信息的外泄,客户端在默认的情况下并不会得到详细的出错信息,这无疑会在开发环境下增加查错纠错的难度.对于生产环境来说,我们也希望最终用户能够根据具体的错误类型得到具有针对性并且友好的错误消息.ASP.NET Core提供了相应的中间件帮助我们将定制化的错误信息呈现出来,这些中间件都定义在"Microsoft.AspN

Android中三种超实用的滑屏方式汇总(转载)

Android中三种超实用的滑屏方式汇总 现如今主流的Android应用中,都少不了左右滑动滚屏这项功能,(貌似现在好多人使用智能机都习惯性的有事没事的左右滑屏,也不知道在干什么...嘿嘿),由于前段时间项目的需要,所以也对其研究了一下,总的来说滑屏实现有三种方式:(至于其他的实现方式目前后还没碰到...) 1.ViewPager 2.ViewFlipper 3.ViewFlow 一.ViewPager 官方文档介绍:http://developer.android.com/reference/

Android三种实现自定义ProgressBar的方式介绍

一.通过动画实现 定义res/anim/loading.xml如下: View Row Code<?xml version="1.0" encoding="UTF-8"?><animation-list android:oneshot="false"xmlns:android="http://schemas.android.com/apk/res/android"><item android:du

C# 三种实现抖屏的方式

1 //int a = -2; 2 //this.BringToFront(); 3 //for (int i = 0; i < 20; i++) 4 //{ 5 // a = -a; 6 // this.Location = new Point(this.Location.X + 6 * a, this.Location.Y + 8 * a); 7 8 // Thread.Sleep(100); 9 10 //} 11 //2. 12 //int X = this.Top; int Y = t

Scala入门到精通——第二十六节 Scala并发编程基础

作者:摇摆少年梦 视频地址:http://www.xuetuwuyou.com/course/12 本节主要内容 Scala并发编程简介 Scala Actor并发编程模型 react模型 Actor的几种状态 Actor深入使用解析 本节主要介绍的scala并发编程的基本思想,由于scala在2.10版本之后宣布使用akka作为其并发编程库,因此本节只进行基础性的内容介绍,后面将把重点放在akka框架的讲解上. 1. Scala并发编程简介 2003 年,Herb Sutter 在他的文章 "

第5课:彻底精通Scala隐式转换和并发编程及Spark源码阅读

隐式转换函数implicit def function例如implicit def rddToPairRDDFunctions[K, V](rdd: RDD[(K, V)]) scala> class Person(val name: String)defined class Person scala> class Enginner(val name: String, val salary: Double) { | def code = println(name + " Coding

详解三种java实现多线程的方式

java中实现多线程的方法有两种:继承Thread类和实现runnable接口. 1.继承Thread类,重写父类run()方法 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class thread1 extends Thread {    public void run() {        for (int i = 0; i < 10000; i++) {            System.out.println("我是线程"+

简述三种异步上传文件方式

 很久没写过博客了! 上次写已经是去年的12月了,离现在也有足足三个月了.这几个月因为要搭建个人网站以及准备个人简历,包括最近要准备最近的各大公司的实习春招,很难抽时间来写博客,这次的异步文件上传我觉得是很有必要了解的,笼络了很多知识点,因此准备写一篇博客来巩固下. 异步上传文件是为了更好的用户体验,是每个前端必须掌握的技能.这里我提出三点有关异步文件上传的方式. 使用第三方控件,如Flash,ActiveX等浏览器插件上传. 使用隐藏的iframe模拟异步上传. 使用XMLHttpReques