NSThread 基础

NSThread 是 Objective-C 对 pthread 的一个封装。通过封装,在 Cocoa 环境中,可以让代码看起来更加亲切。
NSThread是基于线程使用,轻量级的多线程编程方法(相对GCD和NSOperation),一个NSThread对象代表一个线程,需要手动管理线程的生命周期,处理线程同步等问题。

实现多线程的技术方案之一.
面向对象的开发思想.
每个对象表示一条线程.

优缺点

优点:NSThread比其他两种多线程方案较轻量级,更直观地控制线程对象
缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销

NSThread方法

线程创建

NSThread每一个对象代表着一个线程,NSThread的2种创建线程方法:

动态创建

NSThread * newThread = [[NSThread alloc]initWithTarget:self selector:@selector(threadRun) object:nil];

静态创建

[NSThread detachNewThreadSelector:@selector(threadRun) toTarget:self withObject:nil];

detach方法直接创建并启动一个线程去Selector,由于没有返回值,如果需要获取新创建的Thread,需要在执行的Selector中调用-[NSThread currentThread]获取

init方法初始化线程并返回,线程的入口函数由Selector传入。线程创建出来之后需要手动调用-start方法启动

线程操作

创建好线程之后当然需要对线程进行操作,NSThread给线程提供的主要操作方法有启动,睡眠,取消,退出

<li>启动</li>

我们使用init方法将线程创建出来之后,线程并不会立即运行,只有我们手动调用-start方法才会启动线程

 [newThread start];

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

<li>暂停</li>

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

[NSThread sleepForTimeInterval:1.0]; (以暂停一秒为例)
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];

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

sleepUntilDate:相当于执行一个sleep的任务。在执行过程中,即使有其他任务传入runloop,runloop也不会立即响应,必须sleep任务完成之后,才会响应其他任务
runUntilDate:虽然会阻塞线程,阻塞过程中并不妨碍新任务的执行。当有新任务的时候,会先执行接收到的新任务,新任务执行完之后,如果时间到了,再继续执行runUntilDate:之后的代码

<li>取消</li>

对于线程的取消,NSThread提供了一个取消的方法和一个属性

@property (readonly, getter=isCancelled) BOOL cancelled NS_AVAILABLE(10_5, 2_0);
 
- (void)cancel NS_AVAILABLE(10_5, 2_0);

调用取消方法

   [newThread cancel];

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

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

<li>退出</li>

与充满不确定性的-cancel相比,-exit函数可以让线程立即退出。

[NSThread exit];

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

<li>主线程和当前线程</li>

NSThread也提供了非常方便的获取和判断主线程的API

@property (readonly) BOOL isMainThread NS_AVAILABLE(10_5, 2_0);
+ (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0); // reports whether current thread is main
+ (NSThread *)mainThread NS_AVAILABLE(10_5, 2_0);

isMainThread:判断当前线程是否是主线程
mainThread:获取主线程的thread

使用-currentThread可以获取当前线程

+ (NSThread *)currentThread;

线程间通讯

<li>指定当前线程执行操作</li>

[self performSelector:@selector(threadRun)];
[self performSelector:@selector(threadRun) withObject:nil];
[self performSelector:@selector(threadRun) withObject:nil afterDelay:2.0];

<li>线程准备好之后,经常需要从主线程把耗时的任务丢给辅助线程,当任务完成之后辅助线程再把结果传回主线程传,这些线程通讯一般用的都是perform方法</li>

//①
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray *)array; 
//②
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait; 
 
//③
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray *)array NS_AVAILABLE(10_5, 2_0);
//④
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);

①:将selector丢给主线程执行,可以指定runloop mode

②:将selector丢给主线程执行,runloop mode默认为common mode

③:将selector丢个指定线程执行,可以指定runloop mode

④:将selector丢个指定线程执行,runloop mode默认为default mode

所以我们一般用③④方法将任务丢给辅助线程,任务执行完成之后再使用①②方法将结果传回主线程。

注意:

更新UI要在主线程中进行

perform方法只对拥有runloop的线程有效,如果创建的线程没有添加runloop,perform的selector将无法执行。

线程优先级

