Bison眼中的iOS开发多线程是这样的(一)

allluckly.cn.jpg

不知道大家面试iOS软件工程师的时候有没有遇到问多线程的?反正我遇到的还是蛮多的。下面是我面试时候的一个小场景!有点不堪🙈,看完不许笑啊.....

面试官:你平常在开发中有用到多线程吗?

我:有!

面试官:那你说说你在开发的时候都有哪些场景用到多线程啊?

我:很多场景...(气氛瞬间有点不对劲了😓)

面试官:那你说说多线程都有哪些框架?

我:NSThreadNSOperationQueueGCD(很洋气的用英语讲出来😄)

面试官:说说你理解的三者的优缺点(气氛稍稍有点缓解)

我:(网上看到过就背下来了,心里有点小得意,滔滔不绝的说完如下话语)

1.NSThread:

使用NSThread对象建立一个线程非常方便;


但是要使用NSThread管理多个线程非常困难,不推荐使用;


技巧!使用[NSThread currentThread]跟踪任务所在线程,适用于这三种技术.


2.NSOperation/NSOperationQueue:

是使用GCD实现的一套Objective-C的API;


是面向对象的多线程技术;


提供了一些在GCD中不容易实现的特性,如:限制最大并发数量,操作之间的依赖关系.

3.GCD---Grand Central Dispatch:

是基于C语言的底层API;


用Block定义任务,使用起来非常灵活便捷;


提供了更多的控制能力以及操作队列中所不能使用的底层函数.

面试官:面无表情的说,回去等通知.....(结果..........大家懂的😂)

这就是Bison刚出道时眼中的多线程😄

这种局面,究根结底,是自己对底层的东西不够透彻只停留在怎么去使用它,而不知道底层是怎么实现的。下面让我很严肃的和大家说说多线程到底是一个什么样子的!

对于单线程的应用而言,整个应用只是一个顺序执行流,当执行到某个耗时操作时,主线程就会被阻塞,应用就卡在那无法继续执行,因此单线程的应用体验度很低,总感觉像手机卡似得,就像一条小河北阻塞了,只有打通了才能继续有水流到下一个地方放一样。而多线程则更像一条河有无数的分支,这条阻塞了还有其他的分支在运行,影响不到大局。希望我的比喻够恰当啊.......😄
iOS开发平台提供了非常优秀的多线程支持,程序可以通过很简单的方式来开启多线程,提供了我上述场景所说的多线程编程。总之iOS已经降低了开发多线程应用的繁琐,让开发者能轻松、简单的开发多线程应用。
几乎所有的操作系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行的程序就是一个进程。当一个程序运行时,内部可能包含了多个顺序执行流,每个执行流就是一个线程。在此就不得不说下进程和线程有什么区别了,很早以前我还是会混淆的哦😂。下面是我的理解....
仅供参考

当一个程序进入内存运行后,即变成了一个进程。进程是处于运行过程中的程序,并且具有一定的独立功能,是系统进行资源分配和调度的一个独立单位。


线程是进程的组成部分,一个进程可以拥有多个线程,而一个线程必须拥有一个父进程,线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不再拥有系统资源,与父进程中的其他线程共享该进程所拥有的全部资源。也因此多线程编程更加的方便;值得注意的是确保每个线程不会妨碍该进程的其他线程。

通过上面的啰嗦,我想大家对多线程都有一定的了解了。在此总结一下多线程编程的几个优点,如有遗漏,欢迎留言补充:

  • 进程间不能共享内存,但线程之间共享内存非常容易。(这是错误的,共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝,多谢网友@皮皮彭指正)
  • 系统创建进程需要为该进程重新分配系统资源,但创建线程则代价小的多,因此使用多线程来实现多任务并发比多进程的效率高。
  • iOS提供了多种多线程实现方式,从而简化了iOS的多线程编程。

接下来Bison将分别讲解iOS开发多线程中的用法

NSThread

iOS使用NSThread类代表线程,创建新线程也就是创建NSThread对象。
创建NSThread有俩种方式。

