ios 多线程的故事2

上文我们简单的叙述了多线程,那么这篇我们就详细的说一下!

多线程技术方案

多线程技术方案

PThread

导入头文件

#import <pthread.h>

创建线程

/**

参数

1. 指向线程标识符的指针,C 语言中类型的结尾通常 _t/Ref,而且不需要使用 *

2. 线程属性

3. 线程调用的函数

void * 返回类型

(*) 函数指针

void * 参数类型

4. 运行函数的参数

返回值

- 若线程创建成功,则返回 0

- 若线程创建失败,则返回出错编号

*/

pthread_t threadId =NULL;

char * name ="zhangsan";

NSString * ocName =@"lisi";

int age =19;

// 1> 传递 C 语言字符串

//    int result = pthread_create(&threadId, NULL, demo, name);

// 2> 传递 OC 字符串

//    int result = pthread_create(&threadId, NULL, demo, (__bridge void *)(ocName));

// 3> 传递基本数据类型intresult = pthread_create(&threadId,NULL, demo, &age);

if(result ==0) {

NSLog(@"成功");   

}else{

NSLog(@"失败");   

}

线程调用函数

/// 线程调用函数

void * demo(void* param) {

//    NSString *name = (__bridge NSString *)(param);

//    NSLog(@"%@ %@", [NSThread currentThread], name);

int * age = param;

NSLog(@"%@ %d", [NSThreadcurrentThread], *age);

return  NULL;

}

小结

1.  在 C 语言中,没有对象的概念,对象是以结构体的方式来实现的

2.  通常,在 C 语言框架中,对象类型以_t/Ref结尾,而且声明时不需要使用*

3.  C 语言中的void *和 OC 中的id是类似的

4.  内存管理

1)  在 OC 中,如果是ARC开发,编译器会在编译时,根据代码结构,自动添加retain/release/autorelease

2)  但是,ARC 只负责管理 OC 部分的内存管理,而不负责 C 语言 代码的内存管理

3)  因此,开发过程中,如果使用的 C 语言框架出现retain/create/copy/new等字样的函数,大多都需要release,否则会出现内存泄漏

4.  在混合开发时,如果在 C 和 OC 之间传递数据,需要使用__bridge进行桥接,桥接的目的就是为了告诉编译器如何管理内存

1)  桥接的添加可以借助 Xcode 的辅助功能添加

2)  MRC 中不需要使用桥接

NSThread

创建线程的三种方式

准备线程执行方法

#pragma mark - 线程执行方法

- (void)longOperation:(id)param {

NSLog(@"%@ - %@", [NSThread currentThread], param);

}

alloc/init创建线程

#pragma mark - 创建线程

/// 使用 alloc / init 创建线程

- (void)thread1 {

NSLog(@"before %@", [NSThread currentThread]);

NSThread*thread = [[NSThread alloc] initWithTarget:selfselector:@selector(longOperation:) object:@"hello"]; 

  [thread start];

NSLog(@"after %@", [NSThread currentThread]);

}

代码小结

1.  [thread start];执行后,会在另外一个线程执行longOperation:方法

2.  在 OC 中,任何一个方法的代码都是从上向下顺序执行的

3.  同一个方法内的代码,都是在相同线程执行的(block除外)

使用类方法创建线程

/// 使用 NSThread 类方法

- (void)thread2 {    [NSThreaddetachNewThreadSelector:@selector(longOperation:) toTarget:self withObject:@(__FUNCTION__)];}

注: detachNewThreadSelector类方法不需要启动,会自动创建线程并执行@selector方法

使用 NSObject 分类方法创建线程

/// 使用 NSObject 分类方法

- (void)thread3 {

   [self performSelectorInBackground:@selector(longOperation:)   withObject:@(__FUNCTION__)];

}

代码小结

1.  performSelectorInBackground是NSObject的分类方法

2.  会自动在后台线程执行@selector方法

3.  没有thread字眼,隐式创建并启动线程

4.  所有NSObject都可以使用此方法,在其他线程执行方法

5.  Swift中不支持

NSThread 的 Target

NSThread的实例化方法中的target指的是开启线程后,在线程中执行哪一个对象的@selector方法