每个线程的紧急程度是不一样的,有的线程中任务你也许希望尽快执行,有的线程中任务也许并不是那么紧急,所以线程需要有优先级。优先级高线程中的任务会比优先级低的线程先执行

NSThread有4个优先级的API

+ (double)threadPriority;
+ (BOOL)setThreadPriority:(double)p;
 
@property double threadPriority NS_AVAILABLE(10_6, 4_0); // To be deprecated; use qualityOfService below
 
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0); // read-only after the thread is started

<ul>
<li> 前2个是类方法,用于设置和获取当前线程的优先级</li>

<li> threadPriority属性可以通过对象设置和获取优先级</li>

<li> 由于线程优先级是一个比较抽线的东西,没人能知道0.5和0.6到底有多大区别,所以iOS8之后新增了qualityOfService枚举属性,大家可以通过枚举值设置优先级</li>
</ul>

typedef NS_ENUM(NSInteger, NSQualityOfService) {
    NSQualityOfServiceUserInteractive = 0x21,
    NSQualityOfServiceUserInitiated = 0x19,
    NSQualityOfServiceDefault = -1
    NSQualityOfServiceUtility = 0x11,
    NSQualityOfServiceBackground = 0x09,
}

NSQualityOfService主要有5个枚举值,优先级别从高到低排布:

NSQualityOfServiceUserInteractive:最高优先级,主要用于提供交互UI的操作,比如处理点击事件,绘制图像到屏幕上

NSQualityOfServiceUserInitiated:次高优先级,主要用于执行需要立即返回的任务

NSQualityOfServiceDefault:默认优先级,当没有设置优先级的时候,线程默认优先级

NSQualityOfServiceUtility:普通优先级,主要用于不需要立即返回的任务

NSQualityOfServiceBackground:后台优先级,用于完全不紧急的任务

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

线程通知

NSThread有三个线程相关的通知

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

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

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

NSThreadWillExitNotification: 这个通知被发送到通知中心的时候, 并没有包含userinfo的字典信息, 并且这个通知是在进程执行exit方法的时候触发的. 你可以通过监听这个通知来处理一下进程即将结束之前的一些事情(就像遗言一样).

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

多线程的安全隐患

安全隐患 - 资源共享

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

比如多个线程访问同一个对象、同一个变量、同一个文件

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

如下图所示的线程安全隐患分析:

为了解决如图所示的问题,我们可以使用NSThread中的互斥锁技术,如下图所示:

安全隐患解决 – 互斥锁

<li>互斥锁使用格式</li>

@synchronized(锁对象) { // 需要锁定的代码  }

<li>互斥锁的优缺点</li>

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

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

<li>相关专业术语:线程同步</li>

线程同步的意思是:多条线程在同一条线上执行(按顺序地执行任务)

互斥锁,就是使用了线程同步技术

原子和非原子属性

<li>OC在定义属性时有nonatomic和atomic两种选择</li>

atomic:原子属性,为setter方法加锁(默认就是atomic)
nonatomic:非原子属性,不会为setter方法加锁

原子和非原子属性的选择

nonatomic和atomic对比

 atomic:线程安全,需要消耗大量的资源    
 nonatomic:非线程安全,适合内存小的移动设备

iOS开发的建议

  所有属性都声明为nonatomic
  尽量避免多线程抢夺同一块资源
  尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容

  • 父类实现深拷贝时,子类如何实现深度拷贝。父类没有实现深拷贝时,子类如何实现深度拷贝。• 深拷贝同浅拷贝的区别:浅拷...
    JonesCxy阅读 991评论 1 7
  • 1.介绍下内存的几大区域? 2.你是如何组件化解耦的? 3.runtime如何通过selector找到对应的IMP...
    小孩仔阅读 1,649评论 0 21
  • 转载:http://www.cocoachina.com/ios/20150601/11970.html RunL...
    Gatling阅读 1,436评论 0 13
  • 多线程是程序开发中非常基础的一个概念,大家在开发过程中应该或多或少用过相关的东西。同时这恰恰又是一个比较棘手的概念...
    小笨狼阅读 4,690评论 14 61
  • Runloop是iOS和OSX开发中非常基础的一个概念,从概念开始学习。 RunLoop的概念 -般说,一个线程一...
    小猫仔阅读 979评论 0 1