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

Bison

"对于单线程的应用而言,整个应用只是一个顺序执行流,当执行到某个耗时操作时,主线程就会被阻塞,应用就卡在那无法继续执行,因此单线程的应用体验度很低,总感觉像手机卡似得,就像一条小河北阻塞了,只有打通了才能继续有水流到下一个地方放一样。而多线程则更像一条河有无数的分支,这条阻塞了还有其他的分支在运行,影响不到大局。"

不知道大家面试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上线啦,快点此来围观吧

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

好文推荐:详解持久化Core Data框架的原理以及使用

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


分享文章