线程Thread

来源:互联网 发布:java排序算法详解 编辑:程序博客网 时间:2024/05/06 03:19

根据Apple官方的定义:

The term thread is used to refer to a separate path of execution for code.

The term process is used to refer to a running executable, which can encompass multiple threads.

1、线程用于指代一个独立执行的代码路径。

2、进程用于指代一个可执行程序,他可以包含多个线程。

同步:一个人多个任务按顺序执行
异步:多个人同时执行多个任务
进程:系统中正在运行的应用程序;每个进程之间是独立的,每个进程都运行在其专用的且受保护的内存空间中,
线程:一个进程可以有多个线程组成,线程是进程的基本单元,一个进程的所有任务都在线程中执行(一个进程至少有一个线程)

NSThread:
优点:NSThread 比其他两个轻量级
缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销。

NSOperation :
优点:以面向对象的方式封装了用户需要执行的操作,我们只要聚焦于我们需要做的事情,不需要关心线程管理, 数据同步的事情,可以把精力放在自己需要执行的操作上。因为NSOperation已经为我们封装了这些事情。

Grand Central Dispatch :(全优点) iOS4 才开始支持,它提供了一些新的特性,以及运行库来支持多核并行编程,它的关注点更高:如何在多个CPU上提升效率。

多线程:
1.单核CPU同一时间只能处理一个线程;
2.CPU快速的在多个线程之间切换,造成同时执行的假象,每个线程执行15纳秒就会切换;
3.如果线程比较多,每个线程被调度的次数会降低,线程执行的效率也会降低
4.开启线程默认会占有一定的内存空间,默认每个线程都占512kb;
5.线程上的任务执行完成后,线程会自动销毁.

一个程序运行后,默认会开启一个主线程,主线程一般用来处理UI事件,刷新UI界面.
// 互斥锁/同步锁,使用了线程同步技术
// 特点 : 能够保证被锁定的代码,同一时间,只有一个线程可以操作
// 锁 都是会消耗性能的,因为有线程等待
// 锁的范围 : 数据的读写部分,范围要竟可能的小
// self : 锁对象,凡是继承自NSObject的对象,都是锁对象.因为内部都有一把锁,默认是开启的
// 锁对象必须是全局的,self是最方便的锁对象

资源共享:

1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源

当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题

解决办法互斥锁:

