使用NSObject類中的performSelectorInBackground: withObject:方法可以隱式地創建和啟動用於執行對象中方法的新線程。該線程會作為後台次要進程立刻啟動,而當前進程會立刻返回。下面是一個使用該方法的實例:
首先創建一個繼承於NSObject類的,含有將由獨立線程執行的方法的類:
#import
@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
#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
#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
@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
#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
@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
#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
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時應該盡量使用。