准备对象

@interface Person : NSObject

- (void)run;

@end

@implementation Person

- (void)run {

NSLog(@"跑了 5 分钟 %@", [NSThread currentThread]);

}

@end

异步执行对象方法

// 1. 创建 Person 对象

Person *person = [Person new];

// 2. 在异步执行对象方法

// 1> 方式 1

NSThread * t = [[NSThread alloc] initWithTarget:person selector:@selector(run) object:nil];   

[t start];

// 2> 方式2

[NSThread detachNewThreadSelector:@selector(run) toTarget:person withObject:nil];

// 3> 方式3

[person performSelectorInBackground:@selector(run) withObject:nil];

代码小结

1.  通过指定不同的target会在后台线程执行该对象的@selector方法

2.  提示:不要看见target就写self

3.  performSelectorInBackground可以让方便地在后台线程执行任意NSObject对象的方法

线程的状态

线程的状态

状态说明

新建

1.  实例化线程对象

就绪

1.  向线程对象发送start消息,线程对象被加入可调度线程池等待 CPU 调度

2.  detach方法和performSelectorInBackground方法会直接实例化一个线程对象并加入可调度线程池

运行

1.  CPU负责调度可调度线程池中线程的执行

2.  线程执行完成之前,状态可能会在就绪和运行之间来回切换

3.  就绪和运行之间的状态变化由 CPU 负责,程序员不能干预

阻塞

1.  当满足某个预定条件时,可以使用休眠或锁阻塞线程执行

2.  sleepForTimeInterval:休眠指定时长

3.  sleepUntilDate:休眠到指定日期

4.  @synchronized(self):互斥锁

死亡

1.  正常死亡

2.  线程执行完毕

3.  非正常死亡

4.  当满足某个条件后,在线程内部中止执行

5.  当满足某个条件后,在主线程中止线程对象

控制线程状态的方法

启动

1.  [_thread start]

    1)   线程进入就绪状态,当线程执行完毕后自动进入死亡状态

休眠

1.  方法执行过程中,符合某一条件时,可以利用sleep方法让线程进入阻塞状态

    1)  sleepForTimeInterval从现在起睡多少秒

   2)  sleepUntilDate从现在起睡到指定的日期

死亡

1.  [NSThread exit]

    1)  一旦强行终止线程,后续的所有代码都不会被执行

   2)  注意:在终止线程之前,应该注意释放之前分配的对象!

取消

1.  [_thread cancel]

1)  并不会直接取消线程

2)  只是给线程对象添加isCancelled标记

3)  需要在线程内部的关键代码位置,增加判断,决定是否取消当前线程

代码演练

定义成员变量

@implementation ViewController{

NSThread*_thread;

}

实现touch方法

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event {

// 跟踪线程状态

NSLog(@"%@ %zd %zd %zd", _thread, _thread.isFinished, _thread.isCancelled, _thread.isExecuting);

if(_thread ==nil|| _thread.isFinished|| _thread.isCancelled) {

// 1. 实例化线程

_thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];

// 2. 启动线程,添加到可调度线程池

[_thread start];

    }

}

实现调度方法,测试阻塞线程执行