//创建一个新线程对象
 - (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument NS_AVAILABLE(10_5, 2_0);
//创建并启动新线程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;

上面俩种方式的本质都是将target对象的selector方法转换为线程执行体,其中selector方法最多可以接收一个参数,而argument就代表传给selector方法的参数。
这俩种创建新线程的方式并没有明显的区别,只是第一种方式是一个实例化方法,该方法返回一个NSThread对象,必须调用 start方法启动线程:另一种不会返回NSThread对象,因此这种方法会直接创建并启动新线程。
下面我们随便举个栗子,代码如下

- (void)viewDidLoad
{
    [super viewDidLoad];
    for(int i = 0 ; i < 100 ; i++)
    {
        NSLog(@"===%@===%d" , [NSThread currentThread].name , i);
        if(i == 7)
        {
            // 创建线程对象
            NSThread *thread = [[NSThread alloc]initWithTarget:self
                selector:@selector(run) object:nil];
            // 启动新线程
            [thread start];
            // 创建并启动新线程
//          [NSThread detachNewThreadSelector:@selector(run) toTarget:self
//              withObject:nil];
        }
    }
}
- (void)run
{
    for(int i = 0 ; i < 100 ; i++)
    {
        NSLog(@"-----%@----%d" , [NSThread currentThread].name, i);
    }
}

运行一下,打印日志如下:

//主线程正在运行
2016-01-12 14:30:46.400 NSThreadTest[7498:104912] ======0
2016-01-12 14:30:46.401 NSThreadTest[7498:104912] ======1
2016-01-12 14:30:46.401 NSThreadTest[7498:104912] ======2
2016-01-12 14:30:46.401 NSThreadTest[7498:104912] ======3
2016-01-12 14:30:46.401 NSThreadTest[7498:104912] ======4
2016-01-12 14:30:46.401 NSThreadTest[7498:104912] ======5
2016-01-12 14:30:46.401 NSThreadTest[7498:104912] ======6
2016-01-12 14:30:46.402 NSThreadTest[7498:104912] ======7
2016-01-12 14:30:46.402 NSThreadTest[7498:104912] ======8
//新线程开始运行
2016-01-12 14:30:46.402 NSThreadTest[7498:104956] ---------0
2016-01-12 14:30:46.402 NSThreadTest[7498:104912] ======9
2016-01-12 14:30:46.402 NSThreadTest[7498:104956] ---------1
2016-01-12 14:30:46.402 NSThreadTest[7498:104912] ======10
2016-01-12 14:30:46.402 NSThreadTest[7498:104956] ---------2
2016-01-12 14:30:46.402 NSThreadTest[7498:104912] ======11
2016-01-12 14:30:46.402 NSThreadTest[7498:104912] ======12
2016-01-12 14:30:46.402 NSThreadTest[7498:104956] ---------3
................

由上不难看出,这是俩个线程并发执行的效果。除此之外上面的🌰还用到了
+ (NSThread *)currentThread;方法,该方法总是返回当前正在执行的线程对象。

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。即使线程开始运行以后,它也不可能一直“霸占”着CPU独自运行,所以CPU需要再多个线程之间切换,于是线程状态也会多次在运行、就绪状态之间的切换。
当程序创建了一个线程之后,该线程就处于新建状态,此时它和其他Objective-C对象一样,仅仅由系统为其分配了内存,并初始化了其他成员变量的值。此时的线程对象没有表现出任何线程的动态特征,程序也不会执行线程的线程执行体。
当线程对象调用了start方法之后,该线程处于就绪状态,系统会为其创建方法调用栈和程序计数器,处于这种状态中的线程并没有开始运行,它只是表示该线程可以运行了。至于该线程何时运行,取决于系统的调度。

终止子线程

线程会以如下3中方式之一结束,结束后就处于死亡状态。

线程执行体方法执行完成,线程正常结束。


线程执行过程中出现了错误。


直接调用NSThread类的exit方法来终止当前正在执行的线程。

为了测试木个线程是否正在运行,可以调用线程对象的isExecutingisFinished方法,当线程正处于执行过程中时,调用isExecuting方法会返回YES,当线程执行完后,调用isFinished方法也返回YES。
如果希望在UI线程中终止子线程,NSThread并没有提供方法来终止某个子线程,虽然提供了cancel方法,但该方法仅仅只是改变该线程的状态,导致该线程的isCancelled方法返回NO,而不是真正终止该线程。
为了在UI线程中终止子线程,可以向子线程发送一个信号,然后在子线程的线程执行体方法中进行判断,如果子线程收到过终止信号,程序应该调用exit方法来终止当前正在执行的循环。下面举个🌰,代码如下:

NSThread* thread;
- (void)viewDidLoad
{
    [super viewDidLoad];
    // 创建新线程对象
    thread = [[NSThread alloc] initWithTarget:self selector:@selector(run)
        object:nil];
    // 启动新线程
    [thread start];
}
- (void)run
{
    for(int i = 0 ; i < 100 ; i++)
    {
        if([NSThread currentThread].isCancelled)
        {
            // 终止当前正在执行的线程
            [NSThread exit];
        }
        NSLog(@"-----%@----%d" , [NSThread currentThread].name, i);
        // 每执行一次,线程暂停0.5秒
        [NSThread sleepForTimeInterval:0.5];
    }
}
- (IBAction)cancelThread:(id)sender
{
    // 取消thread线程,调用该方法后,thread的isCancelled方法将会返回NO
    [thread cancel]; 
}

如果需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用如下方法。

//  让当前正在执行的线程暂停到date代表的时间,并进入阻塞状态。
+ (void)sleepUntilDate:(NSDate *)date;
//  让当前正在执行的线程暂停到ti秒,并进入阻塞状态。
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

每个线程执行时都具有一定的优先级,优先级高的线程获得较多的执行机会,而优先级的则反之。每个子线程默认的优先级是0.5.
NSThread通过如下代码演示优先级。

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSLog(@"UI线程的优先级为:%g" , [NSThread threadPriority]);
    // 创建第一个线程对象
    NSThread* thread1 = [[NSThread alloc]
        initWithTarget:self selector:@selector(run) object:nil];
    // 设置第一个线程对象的名字
    thread1.name = @"线程A";
    NSLog(@"线程A的优先级为:%g" , thread1.threadPriority);
    // 设置使用最低优先级
    thread1.threadPriority = 0.0;
    // 创建第二个线程对象
    NSThread* thread2 = [[NSThread alloc]
        initWithTarget:self selector:@selector(run) object:nil];
    // 设置第二个线程对象的名字
    thread2.name = @"线程B";
    NSLog(@"线程B的优先级为:%g" , thread2.threadPriority);
    // 设置使用最高优先级
    thread2.threadPriority = 1.0;
    // 启动2个线程
    [thread1 start];
    [thread2 start];
}
- (void)run
{
    for(int i = 0 ; i < 100 ; i++)
    {
        NSLog(@"-----%@----%d" , [NSThread currentThread].name, i);
    }
}

