多线程编程技术之NSThread、Cocoa NSOperation、GCD

iOS有多种线程编程的技术,分别是:
(一)NSThread
(二)Cocoa NSOperation
(三)GCD(全称:Grand Central Dispatch)

这三种编程方式从上到下,抽象度层次是从低到高的,抽象度越高的使用越简单,也是Apple最推荐使用的。
三种方式的优缺点介绍:
1)NSThread:
优点:NSThread 比其他两个轻量级(轻量级重量级的划分:实际上是以启动程序需要的资源来决定,轻量级就是所需要的资源要少一些)
缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销

2)Cocoa NSOperation
优点:不需要关心线程管理,数据同步的事情,可以把精力放在自己需要执行的操作上。
Cocoa operation 相关的类是 NSOperation ,NSOperationQueue。
NSOperation是个抽象类,使用它必须用它的子类,可以实现它或者使用它定义好的两个子类:NSInvocationOperation 和 NSBlockOperation。
创建NSOperation子类的对象,把对象添加到NSOperationQueue队列里执行。

3)GCD
Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法,是基于C语言的框架。在iOS4.0开始之后才能使用。GCD是一个替代诸如NSThread, NSOperationQueue, NSInvocationOperation等技术的很高效和强大的技术。代码比NSThread 、NSOperation简洁很多,而且GCD会自动根据任务在多核处理器上分配资源,优化程序。

介绍完这三种多线程编程方式,本文将依次介绍这三种技术的使用。
(一)NSThread的使用
NSThread 有两种直接创建方式:

- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument 
+ (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument

即:

- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument 
+ (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument

第一个是实例方法,第二个是类方法

1、[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];   

2NSThread* myThread = [[NSThread alloc] initWithTarget:self   
                                        selector:@selector(doSomething:)   
                                        object:nil];   
[myThread start];//alloc 方式创建的是需要手动开启线程

参数的意义:
selector :线程执行的方法,这个selector只能有一个参数,而且不能有返回值。
target :selector消息发送的对象
argument:传输给target的唯一参数,也可以是nil

第一种方式会直接创建线程并且开始运行线程,第二种方式是先创建线程对象,然后再运行线程操作,在运行线程操作前可以设置线程的优先级等线程信息

不显式创建线程的方法:
用NSObject的类方法 performSelectorInBackground:withObject: 创建一个线程:

[Obj performSelectorInBackground:@selector(doSomething) withObject:nil];

下载图片的例子:
新建singeView app
新建项目,并在xib文件上放置一个imageView控件。按住control键拖到viewController.h文件中创建imageView IBOutlet ViewController.m中实现:

 ViewController.m   


#import "ViewController.h"   
#define kURL @"http://avatar.youkuaiyun.com/2/C/D/1_totogo2010.jpg"   
@interface ViewController ()   

@end   

@implementation ViewController   

-(void)downloadImage:(NSString *) url{   
    NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:url]];   
    UIImage *image = [[UIImage alloc]initWithData:data];   
    if(image == nil){   

    }else{   
        [self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];   
    }   
}   

-(void)updateUI:(UIImage*) image{   
    self.imageView.image = image;   
}   

- (void)viewDidLoad   
{   
    [super viewDidLoad];   

//    [NSThread detachNewThreadSelector:@selector(downloadImage:) toTarget:self withObject:kURL];   
    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(downloadImage:) object:kURL];   
    [thread start];   
}   

- (void)didReceiveMemoryWarning   
{   
    [super didReceiveMemoryWarning];   
    // Dispose of any resources that can be recreated.   
}   

@end

线程间通讯
线程下载完图片后怎么通知主线程更新界面呢?
[self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];

performSelectorOnMainThread是NSObject的方法,除了可以更新主线程的数据外,还可以更新其他线程的比如:
performSelector:onThread:withObject:waitUntilDone:

线程同步
我们演示一个经典的卖票的例子来讲NSThread的线程同步:

#import <UIKit/UIKit.h>   

@class ViewController;   

@interface AppDelegate : UIResponder <UIApplicationDelegate>   
{   
    int tickets;   
    int count;   
    NSThread* ticketsThreadone;   
    NSThread* ticketsThreadtwo;   
    NSCondition* ticketsCondition;   
    NSLock *theLock;   
}   
@property (strong, nonatomic) UIWindow *window;   

