iOS多线程的初步研究(一、二、三)-- NSThread -- 锁-- NSRunLoop

    xiaoxiao2026-05-06  10

    iOS多线程的初步研究(一)-- NSThread

    对于多线程的开发,iOS系统提供了多种不同的接口,先谈谈iOS多线程最基础方面的使用。产生线程的方式姑且分两类,一类是显式调用,另一类是隐式调用。

    一、显示调用的类为NSThread。一般构造NSThread的线程对象可通过两种方式:

    1. 初始化线程主方法:

    [NSThread detachNewThreadSelector:@selector(run:) toTarget:target withObject:obj];//类方法

    NSThread *newThread = [[NSThread alloc] initWithTarget:target selector:@selector(run:) object:obj]; //实例方法可以拿到线程对象,便于以后终止线程。

    2. 定义NSThread的子类MyThread,然后实现main方法(即方法1中的run)。然后创建新对象:

    MyThread *newThread = [[MyThread alloc] init];

    启动线程:[newThread start];

    终止线程:实际上没有真正提供终止线程的api,但有个方法可以方便地利用cancel方法; 它是改变线程运行的一个状态标志,我们可以这样来利用:

    先在run:或main方法中这样实现线程循环:

    - (void)main

    {

        // thread init

        while (![[NSThread currentThreadisCancelled])

        {

            // thread loop

            [NSThread sleepForTimeInterval:1.0]; //等同于sleep(1);

        }

        // release resources of thread

    }

     这时如果调用[newThread cancel]; 就可以终止线程循环了。

     NSThread有个类方法exit是用于立即结束当前线程的运行(有点鲁莽),因为无法保证当前线程对资源的释放,所以不推荐使用。

     

    二、隐式调用

    通过NSObject的Category方法调用,罗列如下:

    - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait; //在主线程中运行方法,wait表示是否阻塞这个方法的调用,如果为YES则等待主线程中运行方法结束。一般可用于在子线程中调用UI方法。

    - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait; //在指定线程中执行,但该线程必须具备run loop。

    - (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg; //隐含产生新线程。

    三、NSThread的其它一些常用的方法

    创建的线程是非关联线程(detached thread),即父线程和子线程没有执行依赖关系,父线程结束并不意味子线程结束。

    1. + (NSThread *)currentThread; //获得当前线程

    2. + (void)sleepForTimeInterval:(NSTimeInterval)ti; //线程休眠

    3. + (NSThread *)mainThread; //主线程,亦即UI线程了

    4. - (BOOL)isMainThread; + (BOOL)isMainThread; //当前线程是否主线程

    5. - (BOOL)isExecuting; //线程是否正在运行

    6. - (BOOL)isFinished; //线程是否已结束

     

    四、一些非线程调用(NSObject的Category方法)

    即在当前线程执行,注意它们会阻塞当前线程(包括UI线程):

    - (id)performSelector:(SEL)aSelector;

    - (id)performSelector:(SEL)aSelector withObject:(id)object;

    - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

     以下调用在当前线程延迟执行,如果当前线程没有显式使用NSRunLoop或已退出就无法执行了,需要注意这点:

    - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;

    - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;

    而且它们可以被终止:

    + (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument;

    + (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;

     

    五、线程执行顺序

    通常UI需要显示网络数据时,可以简单地利用线程的执行顺序,避免显式的线程同步:

    1. UI线程调用

    [threadObj performSelectorInBackground:@selector(loadData) withObject:nil];

    2. 子线程中回调UI线程来更新UI

    - (void)loadData

    {

        //query data from network

        //update data model

        //callback UI thread

        [uiObj performSelectorOnMainThread:@selector(updateUI) withObject:nil waitUntilDone:YES];

    }

    也可以使用NSThread实现同样的功能,loadData相当于NSThread的main方法。

    二、iOS多线程的初步研究(二)-- 锁

    谈到线程同步,一般指如何对线程间共享数据的同步读写,如何避免混乱的读写结果。一个基本的解决办法就是使用锁(LOCK)。

    iOS提供多种同步锁的类和方法,这里介绍下基本用法。

     

    1. NSLock:最基本的同步锁,使用lock来获得锁,unlock释放锁。如果其它线程已经使用lock,这时lock方法会阻塞当前线程,直到其它线程调用unlock释放锁为止。NSLock锁一般用于简单的同步算法。比如生产者线程产生数据(produce),消费线程显示数据(consume),可以这样实现:

    - (void)produce

    {

      while (1)

      {

        [theLock lock];

        // create data

        [theLock unlock];

      }

    }

    - (void)consume

    {

      while (1)

      {

        if ([theLock tryLock])

        {

          // display data

          [theLock unlock];

        }

        sleep(1.0); //sleep a while

      }

    }

    NSLock的tryLock方法可以避免阻塞当前线程,如果不能获得锁则返回NO。也可使用:

    - (BOOL)lockBeforeDate:(NSDate *)limit;

    设置超时返回时间。

     

    2. NSConditionLock,即条件锁,可以设置自定义的条件来获得锁。比如上面的例子可以这样改用条件锁实现:

    - (void)produce

    {

      while (1)

      {

        [theLock lockWhenCondition:NO_DATA];

        // create data

        [theLock unlockWithCondition:HAS_DATA];

      }

    }

    - (void)consume

    {

      while (1)

      {

        if ([theLock tryLockWhenCondition:HAS_DATA])

        {

          // display data

          [theLock unlockWithCondition:NO_DATA];

        }

        sleep(1.0); //sleep a while

      }

    }

     

    3. NSCondition:条件(一种信号量),类似Java中的Condition,但有所不同的是NSCondition是锁和条件的组合实现。wait方法用于线程的等待(相当于Java Condition的await()),然后通过signal方法通知等待线程(相当于Java Condition的signal()),或者broadcast通知所有等待的线程相当于Java Condition的signalAll())。一个简单生产消费同步例子:

    - (void)produce

    {

      [theLock lock];

      // create data

      hasData = YES;

      [theLock signal]; //这时通知调用wait的线程结束等待并返回

      [theLock unlock];

    }

    - (void)consume

    {

      [theLock lock];

      while (!hasData)

        [theLock wait]; //注意:这时其它线程的lock调用会成功返回

      //display data

      hasData = NO;

      [theLock unlock];

    }

     

    4. NSRecursiveLock:递归锁,顾名思义一般用于递归程序。它可以让同一线程多次获得锁,解锁次数也必须相同,然后锁才能被其它线程获得。看下官网文档中的简单例子就能明白:

    void MyRecursiveFunction(int value) 

      [theLock lock]; 

      if (value != 0) 

      { 

        --value; 

        MyRecursiveFunction(value); 

      }

      [theLock unlock];

    }

    当然不只用于递归程序,类似Java中的ReentrantLock。

     

    5. @synchronized实现对象锁,类似Java中synchronized关键词。一般这样使用,但不多啰嗦了:

    @synchronized(anObj) 

    //......

    三、iOS多线程的初步研究(三)-- NSRunLoop

    弄清楚NSRunLoop确实需要花时间,这个类的概念和模式似乎是Apple的平台独有(iOS+MacOSX),很难彻底搞懂(iOS没开源,呜呜)。

    官网的解释是说run loop可以用于处理异步事件,很抽象的说法。不罗嗦,先看看NSRunLoop几个常用的方法。

    + (NSRunLoop *)currentRunLoop; //获得当前线程的run loop

    + (NSRunLoop *)mainRunLoop; //获得主线程的run loop

    - (void)run; //进入处理事件循环,如果没有事件则立刻返回。注意:主线程上调用这个方法会导致无法返回(进入无限循环,虽然不会阻塞主线程),因为主线程一般总是会有事件处理。

    - (void)runUntilDate:(NSDate *)limitDate; //同run方法,增加超时参数limitDate,避免进入无限循环。使用在UI线程(亦即主线程)上,可以达到暂停的效果。

    - (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate; //等待消息处理,好比在PC终端窗口上等待键盘输入。一旦有合适事件(mode相当于定义了事件的类型)被处理了,则立刻返回;类同run方法,如果没有事件处理也立刻返回;有否事件处理由返回布尔值判断。同样limitDate为超时参数。

    - (void)acceptInputForMode:(NSString *)mode beforeDate:(NSDate *)limitDate; //似乎和runMode:差不多(测试过是这种结果,但确定是否有其它特殊情况下的不同),没有BOOL返回值。

    官网文档也提到run和runUntilDate:会以NSDefaultRunLoopMode参数调用runMode:来处理事件。

    当app运行后,iOS系统已经帮助主线程启动一个run loop,而一般线程则需要手动来启动run loop。

    使用run loop的一个好处就是避免线程轮询的开销,run loop在无事件处理时可以自动进入睡眠状态,降低CPU的能耗。

    比如一般线程轮询的方式为:

    while (condition)

    {

      // waiting for new data

      sleep(1);

      // process current data

    }

    其实这种方式是很能消耗CPU时间片的,如果在UI线程中这样使用还会阻塞UI响应。而改用NSRunLoop来实现,则可大大改善线程的执行效率,而且不会阻塞UI(很神奇,呵呵。有点像javascript,用单线程实现多线程的效果)。上面的例子可以改为:

    while (condition)

    {

      // waiting for new data

      if ([[NSRunLoop currentRunLooprunMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]])

      {

        // process current data

      }

    }

     

    接下来我们看看具体的例子,包括如何实现线程执行的关联同步(join),以及UI线程run loop的一般使用技巧等。

    假设有个线程A,它会启动线程B,然后等待B线程的结束。NSThread是没有join的方法,用run loop方式实现就比较精巧。

    NSThread *A; //global

    A = [[NSThread alloc] initWithTarget:self selector:@selector(runA) object:nil]; //生成线程A

    [A start]; //启动线程A

    - (void)runA

    {

      [NSThread detachNewThreadSelector:@selector(runB) toTarget:self withObject:nil]; //生成线程B

      while (1)

      {

        if ([[NSRunLoop currentRunLooprunMode:@"CustomRunLoopMode" beforeDate:[NSDate distantFuture]]) //相当于join

        {

          NSLog(@"线程B结束");

          break;

        }

      }

    }

    - (void)runB

    {

      sleep(1);

      [self performSelector:@selector(setData) onThread:A withObject:nil waitUntilDone:YES modes:@[@"CustomRunLoopMode"]];

    }

    实际运行时,过1秒后线程A也会自动结束。这里用到自定义的mode,一般在UI线程上调用run loop会使用缺省的mode。结合while循环,UI线程就可以实现子线程的同步运行(具体例子这里不再描述,可参看:http://www.cnblogs.com/tangbinblog/archive/2012/12/07/2807088.html)。

    下面罗列调用主线程的run loop的各种方式,读者可以加深理解:

    [[NSRunLoop mainRunLoop] run]; //主线程永远等待,但让出主线程时间片

    [[NSRunLoop mainRunLooprunUntilDate:[NSDate distantFuture]]; //等同上面调用

    [[NSRunLoop mainRunLooprunUntilDate:[NSDate date]]; //立即返回

    [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10.0]]; //主线程等待,但让出主线程时间片,然后过10秒后返回

    [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]]; //主线程等待,但让出主线程时间片;有事件到达就返回,比如点击UI等。

    [[NSRunLoop mainRunLooprunMode:NSDefaultRunLoopMode beforeDate: [NSDate date]]; //立即返回

    [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate: [NSDate dateWithTimeIntervalSinceNow:10.0]]; //主线程等待,但让出主线程时间片;有事件到达就返回,如果没有则过10秒返回。

    相关资源:python入门教程(PDF版)
    最新回复(0)