运行所得日志如下

2016-01-12 16:26:26.451 PriorityTest[10135:150983] UI线程的优先级为:0.5
//新线程默认优先级
2016-01-12 16:26:26.452 PriorityTest[10135:150983] 线程A的优先级为:0.5
2016-01-12 16:26:26.452 PriorityTest[10135:150983] 线程B的优先级为:0.5
//改变优先级后,优先级高的线程,获取更多的执行机会
2016-01-12 16:26:26.453 PriorityTest[10135:151058] -----线程B----0
2016-01-12 16:26:26.453 PriorityTest[10135:151058] -----线程B----1
2016-01-12 16:26:26.453 PriorityTest[10135:151058] -----线程B----2
2016-01-12 16:26:26.454 PriorityTest[10135:151058] -----线程B----3
2016-01-12 16:26:26.454 PriorityTest[10135:151058] -----线程B----4
2016-01-12 16:26:26.454 PriorityTest[10135:151058] -----线程B----5
2016-01-12 16:26:26.454 PriorityTest[10135:151058] -----线程B----6
2016-01-12 16:26:26.465 PriorityTest[10135:151056] -----线程A----0

今天暂时写到这吧....,有点长了😄

Bison眼中的iOS开发多线程是这样的(二)

博主app上线啦,快点此来围观吧

好文推荐:详解持久化Core Data框架的原理以及使用---转自Bison的技术博客

原文地址:http://allluckly.cn

如对你有帮助,请不要吝惜你的star和喜欢哦!

推荐一款学习iOS开发的app_____|______| | 传送门

技术交流群:534926022(免费) 511040024(0.8/人付费)

版权归©Bison所有 如需转载请保留原文超链接地址!否则后果自负!

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

推荐阅读更多精彩内容

  • 本文选译自《Threading Programming Guide》。 导语 线程技术作为在单个应用程序中并发执行...
    巧巧的二表哥阅读 2,410评论 4 24
  • 本文首发CSDN,如需转载请与CSDN联系。 记得第一次读这个文档还是3年前,那时也只是泛读。如今关于iOS多线程...
    DevTalking阅读 934评论 0 5
  • Object C中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码、方法又是什么? 1...
    AlanGe阅读 1,701评论 0 17
  • 王可出事已经是十二月。那天早上下雨,金水和小烨、祝勇三个站在走廊上吃早餐,看到三四个一监区的服刑人员把王可叫了出去...
    甘醇阅读 236评论 0 0
  • 找一个差不多的人 回到小城 开一家赚钱的小店 跟父母姐姐大家都住一起 不要孩子 每天吃饭 听歌 看书 散步 看电视...
    Oshiruko阅读 161评论 0 0