@property (strong, nonatomic) ViewController *viewController;   

@end
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions   
{   

    tickets = 100;   
    count = 0;   
    theLock = [[NSLock alloc] init];   
    // 锁对象   
    ticketsCondition = [[NSCondition alloc] init];   
    ticketsThreadone = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];   
    [ticketsThreadone setName:@"Thread-1"];   
    [ticketsThreadone start];   

    ticketsThreadtwo = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];   
    [ticketsThreadtwo setName:@"Thread-2"];   
    [ticketsThreadtwo start];   

    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];   
    // Override point for customization after application launch.   
    self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];   
    self.window.rootViewController = self.viewController;   
    [self.window makeKeyAndVisible];   
    return YES;   
}   

- (void)run{   
    while (TRUE) {   
        // 上锁   (形成资源竞争的时候
也就是多个线程有可能访问同一共享资源的时候需要加锁,以保证在同一时刻只有一个线程在执行操作。)
//        [ticketsCondition lock];   
        [theLock lock];   
        if(tickets >= 0){   
            [NSThread sleepForTimeInterval:0.09];   
            count = 100 - tickets;   
            NSLog(@"当前票数是:%d,售出:%d,线程名:%@",tickets,count,[[NSThread currentThread] name]);   
            tickets--;   
        }else{   
            break;   
        }   
        [theLock unlock];   
//        [ticketsCondition unlock];   
    }   
}

如果没有线程同步的lock,最后肯能会出错,因为不加锁的话,在票数只剩卖剩下的这一张票时,假如两个售票员同时卖这张票就会出现卖的这张票是-1的情况了.加上lock之后线程同步保证了数据的正确性。即保证同一时间只有一个售票员操作这张票。
上面例子我使用了两种锁,一种NSCondition ,一种是:NSLock。 NSCondition我已经注释了。

线程的顺序执行
他们都可以通过[ticketsCondition signal]; 发送信号的方式,在一个线程唤醒另外一个线程的等待。

比如:

#import "AppDelegate.h"   

#import "ViewController.h"   

@implementation AppDelegate   

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions   
{   

    tickets = 100;   
    count = 0;   
    theLock = [[NSLock alloc] init];   
    // 锁对象   
    ticketsCondition = [[NSCondition alloc] init];   
    ticketsThreadone = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];   
    [ticketsThreadone setName:@"Thread-1"];   
    [ticketsThreadone start];   

    ticketsThreadtwo = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];   
    [ticketsThreadtwo setName:@"Thread-2"];   
    [ticketsThreadtwo start];   

    NSThread *ticketsThreadthree = [[NSThread alloc] initWithTarget:self selector:@selector(run3) object:nil];   
    [ticketsThreadthree setName:@"Thread-3"];   
    [ticketsThreadthree start];       
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];   
    // Override point for customization after application launch.   
    self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];   
    self.window.rootViewController = self.viewController;   
    [self.window makeKeyAndVisible];   
    return YES;   
}   

-(void)run3{   
    while (YES) {   
        [ticketsCondition lock];   
        [NSThread sleepForTimeInterval:3];   
        [ticketsCondition signal];   
        [ticketsCondition unlock];   
    }   
}   

- (void)run{   
    while (TRUE) {   
        // 上锁   
        [ticketsCondition lock];   
        [ticketsCondition wait];   
        [theLock lock];   
        if(tickets >= 0){   
            [NSThread sleepForTimeInterval:0.09];   
            count = 100 - tickets;   
            NSLog(@"当前票数是:%d,售出:%d,线程名:%@",tickets,count,[[NSThread currentThread] name]);   
            tickets--;   
        }else{   
            break;   
        }   
        [theLock unlock];   
        [ticketsCondition unlock];   
    }   
}

wait是等待,我加了一个 线程3 去唤醒其他两个线程锁中的wait。

其他同步
我们可以使用指令 @synchronized 来简化 NSLock的使用,这样我们就不必显示编写创建NSLock,加锁并解锁相关代码。

