多线程的学习Part1

前言:

多线程在开发中是时刻存在的,对于提高用户体验是至关重要的。本blog粗略的记录下NSThread,NSOperationQueue,GCD等多线程技术。

NSThread

使用NSThread创建线程有很多方法:

  • 类方法直接生成一个子线程
    + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;

  • 使用实例方法,然后调用start

    - (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument;
    
    NSThread *hardThread = [[NSThread alloc] initWithTarget:self selector:@selector(doHardWork) object:nil];
    [hardThread start];
    
  • 调用NSObject的- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg ;

    [self performSelectorInBackground:@selector(hardWork) withObject:nil];
    
  • 创建一个NSThread子类,然后调用子类实例的start方法。

    根据大神的blog,iOS下主要成本开销包括构造内核数据结构(大约1kb)、栈空间(子线程512kb、主线程1MB,可使用-setStackSize:自定义,注意必须是4k的倍数,而且最小是16k),创建线程大约需要90毫秒的创建时间。
    第二种和第四种可以使用- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;在该线程上执行方法。相对于NSPort(用于通讯)的麻烦设置来说简单方便。Apple建议使用这个接口运行的方法不要是耗时或者频繁的操作,以免子线程的负载过重。

如果需要,可以设置线程的优先级(-setThreadPriority:)如果要在线程中保存一些状态信息,还可以使用到-threadDictionary得到一个NSMutableDictionary,以key-value的方式保存信息用于线程内读写。

NSThread的入口方法

下面是大神的示例代码:

        - (void)threadRoutine
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    BOOL moreWorkToDo = YES;
    BOOL exitNow = NO;
    NSRunLoop* runLoop = [NSRunLoop currentRunLoop];

    NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];
    [threadDict setValue:[NSNumber numberWithBool:exitNow] forKey:@"ThreadShouldExitNow"];

     //添加事件源
    [self myInstallCustomInputSource];

    while (moreWorkToDo && !exitNow)
    {
        //执行线程真正的工作方法,如果完成了可以设置moreWorkToDo为False

        [runLoop runUntilDate:[NSDate date]];

        exitNow = [[threadDict valueForKey:@"ThreadShouldExitNow"] boolValue];
    }

    [pool release];
}
  • 大神说必须创建一个NSAutoreleasePool,因为子线程不会自动创建。同时如果线程中会大量创建autoreleased对象的话,则只有在该子线程退出时才回回收,就需要在NSRunloop每次迭代时创建和销毁一个NSAutoreleasePool。
  • 最好在子线程中设置一个异常处理函数,若子线程无法处理抛出的异常,会导致Crash。
  • (可选)设置RunLoop,如果子线程进入一个循环需要不断处理一些事情,那么设置一个RunLoop是最好的处理方式,如果需要Timer,那么RunLoop就是必须的。
  • 如果需要在子线程运行的时候让子线程结束操作,子线程每次RunLoop迭代中检查相应的标志位来判断是否还需要继续执行,可以使用threadDictionary以及设置InputSource的方式来通知这个子线程。

Run Loop

  • 每个线程都有一个Run Loop,主线程的Run Loop会在App运行时自动运行,子线程中需要手动运行.
  • 每个Run Loop都会以一个模式mode来运行,可以使用NSRunLoop的-(BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate方法运行在某个特定模式mode。
  • Run Loop的处理2大类事件源:Timer Source和Input Source(包括performSelector *)方法簇、Port或者自定义Input Source)、每个事件源都会绑定在Run Loop的某个特定模式mode上,而且只有RunLoop在这个模式运行的时候才会触发该Timer和Input Source。
  • 如果没有任何事件源添加到RunLoop上,Run Loop就会立刻exit。

Run Loop三个运行接口:

  • -(void)run;无条件运行

    不建议使用,该接口会导致Run Loop永久性的运行在NSDefaultRunLoopMode模式,即使使用CFRunLoopStop(runloopRef);也无法停止Run Loop的运行,那么这个子线程久无法停止,只能一直运行。
    
  • - (void)runUntilDate:(NSDate *)limitDate;有一个超时时间限制

    是运行在NSDefaultRunLoopMode模式,有个超时时间,可以控制没次Run Loop的运行时间。
    这个方法运行Run Loop一段时间会退出给你检查运行条件的机会。如果需要可以再次运行Run Loop。
    注意CFRunLoopStop(runloopRef);也无法停止Run Loop的运行,因此最好自己设置一个合理的Run Loop运行时间。
    示例:
        while (!Done)  
    {
        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate
            dateWithTimeIntervalSinceNow:10]];
        NSLog(@"exiting runloop.........:");
    }
    
  • - (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;
    这个接口在非Timer事件触发、显示的用CFRunLoopStop停止RunLoop、到达limitDate后会退出返回。如果仅是Timer事件触发并不会让Run Loop退出返回;如果仅是Timer事件触发并不会让Run Loop退出返回;如果是PerformSelector*事件或者其他Input Source事件触发处理后,runloop会退出返回YES。示例:

        while (!Done)  
    {
        BOOL ret = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                                    beforeDate:[NSDate distantFuture]];
        NSLog(@"exiting runloop.........: %d", ret);
    }
    

什么时候需要使用Port或者自定义InputSource与其他线程进行通讯。

  • 需要在线程中使用Timer。
  • 需要在线程上使用performSelector*方法。
  • 需要让线程执行周期性的工作。
Zerlz wechat
扫码关注一个很懒的程序员!
Winter is coming, give me a penny!