NSThread是苹果官方提供的,使用OC代码编写,使用起来比
pthread
更加面向对象,简单易用,可以直接操作线程对象,需要我们手动管理线程的生命周期。NSThread是一个基于pthreads
使用OC代码封装.
关于NSThread
相关API我这里会结合对应功能使用做些说明。详细的API说明可参考官方文档
NSThread的创建
使用NSThread
该类创建线程有两种方法:
- 使用
detachNewThreadSelector:toTarget:withObject:
class方法生成新线程。 - 创建一个新NSThread对象并调用其start方法。(仅在iOS和OS X v10.5及更高版本中受支持).
这两个方法都会在应用程序中创建一个分离的线程。线程退出时系统会自动回收线程的资源。
detachNewThreadSelector:toTarget:withObject:OS X的所有版本都支持该方法.提供要用作线程入口点的方法名称(选择器),定义该方法的对象以及要在启动时传递给线程的任何数据。
使用实例:
[NSThread detachNewThreadSelector:@selector(threadRun:) toTarget:self withObject:@"使用detachNewThreadSelector开启子线程"];
- (void)threadRun:(NSString *)param {
NSLog(@"----threadRun:%@ ----%@",[NSThread currentThread],param);
}
NSThread
在OS Xv10.5的更高及版本中初始化对象的简单方法可使用initWithTarget:selector:object:方法。但是,它不会启动该线程。要启动该线程,用start显式调用线程对象的方法
使用该initWithTarget:selector:object:方法的
还可以子类继承NSThread并覆盖其main
方法。用此方法的重写实现线程的入口更多操作。
使用实例
//1.alloc init 创建线程,需要手动启动 仅在iOS和OS X v10.5及更高版本中受支持
//创建一个新NSThread对象并调用其start方法,线程退出时系统会自动回收线程的资源
- (void)createNSThreadA {
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadRun:) object:@"使用initWithTarget开启NSThread子线程"];
[thread start];
}
第三种 还可以使用线程通信的方式,使用performselector
相关方法开启子线程
例如可以用performSelectorInBackground:withObject:开启后台线程。
使用实例
[self performSelectorInBackground:@selector(threadRun:) withObject:@"使用perform开始子线程"];
NSthread 线程一些属性说明
配置线程堆栈大小
默认情况下 子线程 堆栈大小512KB左右,iOS主线程1M左右空间大小,
子线线程允许的最小堆栈大小为8 KB,堆栈大小必须为4 KB的倍数。
子线程堆栈大小我们可以通过代码控制。
在iOS和OS X v10.5及更高版本中,分配并初始化NSThread对象(不要使用该detachNewThreadSelector:toTarget:withObject:
方法)。在调用start
线程对象的方法之前使用setStackSize:方法指定新的堆栈大小。在线程启动后设置堆栈大小会更改属性大小,但不会影响为线程预留的实际堆栈大小。
如果设置setStackSize:小于8KB或者不是4KB倍数,系统都会抛出类似异常:
it must be a multiple of the system page size and greater than 8192
配置线程局部存储键值对
每个线程都维护了一个键值对字典,可以再线程任何位置访问。可以使用
NSTread
对象属性threadDictionary存储要在整个线程执行期间保留的信息。
设置线程优先级
使用
NSThread
的类方法setThreadPriority:;
传入的参数是double
类型需要在0.0~1.0范围之间,默认是0.5
.线程优先级越高,CPU调度的该线程的频率会越高。优先级较高的线程比具有较低优先级的线程更可能运行。较高优先级并不能保证线程的特定执行时间,只是与较低优先级的线程相比,调度程序更有可能选择它。
NSThread线程生命周期
相关函数
启动线程 进入就绪->运行状态。任务执行完毕自行销毁
-(void)start
阻塞线程,进入阻塞状态
+ (void)sleepUntilDate:(NSDate *)date
+ (void)sleepForTimeInterval:(NSTimeInterval)ti
更改接收器的取消状态以指示它应该退出.
- (void)cancel
取消线程并不会马上停止并退出线程,仅仅用作(线程是否需要退出)状态记录
然后通过调用@property(readonly, getter=isCancelled) BOOL cancelled获取是否取消的状态然后做相关操作。
终止当前线程
+(void)exit
建议不要使用此方法。杀死一个线程可以防止该线程自行清理。线程分配的内存可能会被泄露,并且线程当前正在使用的任何其他资源可能无法正确清理,从而产生潜在问题。
要在操作过程中终止线程,一开始就设计线程以响应取消或退出消息。调用- (void)cancel。再根@property(readonly, getter=isCancelled) BOOL cancelled状态来是否调用+(void)exit。这样线程将有机会执行任何所需的清理并正常退出。
还可以通过运行循环输入源来控制线程是否退出.这个涉及到runloop,后面我会研究。有兴趣可先看官方文档说明。
其实取消终止线程还有个快捷方法,如果使用detachNewThreadSelector:toTarget:withObject:
class方法生成新线程。可以直接使用类方法+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument
快速取消一个线程。
线程安全和同步
如果多个线程访问统一资源,修改相同资源的两个线程可能会以非预期的方式相互干扰。例如,一个线程可能会覆盖另一个线程的更改,或者将应用程序置于未知且可能无效的状态。这个时候我们就需要同步工具来是线程同步,确保它们在交互时安全地进行交互。是多条线程按顺序的访问同一块资源。
线程同步有以下几种方式:
原子操作
原子操作是一种简单的同步形式,适用于简单的数据类型。原子操作的优点是它们不会阻止竞争线程。对于简单的操作,例如递增计数器变量,这可以带来比获取锁定更好的性能.可以对32位或64位值执行简单的数学和逻辑运算。这些操作依赖于特殊的硬件指令,以确保在再次访问受影响的内存之前完成给定的操作.使用需要导入头文件
.
相关API参考atomic。
这里不做过多说明。
锁
锁是iOS 最常用的同步工具之一。
iOS 锁大概有以下几种类型:
- 互斥锁
- 递归锁
- 读写锁(共享独占锁)
- 分布式锁
- 自旋锁
- 双重锁
这里就不过多讨论。后面我会研究锁相关的东西。
感兴趣的可以看看其他人的文章iOS开发中的11种锁以及性能对比
这里我就@synchronized
这个互斥锁做下简单说明和使用实例
@synchronized
是在Objective-C代码中动态创建互斥锁的便捷方式
使用如下:
@synchronized(Obj)
{
//大括号之间的所有内容都受@synchronized指令保护。
}
传递给@synchronized
指令的对象obj
是用于区分受保护块的唯一标识符。如果在两个不同的线程中执行上述方法,则obj
在每个线程上为参数传递一个不同的对象,每个线程都会锁定并继续处理,而不会被另一个阻塞。但是,如果在两种情况下都传递相同的对象,则其中一个线程将首先获取锁定,另一个线程将阻塞,直到第一个线程完成锁定的部分。
这里就用常用的一个例子购买车票为例子说明线程安全与同步问题。
- (void)createNSThreadD {
//全局变量总票数
totalCount = 100;
NSThread *threadA = [[NSThread alloc] initWithTarget:self selector:@selector(sellingTickets) object:nil];
threadA.name = @"售票员A";
[threadA start];
NSThread * threadB = [[NSThread alloc] initWithTarget:self selector:@selector(sellingTickets) object:nil];
threadB.name = @"售票员B";
[threadB start];
NSThread *threadC = [[NSThread alloc] initWithTarget:self selector:@selector(sellingTickets) object:nil];
threadC.name = @"售票员C";
[threadC start];
}
如果sellingTickets
方法类不加锁
- (void)sellingTickets {
while (1) {
NSInteger currentCount = totalCount;
if (currentCount>0) {
//模拟耗时操作
for (NSInteger i = 0; i<1000000; i++) {
}
totalCount = currentCount-1;
NSLog(@"售票员%@售出一张票剩余%ld张票",[NSThread currentThread].name,totalCount);
}else {
NSLog(@"%@当前票已经售完",[NSThread currentThread].name);
break;
}
}
}
会看到如下同一时刻不同售票员售出查询余票有冲突结果:
使用了@synchronized
锁后完全正常
- (void)sellingTickets {
while (1) {
@synchronized (self) {
NSInteger currentCount = totalCount;
if (currentCount>0) {
//模拟耗时操作
for (NSInteger i = 0; i<1000000; i++) {
}
totalCount = currentCount-1;
NSLog(@"售票员%@售出一张票剩余%ld张票",[NSThread currentThread].name,totalCount);
}else {
NSLog(@"%@当前票已经售完",[NSThread currentThread].name);
break;
}
}
}
}
使用条件
条件是另一种类型的信号量,它允许线程在某个条件为真时相互发信号。条件通常用于指示资源的可用性或确保以特定顺序执行任务。这里先不做过多说明。我的上一篇文章iOS多线程学习(一)pthread对条件作了简单说明。
NSObject执行选择器
这个我们最熟悉,使用NSobject
的performSelector
相关函数,用线程间通信实现实现线程间同步。
参考:
Threading Programming Guide
Concurrent Programming: APIs and Challenges
上一篇: