关于iOS多线程

#1、iOS中的多线程:
文章附上多线程图解

首先明白进程的概念:正在进行中的程序被称为进程,负责程序运行的内存分配,每个进程都有自己独立的虚拟内存空间。

线程是进程中一个独立的执行路径(控制单元),一个进程至少包含一条线程,即主线程;可以将耗时的执行路径(如网络请求)放在其他线程中执行;线程不能被杀掉,但可以暂停/休眠一条线程。

##1.1、创建线程的目的:开启一条新的执行路径,运行指定的代码,与主线程中的代码实现同时运行。

##1.2多线程的优势:
1>充分发挥多核处理器的优势,将不同的线程任务分配给不同的处理器,真正进入“并行运算”状态;
2>将耗时的任务分配到其他线程执行,由主线程负责统一更新界面会使应用更加流畅,用户体验更好;
3>当硬件处理器的数量增加,程序会运行更快,而程序无需作任何调整。

##1.3多线程的弊端:
1>线程也是程序,所以线程越多需要占用内存越多;
2>多线程需要协调和管理,所以需要CUP耗费时间跟踪线程;
3>线程之间对共享资源的访问会相互影响,必须解决竞用资源共享的问题;
4>线程太多会导致控制台复杂,最终可能造成很多buger。

#2、iOS中开辟多线程的方法及其技术特点:

1.NSThread

特点:
1>使用NSThread对象建立一个线程非常的方便;
2>但是要使用NSThread管理多个线程非常的困难,不推荐使用;
3>使用[NSThread currentThread]跟踪任务所在线程

2.GCD---Grand Central Dispatch

1>是基于C语言的底层API;
2>用Block定义任务,使用起来非常灵活便捷;
3>提供了更多的控制能力以及操作队列中所不能使用的底层函数。

3.NSOperation/NSOperationQueue

特点:
1>是使用GCD实现的一套Objective-C的API;
2>是面对对象的多线程技术
3>提供了一些在GCD中不容易实现的特性,如:限制最大并发数量,操作之间的依赖关系。

#3、NSthread开辟新线程的几种方法:

//NSThread第一种创建线程方法,需手动开启
    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(thread1Action) object:nil];
    thread.threadPriority = 0.5;//优先级
    //线程取消(一旦开启就没办法取消)
    //[thread1 cancel];
    //线程开启
    [thread start];
    thread.name = @"线程1";
 /*
   BOOL isMain = [NSThread isMainThread];//是否是主线程,返回BOOL
   BOOL isMultible = [NSThread isMultiThreaded];//判断是否是多线程
    */
- (void)thread1Action{
//[NSThread currentThread]当前线程
    NSLog(@"%@",[NSThread currentThread]);
}
//输出信息:2016-02-25 21:28:19.879 UILesson22 - NSThread[41977:450754] <NSThread: 0x7f9e98413e70>{number = 3, name = 线程1}
//NSThread第二种创建线程方法,无须手动开启
[NSThread detachNewThreadSelector:@selector(thread2Action) toTarget:self withObject:nil];

- (void)thread2Action{
    NSLog(@"%s:%@",__FUNCTION__,[NSThread currentThread]);
}
//输出信息:2016-02-25 21:35:17.167 UILesson22 - NSThread[42279:457916] -[ViewController thread2Action]:<NSThread: 0x7fc688c39800>{number = 4, name = (null)}

NSObject:

//第三种创建线程方法:开辟新线程
//NSObject自带方法
[self performSelectorInBackground:@selector(backgroundAction) withObject:nil];

- (void)backgroundAction{
    NSLog(@"%@",[NSThread currentThread]);
    //所有针对UI的操作必须在主线程中运行
    //NSObject自带的回到主线程的方法,即mainThread是在主线程中执行的
    [self performSelectorOnMainThread:@selector(mainThreadAction) withObject:nil waitUntilDone:YES];
}
//输出信息:2016-02-25 21:43:42.797 UILesson22 - NSThread[42965:465252] <NSThread: 0x7f949b7186b0>{number = 2, name = (null)}

- (void)mainThreadAction{
    NSLog(@"%s:%@",__FUNCTION__,[NSThread currentThread]);
}
//输出信息:2016-02-25 21:43:42.804 UILesson22 - NSThread[42965:465169] -[ViewController mainThreadAction]:<NSThread: 0x7f949b604f90>{number = 1, name = main}

NSObject的多线程方法注意事项:

