iOS多线程学习二NSTread

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);
}

NSThreadOS 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线程生命周期
1786624-91d3315082af3dd5.png
相关函数

启动线程 进入就绪->运行状态。任务执行完毕自行销毁

-(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位值执行简单的数学和逻辑运算。这些操作依赖于特殊的硬件指令,以确保在再次访问受影响的内存之前完成给定的操作.使用需要导入头文件<libkern/OSAtomic.h>.
相关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;
            }
    }
}

会看到如下同一时刻不同售票员售出查询余票有冲突结果:


1786624-aa28f6d029cccbc5.png
不加锁

使用了@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;
            }
        }
        
    }
}
1786624-4393fa9b3cf01c51.png
加锁之后
使用条件

条件是另一种类型的信号量,它允许线程在某个条件为真时相互发信号。条件通常用于指示资源的可用性或确保以特定顺序执行任务。这里先不做过多说明。我的上一篇文章iOS多线程学习(一)pthread对条件作了简单说明。

NSObject执行选择器

这个我们最熟悉,使用NSobjectperformSelector相关函数,用线程间通信实现实现线程间同步。

1786624-a1e86cdff9eb815d.png

参考:
Threading Programming Guide
Concurrent Programming: APIs and Challenges
上一篇:

iOS多线程学习(一)pthread

猜你喜欢

转载自blog.csdn.net/weixin_34007879/article/details/87095816