- (void)doSomeThing:(id)anObj 
{ 
    @synchronized(anObj) 
    { 
        // Everything between the braces is protected by the @synchronized directive. 
    } 
}

还有其他的一些锁对象,比如:循环锁NSRecursiveLock,条件锁NSConditionLock,分布式锁NSDistributedLock等等,可以自己看官方文档学习

二)Cocoa NSOperation的使用
使用 NSOperation的方式有两种,
一种是用定义好的两个子类:NSInvocationOperation 和 NSBlockOperation。
另一种是继承NSOperation
NSOperation是设计用来扩展的,只需继承重写NSOperation的一个方法main。然后把NSOperation子类的对象放入NSOperationQueue队列中,该队列就会启动并开始处理它。
NSInvocationOperation例子:

#import "ViewController.h"   
#define kURL @"http://avatar.youkuaiyun.com/2/C/D/1_totogo2010.jpg"   

@interface ViewController ()    
@end   

@implementation ViewController   

- (void)viewDidLoad   
{   
    [super viewDidLoad];   
    NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self   
                                                                           selector:@selector(downloadImage:)   
                                                                          object:kURL];   

    NSOperationQueue *queue = [[NSOperationQueue alloc]init];   
    [queue addOperation:operation];   
    // Do any additional setup after loading the view, typically from a nib.   
}   

-(void)downloadImage:(NSString *)url{   
    NSLog(@"url:%@", url);   
    NSURL *nsUrl = [NSURL URLWithString:url];   
    NSData *data = [[NSData alloc]initWithContentsOfURL:nsUrl];   
    UIImage * image = [[UIImage alloc]initWithData:data];   
    [self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];   
}   
-(void)updateUI:(UIImage*) image{   
    self.imageView.image = image;   
}

代码注释:
1.viewDidLoad方法里可以看到我们用NSInvocationOperation建了一个后台线程,并且放到
2.NSOperationQueue中。后台线程执行downloadImage方法。
3.downloadImage 方法处理下载图片的逻辑。下载完成后用performSelectorOnMainThread执行主线程updateUI方法。
updateUI 并把下载的图片显示到图片控件中。

第二种方式继承NSOperation
在.m文件中实现main方法,main方法编写要执行的代码即可。

如何控制线程池中的线程数?
队列里可以加入很多个NSOperation, 可以把NSOperationQueue看作一个线程池,可往线程池中添加操作(NSOperation)到队列中。线程池中的线程可看作消费者,从队列中取走操作,并执行它。
通过下面的代码设置:
[queue setMaxConcurrentOperationCount:5];
线程池中的线程数,也就是并发操作数。默认情况下是-1,-1表示没有限制,这样会同时运行队列中的全部的操作。

(三)GCD的介绍和使用
介绍:
Grand Central Dispatch 简称(GCD)是苹果公司开发的技术,以优化的应用程序支持多核心处理器和其他的对称多处理系统的系统。这建立在任务并行执行的线程池模式的基础上的。它首次发布在Mac OS X 10.6 ,iOS 4及以上也可用。

设计:
GCD的工作原理是:让程序平行排队的特定任务,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务。

一个任务可以是一个函数(function)或者是一个block。 GCD的底层依然是用线程实现,不过这样可以让程序员不用关注实现的细节。

GCD中的FIFO队列称为dispatch queue,它可以保证先进来的任务先得到执行。

dispatch queue分为下面三种
Serial
又称为private dispatch queues,同时只执行一个任务。Serial queue通常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然它们各自是同步执行的,但Serial queue与Serial queue之间是并发执行的。

Concurrent
又称为global dispatch queue,可以并发地执行多个任务,但是执行完成的顺序是随机的。

Main dispatch queue
它是全局可用的serial queue,它是在应用程序主线程上执行任务的。

我们看看dispatch queue如何使用?

1、常用的方法dispatch_async
为了避免界面在处理耗时的操作时卡死,比如读取网络数据,IO,数据库读写等,我们会在另外一个线程中处理这些操作,然后通知主线程更新界面。

用GCD实现这个流程的操作比前面介绍的NSThread NSOperation的方法都要简单。代码框架结构如下:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{   
    // 耗时的操作   
    dispatch_async(dispatch_get_main_queue(), ^{   
        // 更新界面   
    });   
});

如果这样还不清晰的话,那我们还是用上两篇博客中的下载图片为例子,代码如下:

    NSURL * url = [NSURL URLWithString:@"http://avatar.youkuaiyun.com/2/C/D/1_totogo2010.jpg"];   
    NSData * data = [[NSData alloc]initWithContentsOfURL:url];   
    UIImage *image = [[UIImage alloc]initWithData:data];   
    if (data != nil) {   
        dispatch_async(dispatch_get_main_queue(), ^{   
            self.imageView.image = image;   
         });   
    }   
});

代码比NSThread NSOperation简洁很多,而且GCD会自动根据任务在多核处理器上分配资源,优化程序。

系统给每一个应用程序提供了三个concurrent dispatch queues。这三个并发调度队列是全局的,它们只有优先级的不同。因为是全局的,我们不需要去创建。我们只需要通过使用函数dispath_get_global_queue去得到队列,如下:

dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

这里也用到了系统默认就有一个串行队列main_queue:

dispatch_queue_t mainQ = dispatch_get_main_queue();

虽然dispatch queue是引用计数的对象,但是以上两个都是全局的队列,不用retain或release。

2、dispatch_group_async的使用
dispatch_group_async可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。这个方法很有用,比如你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成的了。下面是一段例子代码:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);   
dispatch_group_t group = dispatch_group_create();   
dispatch_group_async(group, queue, ^{   
    [NSThread sleepForTimeInterval:1];   
    NSLog(@"group1");   
});   
dispatch_group_async(group, queue, ^{   
    [NSThread sleepForTimeInterval:2];   
    NSLog(@"group2");   
});   
dispatch_group_async(group, queue, ^{   
    [NSThread sleepForTimeInterval:3];   
    NSLog(@"group3");   
});   
dispatch_group_notify(group, dispatch_get_main_queue(), ^{   
    NSLog(@"updateUi");   
});   
dispatch_release(group);

dispatch_group_async是异步的方法,运行后可以看到打印结果:

2012-09-25 16:04:16.737 gcdTest[43328:11303] group1 
2012-09-25 16:04:17.738 gcdTest[43328:12a1b] group2 
2012-09-25 16:04:18.738 gcdTest[43328:13003] group3 
2012-09-25 16:04:18.739 gcdTest[43328:f803] updateUi

每隔一秒打印一个,当第三个任务执行后,upadteUi被打印。

3、dispatch_barrier_async的使用
dispatch_barrier_async是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
例子代码如下:

dispatch_queue_t queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT);   
dispatch_async(queue, ^{   
    [NSThread sleepForTimeInterval:2];   
    NSLog(@"dispatch_async1");   
});   
dispatch_async(queue, ^{   
    [NSThread sleepForTimeInterval:4];   
    NSLog(@"dispatch_async2");   
});   
dispatch_barrier_async(queue, ^{   
    NSLog(@"dispatch_barrier_async");   
    [NSThread sleepForTimeInterval:4];   

});   
dispatch_async(queue, ^{   
    [NSThread sleepForTimeInterval:1];   
    NSLog(@"dispatch_async3");   
});

打印结果:

2012-09-25 16:20:33.967 gcdTest[45547:11203] dispatch_async1 
2012-09-25 16:20:35.967 gcdTest[45547:11303] dispatch_async2 
2012-09-25 16:20:35.967 gcdTest[45547:11303] dispatch_barrier_async 
2012-09-25 16:20:40.970 gcdTest[45547:11303] dispatch_async3

4、dispatch_apply
执行某个代码片段N次。

dispatch_apply(5, globalQ, ^(size_t index) { 
    // 执行5次 
});

GCD还有很多其他用法,可以参考官方文档、http://en.wikipedia.org/wiki/Grand_Central_Dispatch