1> NSObject的多线程方法使用的是NSThread的多线程技术.

2> NSThread的多线程技术不会自动使用@autoreleasepool.

在使用NSObject或NSThread的多线程技术时,如果涉及到对象分配,需要手动添加@autoreleasepool.

4、GCD
Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法。
dispatch queue分成以下三种:

1>运行在主线程的Main queue,通过dispatch_get_main_queue获取。dispatch_get_main_queue也是一种dispatch_queue_t。

2>并行队列global dispatch queue,通过dispatch_get_global_queue获取,由系统创建三个不同优先级的dispatch queue。并行队列的执行顺序与其加入队列的顺序相同。

3>串行队列serial queues一般用于按顺序同步访问,可创建任意数量的串行队列,各个串行队列之间是并发的。

当想要任务按照某一个特定的顺序执行时,串行队列是很有用的。串行队列在同一个时间只执行一个任务。我们可以使用串行队列代替锁去保护共享的数据。和锁不同,一个串行队列可以保证任务在一个可预知的顺序下执行。

 //GCD
    //创建一个队列,名叫queue
    //串行 DISPATCH_QUEUE_SERIAL 并行:DISPATCH_QUEUE_CONCURRENT
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);

//   分组 
//    dispatch_group_async(dispatch_group_create(), queue, ^{
//        NSLog(@"%@",[NSThread currentThread]);
//    });
//    
//    dispatch_group_async(dispatch_group_create(), queue, ^{
//        NSLog(@"%@",[NSThread currentThread]);
//    });

    //添加任务:自动添加线程
    dispatch_async(queue, ^{
        NSLog(@"%@",[NSThread currentThread]);
        NSLog(@"自动添加线程");
    });

    dispatch_async(queue, ^{
        NSLog(@"%@",[NSThread currentThread]);
        NSLog(@"fdsgaggag");
    });

    dispatch_async(queue, ^{
        NSLog(@"%@",[NSThread currentThread]);
        NSLog(@"qweqeqeqeqeq");
    });
//
//    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 3);
//    //延时:多少秒之后执行
//    dispatch_after(time, queue, ^{
//        NSLog(@"%@",[NSThread currentThread]);
//        NSLog(@"都没了武器");
//    });
    //重复执行3次
    dispatch_apply(3, queue, ^(size_t m) {

        //只执行一次
        dispatch_once_t once;
        dispatch_once(&once, ^{
            NSLog(@"nidwnidnw");
        });
         NSLog(@"尽快发什么的");
    });
     //只执行一次:和延时冲突
    dispatch_once_t once;
    dispatch_once(&once, ^{
        NSLog(@"nidwnidnw");
    });

本人写了一段简易的模仿售票的代码如下:

#import "ViewController.h"

@interface ViewController ()
{
    int  ticketsNum;//总票数
    NSLock *lock;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    lock = [[NSLock alloc]init];
    //假定还剩40张票
    ticketsNum = 40;
    //创建4个队列,模仿不同地区对售票系统余票的访问
    dispatch_queue_t queue1 = dispatch_queue_create("北京西站",DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue1, ^{
        [self saleTickets:queue1];
    });

    dispatch_queue_t queue2 = dispatch_queue_create("北京北站",DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue2, ^{
        [self saleTickets:queue2];
    });

    dispatch_queue_t queue3 = dispatch_queue_create("北京站",DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue3, ^{
        [self saleTickets:queue3];
    });

    dispatch_queue_t queue4 = dispatch_queue_create("北京南站",DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue4, ^{
        [self saleTickets:queue4];
    });
}
- (void)saleTickets:(dispatch_queue_t)queue{
    while (ticketsNum>0)
    {
        //上锁,一次只允许一个队列访问
        [lock lock];
        //余票为零,则不能访问
        if (ticketsNum <= 0)
        {
            break;
        }
        ticketsNum--;
        [NSThread sleepForTimeInterval:0.01];
        const char *name1 = dispatch_queue_get_label(queue);
        NSString *name = [NSString stringWithCString:name1 encoding:NSUTF8StringEncoding];
        //为方便观察结果,输出请求的站名以及余票数量
        NSLog(@"%@,%d",name,ticketsNum);
        //解锁
        [lock unlock];
    }
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}
@end