- (void)demo {NSLog(@"开始");// 1. 阻塞到指定日期[NSThreadsleepUntilDate:[NSDatedateWithTimeIntervalSinceNow:1]];for(NSIntegeri =0; i <10; i++) {NSLog(@"%zd %@", i, [NSThreadcurrentThread]);// 1> 阻塞指定时长[NSThreadsleepForTimeInterval:1];    }NSLog(@"over");}

在线程内部直接终止线程

- (void)demo {

NSLog(@"开始");

// 1. 阻塞到指定日期

[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];

for(NSInteger i =0; i <10; i++) {

NSLog(@"%zd %@", i, [NSThread currentThread]);

// 1> 阻塞指定时长

[NSThread sleepForTimeInterval:1];

// 2> 当满足某一个条件后,直接终止线程,线程终止后,不会执行后续代码

if(i ==5) {     

      [NSThread exit];   

    }   

}

NSLog(@"over");

}

在外部终止线程

修改touch方法

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event {

// 跟踪线程状态

NSLog(@"%@ %zd %zd %zd", _thread, _thread.isFinished, _thread.isCancelled, _thread.isExecuting);

if(_thread ==nil|| _thread.isFinished|| _thread.isCancelled) {

// 1. 实例化线程_thread = [[NSThreadalloc] initWithTarget:selfselector:@selector(demo) object:nil];

// 2. 启动线程,添加到可调度线程池

[_thread start]; 

  }else{

// 线程执行中,直接终止

[_thread cancel]; 

  }

}

运行测试,线程并不会被终止

给线程对象发送cancel消息,只是给线程对象增加的一个标记

是否终止需要在线程调用的代码内部处理

修改demo函数

- (void)demo {

NSLog(@"开始");

// 1. 阻塞到指定日期

[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];

if([NSThread currentThread].isCancelled) {

NSLog(@"被取消");

return;   

}

for(NSInteger i =0; i <10; i++) {

NSLog(@"%zd %@", i, [NSThread currentThread]);

if([NSThread currentThread].isCancelled) {

NSLog(@"被取消");

return;

        }

// 1> 阻塞指定时长

[NSThread sleepForTimeInterval:1];

// 2> 当满足某一个条件后,直接终止线程,线程终止后,不会执行后续代码

if(i ==5) {

            [NSThreadexit];

      }

    }

NSLog(@"over");

}

线程的属性

常用属性

1.  name- 线程名称

1). 设置线程名称可以当线程执行的方法内部出现异常时,记录异常和当前线程

2.  stackSize- 栈区大小

1). 默认情况下,无论是主线程还是子线程,栈区大小都是512K

2). 栈区大小可以设置[NSThread currentThread].stackSize = 1024 * 1024;

3). 必须是4KB的倍数

3.  isMainThread- 是否主线程

4.  threadPriority- 线程优先级

1). 优先级,是一个浮点数,取值范围从0~1.0

2). 1.0表示优先级最高

3). 0.0表示优先级最低

4). 默认优先级是0.5

5). 优先级高只是保证 CPU 调度的可能性会高

5.  qualityOfService- 服务质量(iOS 8.0 推出)

1). NSQualityOfServiceUserInitiated- 用户需要

2). NSQualityOfServiceUtility- 实用工具,用户不需要立即得到结果

3). NSQualityOfServiceBackground- 后台

4). NSQualityOfServiceDefault- 默认,介于用户需要和实用工具之间

5). NSQualityOfServiceUserInteractive- 用户交互,例如绘图或者处理用户事件

关于优先级和服务质量

1.  多线程的目的:是将耗时的操作放在后台,不阻塞主线程和用户的交互!

2.  多线程开发的原则:简单

3.  在开发时,最好不要修改优先级,不要相信用户交互服务质量

4.  内核调度算法在决定该运行哪个线程时,会把线程的优先级作为考量因素

1). 较高优先级的线程会比较低优先级的线程具有更多的运行机会

2).较高优先级不保证你的线程具体执行的时间,只是相比较低优先级的线程,更有可能被调度器选择执行而已

代码

- (void)viewDidLoad { 

  [superview DidLoad];

if([NSThread currentThread].isMainThread){

NSLog(@"主线程 %zd K", [NSThread currentThread].stackSize/1024); 

  }

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

// 指定线程名称,便于调试

t1.name=@"thread 001";

// 指定线程优先级,CPU 会增加改线程的调度频率

//    t1.threadPriority = 1;

// 指定线程服务质量

t1.qualityOfService=NSQualityOfServiceUserInitiated;

    [t1 start];

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

    t2.name=@"thread 002";

    t2.threadPriority=0;

    t2.qualityOfService=NSQualityOfServiceBackground;

    [t2 start];

}

- (void)demo {

for(NSInteger i =0; i <10; i++) {

NSLog(@"%@ %zd K", [NSThread currentThread], [NSThread currentThread].stackSize/1024);

    }

// 模拟线程崩溃

if(![NSThread currentThread].isMainThread) {

int i =10;

int x =0;

NSLog(@"%zd", i / x);

    }

}

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

推荐阅读更多精彩内容