内容概要:本文深入探讨了Kotlin语言在函数式编程和跨平台开发方面的特性和优势,结合详细的代码案例,展示了Kotlin的核心技巧和应用场景。文章首先介绍了高阶函数和Lambda表达式的使用,解释了它们如何简化集合操作和回调函数处理。接着,详细讲解了Kotlin Multiplatform(KMP)的实现方式,包括共享模块的创建和平台特定模块的配置,展示了如何通过共享业务逻辑代码提高开发效率。最后,文章总结了Kotlin在Android开发、跨平台移动开发、后端开发和Web开发中的应用场景,并展望了其未来发展趋势,指出Kotlin将继续在函数式编程和跨平台开发领域不断完善和发展。; 适合人群:对函数式编程和跨平台开发感兴趣的开发者,尤其是有一定编程基础的Kotlin初学者和中级开发者。; 使用场景及目标:①理解Kotlin中高阶函数和Lambda表达式的使用方法及其在实际开发中的应用场景;②掌握Kotlin Multiplatform的实现方式,能够在多个平台上共享业务逻辑代码,提高开发效率;③了解Kotlin在不同开发领域的应用场景,为选择合适的技术栈提供参考。; 其他说明:本文不仅提供了理论知识,还结合了大量代码案例,帮助读者更好地理解和实践Kotlin的函数式编程特性和跨平台开发能力。建议读者在学习过程中动手实践代码案例,以加深理解和掌握。
内容概要:本文深入探讨了利用历史速度命令(HVC)增强仿射编队机动控制性能的方法。论文提出了HVC在仿射编队控制中的潜在价值,通过全面评估HVC对系统的影响,提出了易于测试的稳定性条件,并给出了延迟参数与跟踪误差关系的显式不等式。研究为两轮差动机器人(TWDRs)群提供了系统的协调编队机动控制方案,并通过9台TWDRs的仿真和实验验证了稳定性和综合性能改进。此外,文中还提供了详细的Python代码实现,涵盖仿射编队控制、HVC增强、稳定性条件检查以及仿真实验。代码不仅实现了论文的核心思想,还扩展了邻居历史信息利用、动态拓扑优化和自适应控制等性能提升策略,更全面地反映了群体智能协作和性能优化思想。 适用人群:具备一定编程基础,对群体智能、机器人编队控制、时滞系统稳定性分析感兴趣的科研人员和工程师。 使用场景及目标:①理解HVC在仿射编队控制中的应用及其对系统性能的提升;②掌握仿射编队控制的具体实现方法,包括控制器设计、稳定性分析和仿真实验;③学习如何通过引入历史信息(如HVC)来优化群体智能系统的性能;④探索中性型时滞系统的稳定性条件及其在实际系统中的应用。 其他说明:此资源不仅提供了理论分析,还包括完整的Python代码实现,帮助读者从理论到实践全面掌握仿射编队控制技术。代码结构清晰,涵盖了从初始化配置、控制律设计到性能评估的各个环节,并提供了丰富的可视化工具,便于理解和分析系统性能。通过阅读和实践,读者可以深入了解HVC增强仿射编队控制的工作原理及其实际应用效果。
内容概要:本文深入探讨了与液冷数据中心废热集成的跨临界压缩 CO₂储能系统(CCES)。研究构建了单级压缩(System-CP)和双级压缩(System-VP)两种系统,通过准动态模型分析其能量和㶲性能,并开发经济模型评估其经济性。研究结果显示,System-CP 和 System-VP 的往返效率分别为 64.67% 和 67.41%,储能密度分别为 0.24 和 0.26 kW·h/m³。对于 15 MW × 5 h 的储能容量,两种系统的总资本成本分别为 4.7784 亿和 4.3741 亿美元,投资回收期分别为 14.76 年和 12.39 年。此外,研究还揭示了关键参数如压力比、热源温度等对系统性能的影响,提出了优化建议和技术实现路径。 适合人群:从事能源管理、数据中心运营、储能技术研发的专业人员以及关注绿色能源和碳减排的研究者。 使用场景及目标:①评估液冷数据中心与跨临界 CO₂储能系统的集成方案;②优化数据中心废热利用效率;③分析不同压缩系统在储能密度、成本和效率方面的差异;④探索系统集成的经济性和技术可行性。 其他说明:该研究不仅提供了详细的热力学和经济性分析,还通过实证数据验证了系统的技术经济可行性,为绿色数据中心的建设提供了量化决策依据。文中还讨论了未来的研究方向,如低温差高效热泵材料、自适应压力容器设计等,旨在进一步提升系统的性能和经济性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值