//输出结果
2016-02-27 17:17:56.555 UILesson22-tickets[32351:407754] 北京西站,39
2016-02-27 17:17:56.566 UILesson22-tickets[32351:407755] 北京北站,38
2016-02-27 17:17:56.578 UILesson22-tickets[32351:407756] 北京站,37
2016-02-27 17:17:56.591 UILesson22-tickets[32351:407758] 北京南站,36
2016-02-27 17:17:56.602 UILesson22-tickets[32351:407754] 北京西站,35
2016-02-27 17:17:56.614 UILesson22-tickets[32351:407755] 北京北站,34
2016-02-27 17:17:56.627 UILesson22-tickets[32351:407756] 北京站,33
2016-02-27 17:17:56.639 UILesson22-tickets[32351:407758] 北京南站,32
2016-02-27 17:17:56.651 UILesson22-tickets[32351:407754] 北京西站,31
2016-02-27 17:17:56.663 UILesson22-tickets[32351:407755] 北京北站,30
2016-02-27 17:17:56.676 UILesson22-tickets[32351:407756] 北京站,29
2016-02-27 17:17:56.688 UILesson22-tickets[32351:407758] 北京南站,28
2016-02-27 17:17:56.701 UILesson22-tickets[32351:407754] 北京西站,27
2016-02-27 17:17:56.712 UILesson22-tickets[32351:407755] 北京北站,26
2016-02-27 17:17:56.724 UILesson22-tickets[32351:407756] 北京站,25
2016-02-27 17:17:56.734 UILesson22-tickets[32351:407758] 北京南站,24
2016-02-27 17:17:56.746 UILesson22-tickets[32351:407754] 北京西站,23
2016-02-27 17:17:56.756 UILesson22-tickets[32351:407755] 北京北站,22
2016-02-27 17:17:56.767 UILesson22-tickets[32351:407756] 北京站,21
2016-02-27 17:17:56.780 UILesson22-tickets[32351:407758] 北京南站,20
2016-02-27 17:17:56.792 UILesson22-tickets[32351:407754] 北京西站,19
2016-02-27 17:17:56.804 UILesson22-tickets[32351:407755] 北京北站,18
2016-02-27 17:17:56.815 UILesson22-tickets[32351:407756] 北京站,17
2016-02-27 17:17:56.826 UILesson22-tickets[32351:407758] 北京南站,16
2016-02-27 17:17:56.837 UILesson22-tickets[32351:407754] 北京西站,15
2016-02-27 17:17:56.851 UILesson22-tickets[32351:407755] 北京北站,14
2016-02-27 17:17:56.862 UILesson22-tickets[32351:407756] 北京站,13
2016-02-27 17:17:56.873 UILesson22-tickets[32351:407758] 北京南站,12
2016-02-27 17:17:56.884 UILesson22-tickets[32351:407754] 北京西站,11
2016-02-27 17:17:56.895 UILesson22-tickets[32351:407755] 北京北站,10
2016-02-27 17:17:56.907 UILesson22-tickets[32351:407756] 北京站,9
2016-02-27 17:17:56.922 UILesson22-tickets[32351:407758] 北京南站,8
2016-02-27 17:17:56.934 UILesson22-tickets[32351:407754] 北京西站,7
2016-02-27 17:17:56.945 UILesson22-tickets[32351:407755] 北京北站,6
2016-02-27 17:17:56.955 UILesson22-tickets[32351:407756] 北京站,5
2016-02-27 17:17:56.968 UILesson22-tickets[32351:407758] 北京南站,4
2016-02-27 17:17:56.979 UILesson22-tickets[32351:407754] 北京西站,3
2016-02-27 17:17:56.991 UILesson22-tickets[32351:407755] 北京北站,2
2016-02-27 17:17:57.002 UILesson22-tickets[32351:407756] 北京站,1
2016-02-27 17:17:57.013 UILesson22-tickets[32351:407758] 北京南站,0

GCD的另一个用处是可以让程序在后台较长久的运行。

在没有使用GCD时,当app被按home键退出后,app仅有最多5秒钟的时候做一些保存或清理资源的工作。但是在使用GCD后,app最多有10分钟的时间在后台长久运行。这个时间可以用来做清理本地缓存,发送统计数据等工作。

// AppDelegate.h文件
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundUpdateTask;

// AppDelegate.m文件
- (void)applicationDidEnterBackground:(UIApplication *)application
{
    [self beingBackgroundUpdateTask];
    // 在这里加上你需要长久运行的代码
    [self endBackgroundUpdateTask];
}

- (void)beingBackgroundUpdateTask
{
    self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        [self endBackgroundUpdateTask];
    }];
}

