多线程编程--NSThread

每个iOS应用程序都有个专门用来更新显示UI界面、处理用户的触摸事件的主线程,因此不能将其他太耗时的操作放在主线程中执行,不然会造成主线程堵塞(出现卡机现象),带来极坏的用户体验。一般的解决方案就是将那些耗时的操作放到另外一个线程中去执行,多线程编程是防止主线程堵塞,增加运行效率的最佳方法

iOS支持多个层次的多线程编程,层次越高的抽象程度越高,使用也越方便,也是苹果最推荐使用的方法。下面根据抽象层次从低到高依次列出iOS所支持的多线程编程方法:

1.Thread :是三种方法里面相对轻量级的,但需要管理线程的生命周期、同步、加锁问题,这会导致一定的性能开销
2.Cocoa Operations:是基于OC实现的,NSOperation以面向对象的方式封装了需要执行的操作,不必关心线程管理、同步等问题。NSOperation是一个抽象基类,iOS提供了两种默认实现:NSInvocationOperation和NSBlockOperation,当然也可以自定义NSOperation
3.Grand Central Dispatch(简称GCD,iOS4才开始支持):提供了一些新特性、运行库来支持多核并行编程,它的关注点更高:如何在多个cpu上提升效率


一.优缺点

1.优点:NSThread比其他两种多线程方案较轻量级,更直观地控制线程对象

2.缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销

二.线程的创建与启动

1.实例化启动

- (id)init; // designated initializer
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument;
<p class="p1"><span class="s1">栈空间是用来存储为线程创建的本地变量的,栈空间的大小必须在线程的创建之前设定</span></p><p class="p1"><span class="s1">在调用NSThread的</span><span class="s2">start</span><span class="s1">方法之前通过</span><span class="s2">setStackSize:</span><span class="s1"> 设定新的栈空间大小。</span></p>
[thread start];  
2.静态方法

+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;  
调用后会马上创建线程并启动

3.隐式创建线程

[self performSelectorInBackground:@selector(run) withObject:nil]
三.获取线程

1.获取当前线程

NSThread *current = [NSThread currentThread]; 
多线程调用单例方法,在代理回调中通过获取当前线程区分

2.获取主线程

NSThread *main = [NSThread mainThread];  
[[NSThread currentThread] isMainThread];
四.暂停当前线程

// 暂停2s  
[NSThread sleepForTimeInterval:4]; 
NSDate *date = [NSDate dateWithTimeInterval:4 sinceDate:[NSDate date]];  
[NSThread sleepUntilDate:date]; 


五.线程间的通信
1.在主线程上

[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES]; 
2.在当前线程上

[self performSelector:@selector(run) withObject:nil];  
3.在指定线程上

[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES];
六.线程名字、属性、优先级

@property (nullable, copy) NSString *name;
  设置名字后可在后续中通过currentThread区分线程。 通过NSThread的currentThread可以取得当前操作的线程,其中会记录线程名称name和编号number,需要注意主线程编号永远为1。

@property (readonly, retain) NSMutableDictionary *threadDictionary;

每个线程都维护一个在线程任何地方都能获取的字典。 我们可以使用NSThread的threadDictionary方法获取一个NSMutableDictionary对象,然后添加我们需要的字段和数据。

@property double threadPriority;
    线程优先级范围为0~1,值越大优先级越高,每个线程的优先级默认为0.5,
七.线程状态

    1.线程状态分为isExecuting(正在执行)、isFinished(已经完成)、isCancellled(已经取消)三种。其中取消状态程序可以干预设置,只要调用线程的cancel方法即可。但是需要注意在主线程中仅仅能设置线程状态,并不能真正停止当前线程,如果要终止线程必须在线程中调用exist方法,这是一个静态方法,调用该方法可以退出当前线程。
    2.使用NSThread在进行多线程开发过程中操作比较简单,但是要控制线程执行顺序并不容易(前面万不得已采用了休眠的方法),另外在这个过程中如果打印线程会发现循环几次就创建了几个线程,这在实际开发过程中是不得不考虑的问题,因为每个线程的创建也是相当占用系统开销的。
八.
完成线程入口
1.创建Autorelease Pool

   在线程的入口处我们需要创建一个Autorelease Pool,当线程退出的时候释放这个Autorelease Pool。这样在线程中创建的autorelease对象就可以在线程结束的时候释放,避免过多的延迟释放造成程序占用过多的内存。如果是一个长寿命的线程的话,应该创建更多的Autorelease Pool来达到这个目的。例如线程中用到了run loop的时候,每一次的迭代都需要创建Autorelease Pool。在非ARC的情况下创建Autorelease Pool代码如下:

- (void)myThreadMainRoutine {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool
    // Do thread work here.
    [pool release];  // Release the objects in the pool.
}

在ARC环境的代码如下:

- (void)myThreadMainRoutine {
    @autoreleasepool
    {
        //do thread task
    }
}
2. 设置Run Loop

  当创建线程的时候我们有两种选择,一种是线程执行一个很长的任务然后在任务结束的时候退出。另外一种是线程可以进入一个循环,然后处理动态到达的任务。每一个线程默认都有一个NSRunloop,主线程是默认开启的,其他线程要手动开启。

3. detached

  通过NSThread创建的线程是非关联线程(detached thread),即父线程和子线程没有执行依赖关系,父线程结束并不意味子线程结束。如果想要创建joinable线程,只能通过POSIX线程接口创建。

4. (可选)设置Run Loop

  如果子线程只是做个一次性的操作,那么无需设置Run Loop;如果子线程进入一个循环需要不断处理一些事件,那么设置一个Run Loop是最好的处理方式,如果需要Timer,那么Run Loop就是必须的。

  如果需要在子线程运行的时候让子线程结束操作,子线程每次Run Loop迭代中检查相应的标志位来判断是否还需要继续执行,可以使用threadDictionary以及设置Input Source的方式来通知这个子线程。那么什么是Run Loop呢?这是涉及NSThread及线程相关的编程时无法回避的一个问题。




猜你喜欢

转载自blog.csdn.net/liqun3yue25/article/details/51082646
今日推荐