美文网首页
iOS 再学多线程(一)

iOS 再学多线程(一)

作者: 骑行怪状 | 来源:发表于2016-05-27 02:40 被阅读12次
    噢,达令

    之前“写”过一篇关于多线程的博客,主要是综合各路大神的博文写得,回过头看其实对于多线程的相关知识点还是模糊的,从今天起重新认识多线程。

    基础知识

    进程、线程

    进程进程

    进程,指的是系统中正在运行的一个应用程序,每个进程是独立的,简言之: 正在进行的程序即进程。
    线程和线程是密不可分的,一个进程想要运行并执行任务,必须有线程才行。一个进程的所有任务都在线程中执行的。
    例如: 使用迅雷下片儿都需要线程,一般我们在工程中,将耗时的任务放到子线程中执行。

    线程线程

    串行

    在同一时间内,一个线程只能执行一个任务,在串行中,如果要在一个线程中执行多个任务(串行),那么只能一个一个的按顺序执行这些任务。

    例如排队办业务,别插队,挨打!例如排队办业务,别插队,挨打!
    多线程

    一个进程中可以开启多条线程,每条线程可以并行(同时)进行,用于执行不同的任务。

    下片儿下片儿

    如图:

    并行并行

    多线程原理

    同一时间,CPU 只能处理一条线程的任务,只有一条线程工作。多线程并发同时执行,其实是 CPU 快速地在多条线程之间调度,来回切换调度,造成了“同时”执行的假象,只不过 CPU 切换时间很快。其实也可以理解为“串行”。

    多线程的缺点

    1. 多线程如果在很多条线程之间切换调度, CPU 会消耗大量的 CPU 资源,CPU 的开销会很大,很累;
    2. 多线程开的过多会降低每条线程的调动频次,造成线程执行效率低;
    3. 创建线程对内存是有开销的,而且需要时间,大约 90 毫秒的创建时间,所以创建的线程越多占用空间还有耗费时间也会越多、越久;
    4. 线程创建的越多,对于项目的设计会更加复杂,比如线程之间的通讯,多线程的数据共享。

    常用多线程方案对比

    以下为我们项目中的常用多线程方案,其中 GCD 多线程方案,使用相对多,相对“牛逼“,恩,是牛逼,面试没回答好 /(ㄒoㄒ)/ 。其中的 pthread 使用时需要导入 pthread 头文件,使用 C 语言实现。

    多线程方案对比多线程方案对比

    NSThread 线程创建

    我们在 viewController 中创建子线程,创建方法常用的有 ”3种“。

    #pragma mark - 在touche方法中调用创建线程
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
        [self creatThread1];
        [self creatThread2];
        [self creatThread3];
    
    }
    
    
    #pragma mark -- 隐式创建子线程 (不能更详细的设置线程相关的信息)
    - (void)creatThread3{
        
        [self performSelector:@selector(run:) withObject:@"jake" ];
    }
    
    #pragma mark -- 自动创建线程 自动开启
    - (void)creatThread2{
    
        [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"Mob"];
    }
    
    #pragma mark - 手动创建 线程创建完,会自动销毁,不用程序员管理
    
    - (void)creatThread1{
    
        //创建线程
        NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"run"];
        //线程命名
        thread.name = @"线程1";
        //开启线程
        [thread start];
    }
    
    
    - (void)run: (NSString *)pat{
    
        NSLog(@"pat : %@ --- %@ ", pat , [NSThread currentThread].name);
        
        [NSThread sleepForTimeInterval:2];//阻塞线程,让线程休眠两秒后执行
        
        //遥远的未来 暂停到某个时刻
        //[NSThread sleepUntilDate:[NSDate distantFuture]];
            NSLog(@"阻塞线程,让线程休眠两秒后执行");
    }
    

    多线程安全

    多条线程访问同一个地址,或是同时做同一件事情时,会出现访问错误的情况,例如买票、取款。

    解决方法: 需要使用线程锁(互斥锁),在取值前添加,一般我们将 self 作为锁对象,锁对象一定要注意,只有一个,多个锁对象还会出现访问问题。

    线程同步和线程锁为同一件事。

    @synchronized (self) {
        
    }
    

    GCD 多线程

    全称是Grand Central Dispath (牛逼的中枢调度器),纯C语言,提供非常多强大的函数,是目前苹果官网推荐的多线程开发方法,NSOperation便是基于GCD的封装。

    GCD中有2个核心概念

    (1)任务:执行什么操作
    (2)队列:用来存放任务

    容易混淆:同步、异步、并发、串行

    同步和异步主要影响:能不能开启新的线程

    同步:只是在当前线程中执行任务,不具备开启新线程的能力
    异步:可以在新的线程中执行任务,具备开启新线程的能力

    并发和串行主要影响:任务的执行方式

    并发:允许多个任务并发(同时)执行
    串行:一个任务执行完毕后,再执行下一个任务

    并行和串行不会影响是否能开启线程。

    GCD 自己可以创建 串行队列, 也可以创建并行队列.它有两个参数,第一个参数是标识符,用于 DEBUG 的时候标识唯一的队列,可以为空。第二个才是最重要的。第二个参数用来表示创建的队列是串行的还是并行的,传入DISPATCH_QUEUE_SERIAL 或 NULL 表示创建串行队列。传入 DISPATCH_QUEUE_CONCURRENT 表示创建并行队列。

    //串行队列
    dispatch_queue_t queue1 = dispatch_queue_create("GCD多线程,串行", DISPATCH_QUEUE_SERIAL);
    
    // 异步任务
    dispatch_async(queue1, ^{
       NSLog(@"异步执行任务,开线程,在串行队列中执行");
       });
    }];
    
    
     //并行队列
    dispatch_queue_t queue2 = dispatch_queue_create("GCD多线程,并行", DISPATCH_QUEUE_CONCURRENT);
    也可以:
    dispatch_queue_t queue2 = dispatch_queue_create("GCD多线程,并行", NULL);
    
    //同步任务
    dispatch_sync(queue2, ^{
    NSLog(@"同步任务:不会开启线程,在并行队列中执行");
    
    });
    

    GCD 官方已经提供了一个全局的并发队列,供整个应用使用

    dispatch_queue_t queque3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    

    dispatch_barrier_async 任务 “栅栏”

    前面任务执行完之后才会执行它,执行完之后才会执行后面的。

        // 全局并发队列  在 barrier 任务中不能使用全局队列 ×
    dispatch_queue_t quent = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
       
    // 要使用这个队列 √
    dispatch_queue_t quent2 = dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(quent2, ^{
        
        NSLog(@"---1--- %@",[NSThread currentThread]);
    });
    
    // “栅栏” 前面的任务执行完后,再执行 barrier 里面的,执行完之后才会执行后面的任务
    dispatch_barrier_async(quent2, ^{
        
        NSLog(@"---栅栏 2--- %@",[NSThread currentThread]);
        
        [self run:@"li"];
        
    });
    
    dispatch_async(quent2, ^{
        NSLog(@"---3--- %@",[NSThread currentThread]);
    });
    

    主队列 特殊的队列

    在主队列中的任务都会在主线程中进行。

    dispatch_queue_t queue4 = dispatch_get_main_queue();
    
    同步、异步 并发和串行队列组合区别同步、异步 并发和串行队列组合区别

    由于队列和任务的不同,可以搭配出不同的组合,如上图,大体上的问题有两个,一个是否会创建新的子线程,还有是否会阻塞线程。

    异步函数+并发队列

    可以同时开启多条线程

    同步函数 + 并发队列

    不可以开新的线程,不能并发执行任务(只要不能开辟新的线程就不会并发执行任务)

    异步函数 + 串行队列

    可以开辟新的子线程,但是不能并发执行任务,只能开一条线程执行任务

    异步函数 + 主队列

    不管你是同步还是异步操作,只要是主队列,那么任务执行都会在主线程中执行,异步函数在主队列中将不会开辟线程。

    同步函数 + 串行队列 同步函数 + 主队列

    这两个组合比较特殊,一般不会在项目中这样干活搭配,这样的组合会造成堵塞线程。”你先“, ”还是你先“ 让来让去,谁都不能走了。

    描述描述

    因为主队列要在主线程中执行,我们假设函数就在当前的主线程中,当执行主队列时,他想回到主线程,但是执行主线程的时候,需要同步的任务执行完才能执行主线程。然而此刻同步任务也想执行完,但是主线程还没执行完。把自己绕蒙逼了 /(ㄒoㄒ)/~~

    简而言之: 主队列要执行,就得回到主线程,但是,同步任务还没执行完毕,无法完成祖国回归,回到主线程怀抱的梦想,所以就很尴尬了,最终隔岸相望。

    相关文章

      网友评论

          本文标题: iOS 再学多线程(一)

          本文链接:https://www.haomeiwen.com/subject/qcwxdttx.html