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线程生命周期
相关函数

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

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

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


不加锁

使用了@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执行选择器

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

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

iOS多线程学习(一)pthread
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355