objective-c 编程总结,第十篇并行开发与线程管理

写这一篇总结有些纠结。因为这是一个很大的题目,而我只是为了自己总结一下,没想长篇大论写教程。思来想去,还是写一个备忘录言简意赅吧。

从apple给出的开发指引来看,apple官方是不推荐使用自定义线程的,而是推荐使用block、NSOpration这样的方式进行异步调用。因为内部的实现保证了更好的资源管理。并且给出了创建线程的代价参考。Mac OS和iOS不仅需要为线程分配堆栈空间,而且还需要分配内核空间。这样看起来线程是非常消耗资源的。但是在某些情况下,又必须使用一些特殊的线程创建方式,例如程序结束之前的数据持久化操作,需要创建“可连接”线程(jointable)。这个时候就要用到Posix线程创建方式。

这里是官方给出的线程消耗资源的参考表:

ItemApproximate costNotes
Kernel data structuresApproximately 1 KBThis memory is used to store the thread data structures and attributes, much of which is allocated as wired memory and therefore cannot be paged to disk.
Stack space512 KB (secondary threads) 8 MB (Mac OS X main thread) 1 MB (iOS main thread)The minimum allowed stack size for secondary threads is 16 KB and the stack size must be a multiple of 4 KB. The space for this memory is set aside in your process space at thread creation time, but the actual pages associated with that memory are not created until they are needed.
Creation timeApproximately 90 microsecondsThis value reflects the time between the initial call to create the thread and the time at which the thread’s entry point routine began executing. The figures were determined by analyzing the mean and median values generated during thread creation on an Intel-based iMac with a 2 GHz Core Duo processor and 1 GB of RAM running Mac OS X v10.5.

线程创建时间,90毫秒。这个大于多数实例的创建时间。内核数据占用大概1KB的空间,堆栈空间最小16KB,一般都是512KB for iOS/8MB for Mac.这个数据看起来就有点儿狠了。iphone程序创建4、5个现成,就要占去2MB的数据,而iphone一般可用的堆栈空间大概只有100多MB。

不过堆栈空间大小在创建线程的时候是可以设定的:

NSThread - 可以调用[[[NSThread alloc] init…] setStackSize:int] 来设置。

POSIX - 设置pthread_attr_t,并使用pthread_attr_setstacksize方法来设置。

Multiprocessing Services - 通过MPCreateTask,传递stackSize参数来设置。

总结一下,有关线程的操作包括:

  1. NSThread
  2. NSOperation,通过NSOperationQueue来调度。
  3. 使用GCD

至于NSTimer、idle Notification,以及异步调用,都属于异步调用的方法。我不认为是作为线程相关功能来总结了。

另外需要关注的是线程调度的问题。

首先是管理线程的结束。在线程结束时获取通知,如果采用轮询的方式非常浪费,我目前了解的至少有三种方法。

  1. 借助semaphore. cocoa提供了dispatch semaphore通信机制,可以实现wait和signal这样的操作。主线程对某个semaphore进行等待。在每个线程结束的时候,对semaphore调用signal,解锁主线程的等待。代码示例如下:
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    NSOperation * op1 = [[MyOperationObj alloc] initWithSema:sem];
    [queue  addOperation:op1];
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    NSLog(@"the operation is ended");
  2. 借助RunLoop。RunLoop是一个比较大的话题。详细的细节可以参考这里:http://www.cocoachina.com/iphonedev/sdk/2011/1111/3487.html. 至少比官方那绕口的语言好多了。基本上一看就明白了。代码示例:
    while(op1.ended){
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }
    
    //op1的main方法
    -(void)main{
            while(YES){
                    .....
            }
            //when end
            [self performSelectorOnMainThread:@selector(notify) withObject:nil waitUntilEnd:NO];
            //performSelector可以立即触发RunLoop事件处理。
    }

有关线程的数据传递,可以通过自定义端口的方式,或者借助NSThread threadDictionary来传递。

然后所以下NSOperation。这是一个内建的线程执行方法。需要定义一个类,集成NSOperation,然后将这个类的实例放入NSOperationQueue中。

@interface MyOp:NSOperation
@end

@implementation @MyOp
-(void)main{
        //body...
}
@end

//main 
NSOperationQueue * queue = [NSOperationQueue new];
MyOp * op = [MyOp new];
[queue addOperation:op];
[queue setMaxConcurrentOperationCount:2];//否则就都是同步按照放入顺序执行

[queue release];
[op release];

最后再说一下block,应该就是GCD支持的方式。

定义一个block:

void(^bblock)(void)=^(void){
        //body;
}

如果要在block中引用某个对象(或变量),这个对象(或变量)必须是声明为static 或者用__block进行了修饰。

__block int blockLocal = 100;
static int staticLocal = 100;

另外还有一个方法可以对block进行迭代,那就是block_apply.可以帮助你将循环放入多核支持中。

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

initData();

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

__block int sum = 0;
__block int *pArray = data;

// iterations
//
dispatch_apply(Length, queue, ^(size_t i) {
sum += pArray[i];
});

NSLog(@" >> sum: %d", sum);

dispatch_release(queue);

[pool drain];

定义好block以后,将它放入queue中进行执行

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, bblock);

block中不能修改