互斥锁使用格式
@synchronized(锁对象) { // 需要锁定的代码 }
只用一把锁,多锁是无效的

互斥锁的优缺点:

优点:能有效防止因多线程抢夺资源造成的数据安全问题
缺点:需要消耗大量的CPU资源

互斥锁的使用前提:多条线程抢夺同一块资源

iOS 8以前使用

thread1.threadPriority = 1.0;

//设置优先级,范围是0.0~1.0,最高是1.0.默认是0.5
// 决定的是线程有更多的机会被CPU调度执行,并不是决定的线程执行的先后顺序
这个方法的优先级的数值设置让人困惑,因为你不知道你应该设置多大的值是比较合适的,因此在iOS8之后,threadPriority添加了
一句注释:To be deprecated; use qualityOfService below
意思就是iOS 8以后推荐使用qualityOfService属性,通过量化的优先级枚举值来设置

qualityOfService的枚举值如下:

NSQualityOfServiceUserInteractive:最高优先级,用于用户交互事件,主要用于提供交互UI的操作,比如处理点击事件,绘制图像到屏幕上
NSQualityOfServiceUserInitiated:次高优先级,用于用户需要马上执行的事件,主要用于执行需要立即返回的任务
NSQualityOfServiceDefault:默认优先级,主线程和没有设置优先级的线程都默认为这个优先级,当没有设置优先级的时候,线程默认优先级
NSQualityOfServiceUtility:普通优先级,用于普通任务,主要用于不需要立即返回的任务
NSQualityOfServiceBackground:最低优先级,用于完全不紧急的任务,用于不重要的任务
比如给线程设置次高优先级:

[newThread setQualityOfService:NSQualityOfServiceUserInitiated];

这里要注意的是:部分线程属性需要在启动前设置,线程启动之后再设置会无效。如qualityOfService属性。

一般主线程和没有设置优先级的线程都是默认优先级。

NSThread有三个线程相关的通知

NSString * const NSWillBecomeMultiThreadedNotification;
NSString * const NSDidBecomeSingleThreadedNotification;
NSString * const NSThreadWillExitNotification;

NSWillBecomeMultiThreadedNotification:由当前线程派生出第一个其他线程时发送,一般一个线程只发送一次。
NSDidBecomeSingleThreadedNotification:这个通知目前没有实际意义,可以忽略
NSThreadWillExitNotification:线程退出之前发送这个通知。

由于线程的创建和销毁非常消耗性能,大多情况下,我们需要复用一个长期运行的线程来执行任务。

在线程启动之后会首先执行-threadMain,正常情况下threadMain方法执行结束之后,线程就会退出。为了线程可以长期复用接收消息,我们需要在threadMain中给thread添加runloop

  • (void)threadMain {
    [[NSThread currentThread] setName:@”myThread”]; // ①给线程设置名字
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; // ②给线程添加runloop
    [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; //③给runloop添加数据源
    while (![[NSThread currentThread] isCancelled]) { //④:检查isCancelled
    [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]; //⑤启动runloop
    }
    }
    ①:设置线程的名字,这一步不是必须的,主要是为了debug的时候更方便,可以直接看出这是哪个线程

②:自定义的线程默认是没有runloop的,调用-currentRunLoop,方法内部会为线程创建runloop

③:如果没有数据源,runloop会在启动之后会立刻退出。所以需要给runloop添加一个数据源,这里添加的是NSPort数据源

④:定期检查isCancelled,当外部调用-cancel方法将isCancelled置为YES的时候,线程可以退出

⑤:启动runloop

线程通讯

线程创建好了之后我们就可以给线程丢任务了,当我们有一个需要比较耗时的任务的时候,我们可以调用perform方法将task丢给这个线程。

[self performSelector:@selector(threadTask) onThread:self.thread withObject:nil waitUntilDone:NO]
结束线程

当我们想要结束线程的时候,我们可以使用CFRunLoopStop()配合-cancel来结束线程。

  • (void)cancelThread
    {
    [[NSThread currentThread] cancel];
    CFRunLoopStop(CFRunLoopGetCurrent());
    }
    不过这个方法必须在self.thread线程下调用。如果当前是主线程。可以perform到self.thread下调用这个方法结束线程

1.  - (void)cancel // 取消

    这个方法会将正在执行的当前进程信息保存给接收者,然后再将进程取消,同时会通过方法isCancled反馈状态,如果成功取消,isCancled将会返回YES,否则返回NO;
    注意设置为取消状态仅仅是改变了线程状态而言,并不能终止线程。

    进程被取消后,会调用exit方法;

不过大家千万不要被它的名字迷惑,调用-cancel方法并不会立刻取消线程,它仅仅是将cancelled属性设置为YES。cancelled也仅仅是一个用于记录状态的属性。线程取消的功能需要我们在main函数中自己实现

要实现取消的功能,我们需要自己在线程的main函数中定期检查isCancelled状态来判断线程是否需要退出,当isCancelled为YES的时候,我们手动退出。如果我们没有在main函数中检查isCancelled状态,那么调用-cancel将没有任何意义

  2.+ (void)sleepUntilDate:(NSDate *)aDate // 休眠进程

    这个方式NSThread的静态方法;它根据给定的时间(aDate)阻塞当前进程的执行,它实现的底层原理是阻塞了进程的runloop的运行,这样当前进程就无法获得执行的机会;

    与此类似的,还有一个静态方法:+ (void)sleepForTimeInterval:(NSTimeInterval)ti,它们的使用原理是一样的.

线程处理阻塞状态时在内存中的表现情况:(线程被移出可调度线程池,此时不可调度)。

NSThread提供了2个让线程睡眠的方法,一个是根据NSDate传入睡眠时间,一个是直接传入NSTimeInterval

1、+ (void)sleepUntilDate:(NSDate *)date;
2、+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

看到sleepUntilDate:大家可能会想起runloop的runUntilDate:。他们都有阻塞线程的效果,但是阻塞之后的行为又有不一样的地方,使用的时候,我们需要根据具体需求选择合适的API。

sleepUntilDate:相当于执行一个sleep的任务。在执行过程中,即使有其他任务传入runloop,runloop也不会立即响应,必须sleep任务完成之后,才会响应其他任务。

runUntilDate:虽然会阻塞线程,阻塞过程中并不妨碍新任务的执行。当有新任务的时候,会先执行接收到的新任务,新任务执行完之后,如果时间到了,再继续执行runUntilDate:之后的代码。

  3.+ (void)exit // 退出进程

    这个方法会调用NSThread的类方法 currentThread来访问当前进程,这样做的一个目的是为了让当前进程可以发送一个通知 (NSThreadWillExitNotification)给通知中心.

    这个方法可以手动调用,也可以等进程正常执行结束会自动调用.

    在手动调用这个进程时,一定要提前释放之前申请的资源;否则容易产生内存泄漏;
    
人死不能复生,线程死了也不能复生(重新开启),如果在线程死亡之后,再次点击屏幕尝试重新开启线程,则程序会挂。

-exit属于核弹级别终极API,调用之后会立即终止线程,即使任务还没有执行完成也会中断。这就非常有可能导致内存泄露等严重问题,所以一般不推荐使用。

对于有runloop的线程,可以使用CFRunLoopStop()结束runloop配合-cancel结束线程。

runloop启动的方法中run和runUntilDate:都无法使用CFRunLoopStop()退出,只有runMode:beforeDate:可以响应CFRunLoopStop(),所以要想使用CFRunLoopStop()退出runloop,必须使用runMode:beforeDate:启动。

新建线程对象——把线程添加到可调度线程池中,等待被CPU调度—运行—休眠(阻塞)(这里是指从可调度线程池中移除到内存中,并没有被销毁)—–回到可调度线程池,等待被调度—–运行——-退出.

  同时NSThread也提供了三个查询进程执行状态的方法:

  1.- (BOOL)isExecuting

    如果进程正在执行,这个方法返回YES,否则返回NO;

  2.- (BOOL)isFinished

    如果进程执行结束,返回YES,否则返回NO;

  3.- (BOOL)isCancelled

    如果进程被取消,返回YES,否则返回NO;

 在OC中,NSThread的状态变化,都会在内部使用消息通知机制来告知系统,并且NSThread只会发送三种消息到系统的通知中心:

  1.NSDidBecomeSingleThreadedNotification

    这个通知目前没有实际意义,苹果也仅仅是保留这个扩展通知而已,并没有在NSThread的方法中,来触发这个消息,不过根据这个通知的字面意思来理解,是进程又回到单一线程的    时候,会发送这个通知;但在多线程环境下, 这个没有什么实际的处理工作,可暂时忽略;

  2.NSThreadWillExitNotification

    这个通知被发送到通知中心的时候,并没有包含userinfo的字典信息(使用通知中心的伙伴们都应该知道userinfo滴,偶就不解释啦),并且这个通知是在进程执行exit方法的时候触发    的.你可以通过监听这个通知来处理一下进程即将结束之前的一些事情(就像遗言一样).

  3.NSWillBecomeMultiThreadedNotification

    这个通知只会被NSThread触发一次,条件是当第一个进程在调用了start或者detachNewThreadSelector:toTarget:withObject:方法.这个通知的接收者的一些处理方法都是在主    线程上进行的;这是因为这个通知是在系统生产新的子线程之前进行的,所以在监听这个通知的时候,调用监听者的通知方法都会在主线程进行.

其实NSThread提供的这些基本方法和状态通知,我们可以大体的了解进程一个生命周期,并且在这个生命周期中,进程经过了什么状态的转变,以及进程间简单的交互(使用通知中心),这对我们以后在使用GCD以及NSOperate的时候,能够更好的理解它们的内部实现基本原理.

常用的performSelector简单分析
1.
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

这三个方法,均为同步执行,与线程无关,主线程和子线程中均可调用成功。等同于直接调用该方法。在需要动态的去调用方法的时候去使用。
例如:[self performSelector:@selector(test2)];与[self test2];执行效果上完全相同。

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

这两个方法为异步执行,即使delay传参为0,仍为异步执行。只能在主线程中执行,在子线程中不会调到aSelector方法。可用于当点击UI中一个按钮会触发一个消耗系统性能的事件,在事件执行期间按钮会一直处于高亮状态,此时可以调用该方法去异步的处理该事件,就能避免上面的问题。
在方法未到执行时间之前,取消方法为:
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;
注意:调用该方法之前或在该方法所在的viewController生命周期结束的时候去调用取消函数,以确保不会引起内存泄露。

3.
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
这两个方法,在主线程和子线程中均可执行,均会调用主线程的aSelector方法;
如果设置wait为YES:等待当前线程执行完以后,主线程才会执行aSelector方法;
设置为NO:不等待当前线程执行完,就在主线程上执行aSelector方法。
如果,当前线程就是主线程,那么aSelector方法会马上执行。
注意:apple不允许程序员在主线程以外的线程中对ui进行操作,此时我们必须调用performSelectorOnMainThread函数在主线程中完成UI的更新。

4.
- (void)performSelector:(SEL)aSelector onThread:(NSThread )thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray )array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
调用指定线程中的某个方法。分析效果同3。

5.
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg
开启子线程在后台运行

总结:多线程就是进程的分支,每个独立的代码块都是一条线程。其多线程可以使得代码变得壮大。在多线程的情况下不会因为一条线程的崩溃而影响其它线程。同时也可以满足并发的功能,更加的提高了程序的运行速度很性能。

http://blog.csdn.net/hbblzjy/article/details/51565590

http://www.cocoachina.com/ios/20160225/15421.html

http://www.2cto.com/kf/201408/327231.html

http://www.cnblogs.com/start-ios/p/5236146.html

http://www.jianshu.com/p/0b0d9b1f1f19

0 0
原创粉丝点击