- (void)endBackgroundUpdateTask
{
    [[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];
    self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}

5、NSOperation/NSOperationQueue
NSOperation是一个抽象类,表示一个操作,其实就是一个方法,里面实现某些功能,本身跟我们的多线程无关,但是一旦放入NSOperationQueue里面就会自动给每个NSOperation添加一个线程。
NSOperation的基本使用步骤:

定义操作队列 --> 定义操作 -->将操作添加到队列.

通常我们使用NSOperation的子类来创建操作:NSInvocationOperation和NSBlockOperation

- (void)viewDidLoad {
    [super viewDidLoad];
     //创建一个NSInvocationOperation操作
    NSInvocationOperation *invocation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationAction) object:nil];
    //手动开启
    [invocation start];

    //创建一个NSBlockOperation操作
    NSBlockOperation *block = [NSBlockOperation blockOperationWithBlock:^{
         NSLog(@"%s:%@",__FUNCTION__,[NSThread currentThread]);
    }];
//     手动开启
    [block start];

    //创建一个NSOperationQueue队列管理所有操作:queue中的操作不用手动开启
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    //添加操作:操作必须还未执行才能添加,否则会崩溃

    //添加依赖关系(队列添加操作之前):后面那个操作永远在前面操作的前面,此处即blcok在invocation前面
    //注意: 利用 " addDependency "可以指定操作之间彼此的依赖关系(执行先后顺序),但是注意不要出现循环依赖.
    [invocation addDependency:block];
//队列添加操作的两种方法:一数组形式添加、逐个添加
    //[queue addOperations:@[invocation,block]];
    [queue addOperation:invocation];
    [queue addOperation:block];
    /*
    [queue setMaxConcurrentOperationCount:2];可以用来设置最大同时并发的线程数量。
    */
}
- (void)invocationAction{
    NSLog(@"%s:%@",__FUNCTION__,[NSThread currentThread]);

}

自定义Operation:
除了上面的两种 Operation 以外,我们还可以自定义 Operation。自定义 Operation 需要继承 NSOperation 类,并实现其 main() 方法,因为在调用 start() 方法的时候,内部会调用 main() 方法完成相关逻辑。所以如果以上的两个类无法满足你的欲望的时候,你就需要自定义了。你想要实现什么功能都可以写在里面。除此之外,你还需要实现 cancel() 在内的各种方法。这个功能我最近知识刚刚接触,并不熟练,所以暂时不介绍。

NSOperation小结:
从本质上看,操作队列的性能会比GCD略低,不过,大多数情况下这点负面影响可以忽略不计.操作队列是并发编程的首选工具.

在这里,推荐一个非常好用的第三方编程框架AFN,底层用GCD开发,开发的接口是NSOperation的.

多线程中得循环引用问题:

如果self对象持有操作对象的引用,同时操作对象当中又直接访问了self时,才会造成循环引用.

单纯在操作对象中使用self不会造成循环引用.

注意: 此时不要使用[weakSelf].

多线程中的资源共享问题:

并发编程中许多问题的根源就是在多线程中访问共享资源.资源可以是一个属性,一个对象,网络设备或者一个文件等.

在多线程中任何一个共享的资源都可能是一个潜在的冲突点,必须精心设计以防止这种冲突的发生.

为了保证性能,atomic仅针对属性的setter方法做了保护.

争抢共享资源时,如果涉及到属性的getter方法,可以使用互斥锁(@synchronized)可以保证属性在多个线程之间的读写都是安全的.

无论是atomic还是@synchronized ,使用的代价都是高昂的.

建议:

多线程是并发执行多个任务提高效率的,如果可能,应该在线程中避免争抢共享资源.

正是出于性能的考虑,UIKit中的绝大多数类都不是线程安全的,因此,苹果公司要求:更新UI相关的操作,应该在主线程中执行.

常见面试题:在项目什么时候选择使用GCD,什么时候选择NSOperation?
项目中使用NSOperation的优点是NSOperation是对线程的高度抽象,在项目中使用它,会使项目的程序结构更好,子类化NSOperation的设计思路,是具有面向对象的优点(复用、封装),使得实现是多线程支持,而接口简单,建议在复杂项目中使用。
项目中使用GCD的优点是GCD本身非常简单、易用,对于不复杂的多线程操作,会节省代码量,而Block参数的使用,会是代码更为易读,建议在简单项目中使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值