iOS 多线程

作者: 9岁就很6 | 来源:发表于2019-02-22 10:39 被阅读59次

    关于多线程

    概念:

    同一个进程中同时开启多个线程,每条线程执行不同的任务。

    本质:速度快

    1.同一时间,CPU只能处理一条线程,意味着只有一条线程在执行。
    2.CPU以人类难以察觉的速度在不同的线程之间切换,造成多条线程并发执行的假象。
    3.如果线程非常非常多,CPU切换频繁,消耗大量资源,线程执行效率降低。

    优点:

    1.多条线程同时(并发)执行,提高程序的执行效率。
    2.提高资源利用率,包括CPU、内存等。

    缺点:

    开启新线程会占用一定内存,线程过多会降低性能。
    2.程序设计更加复杂,比如线程之间的通信、多条线程的数据共享。

    图片说明(附:本文目录)

    图片

    一、基本概念

    进程和线程的区别:

    • 进程:
      1.可以理解成一个运行中的应用程序,是系统进行资源分配和调度的基本单位,是操作系统结构的基础,主要管理资源。
      2.每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内。
    • 线程:
      1.线程是进程的基本执行单元,即进程想要执行任务,必须得有线程。
      2.序执行流的最小单元,线程是进程中的一个实体。

    同步线程和异步线程的区别:

    • 同步线程:
      在当前线程内按照创建的先后顺序依次执行,不开启新线程。
    • 异步线程:
      在当前线程内开启多个新的线程,同时运行,无先后关系。

    队列:

    • 装载线程任务的队形结构。

    并发:

    • 线程执行可以同时一起进行执行。

    串行:

    • 线程执行只能依次逐一先后有序的执行。

    线程通讯:

    • 在一个进程中,通常有多个线程,线程不是孤立存在的,线程之前需要"沟通交流"。

    注意点(面试常问)

    • 一个进程可有多个线程。
    • 一个进程可有多个队列。
    • 队列可分并发队列和串行队列。

    二、创建方式

    1-1: NSThread :每个NSThread对象对应一个线程,真正最原始的线程。

    优点:

    • NSThread 轻量级最低,相对简单。
    • 可直接操作线程对象。

    缺点:

    • 手动管理所有的线程活动,如生命周期、线程同步、睡眠等。
    1-2: NSOperation :自带线程管理的抽象类。

    优点:

    • 自带线程周期管理,操作上可更注重自己逻辑。

    缺点:

    • 面向对象的抽象类,只能实现它或者使用它定义好的两个子类:NSInvocationOperation 和 NSBlockOperation。
    1-3: GCD :Grand Central Dispatch是由苹果开发的一个多核编程的解决方案。iOS4.0+才能使用。

    优点:

    • 最高效,避开并发陷阱。
    • 苹果官方推荐使用。
    • 使用比上面2种更为简单。
    • 性能更好,GCD自动根据系统负载来增减线程数量,这就减少了上下文切换以及增加了计算效率。
    • 开发者只需要告诉 GCD 想要如何执行什么任务,不需要编写任何线程管理代码。

    缺点:

    • 基于C语言实现

    三、如何选择使用方案

    • 简单而安全的选择NSOperation实现多线程即可。
    • 处理大量并发数据,又追求性能效率的选择GCD。
    • NSThread本人选择基本上是在做些小测试上使用。
    • 作者个人推荐使用gcd。

    四、生命周期

    无论使用哪种方式创建的线程,在正常情况下它的生命周期都是一样的。

    线程状态示意图
    新建:实例化线程对象(概念):
    • 就绪:向线程对象发送start消息,线程对象被加入可调度线程池等待CPU调度。
    • 运行:CPU 负责调度可调度线程池中线程的执行。线程执行完成之前,状> * 态可能会在就绪和运行之间来回切换。就绪和运行之间的状态变化由CPU负> * 责,程序员不能干预。
    • 阻塞:当满足某个预定条件时,可以使用休眠或锁,阻塞线程执行。sleepForTimeInterval(休眠指定时长),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥锁)。
    • 死亡:正常死亡,线程执行完毕。非正常死亡,当满足某个条件后,在线程> * 内部中止执行/在主线程中止线程对象
    • 还有线程的exit和cancel
    • [NSThread exit]:一旦强行终止线程,后续的所有代码都不会被执行。
    • [thread cancel]取消:并不会直接取消线程,只是给线程对象添加 isCancelled 标记。

    五、使用方法(详细介绍GCD的使用方法)

    NSThread 创建线程

      //动态创建线程
        NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadAction:) object:@"one"];
        thread.threadPriority = 1;
        [thread start];
        
        //静态创建线程
        [NSThread detachNewThreadSelector:@selector(threadAction:) toTarget:self withObject:@"two"];
        
        //隐式创建线程
        [self performSelectorInBackground:@selector(threadAction:) withObject:@"three"];
    
       //响应方法
        -(void)threadAction:(id)object
        {
            NSLog(@"object = %@",object);
        }
    
    • 线程通讯
    //在指定线程上执行操作
    [self performSelector:@selector(threadAction:) onThread:thread withObject:nil waitUntilDone:YES]; 
    //在主线程上执行操作
    [self performSelectorOnMainThread:@selector(threadAction:) withObject:nil waitUntilDone:YES]; 
    //在当前线程执行操作
    [self performSelector:@selector(threadAction:) withObject:nil];
    

    NSOperation (作者写了几种情况下的使用,请看代码注释,方便对比)

    //初始化一个NSOperationQueue对象
    @property(nonatomic,retain)NSOperationQueue * queue;
    
    #pragma mark NSInvocationOperation start(创建方式)
    -(void)case1{
        
        NSInvocationOperation * invocation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operationMethod:) object:nil];
        
        //如果是调用 start方法,会在主线程执行
        [invocation start];
        
    }
    -(void)operationMethod:(id)objc{
        
        NSLog(@">>>>>>>>%@:%@",[NSThread currentThread],objc);
        //<NSThread: 0x7ff74b525bd0>{number = 1, name = main}:(null)
        
    }
    
    #pragma mark 添加到操作队列的operation都是在分线程执行的
    -(void)case2{
        
        for (int i =0;  i<100; i++) {
            NSInvocationOperation * invocation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operationMethod:) object:nil];
            //把操作添加到队列里面(份线程)
            [self.queue addOperation:invocation];
        }
        
    }
    
    #pragma mark  block  addExecution添加到操作队列的operation都是在分线程执行的
    -(void)case3{
        
        NSBlockOperation * block = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"1111111>>>>>%@",[NSThread currentThread]);
            
        }];
        
        //添加的都自动变份线程
        [block addExecutionBlock:^{
            NSLog(@"222222222%@",[NSThread currentThread]);
            
        }];
        
        [block addExecutionBlock:^{
            NSLog(@"433333333%@",[NSThread currentThread]);
            
        }];
        
        [block addExecutionBlock:^{
            NSLog(@"444444444%@",[NSThread currentThread]);
            
        }];
        
        [block start];
        
        
    }
    
    #pragma mark  同一时间在五条线程上各自完成任务(不是开辟五条)
    
    -(void)case4{
        
        [self.queue setMaxConcurrentOperationCount:5];
    
        for (int i =0; i<100; i++) {
            NSBlockOperation * block = [NSBlockOperation blockOperationWithBlock:^{
                
                NSLog(@">>>>>%@",[NSThread currentThread]);
            }];
            
            [self.queue addOperation:block];
        }
    }
    
    #pragma mark 依赖关系 举例:下载电影,解压,播放完成后接着执行
    -(void)case5{
        
        NSBlockOperation * block1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"下载--------");
            
        }];
        
        NSBlockOperation * block2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"解压  --------");
            
        }];
        
        NSBlockOperation * block3 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"播放--------");
            
        }];
        
        [block2 addDependency:block1];
        [block3 addDependency:block2];
        
        
        [self.queue addOperation:block1];
        [self.queue addOperation:block2];
        [self.queue addOperation:block3];
        
    }
    
    
    
    #pragma mark 举例:上传十张照片Demo 一张上传完了再传另外一张(看执行顺序,都是在子线程执行)
    -(void)case6{
        
        NSBlockOperation * lastBlock =nil;
        
        for (int i =0; i<10; i++) {
            NSBlockOperation * block =[NSBlockOperation blockOperationWithBlock:^{
                NSLog(@">>>>>%@,%d",[NSThread currentThread],i);
                
            }];
            if (lastBlock !=nil) {
                [block addDependency:lastBlock];
            }
            [self.queue addOperation:block];
            
            //每次循环都要重置lastOperation
            lastBlock=block;
            
        }
    }
    

    GCD

    1.任务和队列

    • 1.1 任务
    • 就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。
    • 执行任务有两种方式:同步执行(sync)和异步执行(async)。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
    同步执行(sync):
        * 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
        * 只能在当前线程中执行任务,不具备开启新线程的能力。
    异步执行(async):
        * 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
        * 可以在新的线程中执行任务,具备开启新线程的能力。
    
    • 1.2 队列
    • 这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。
    • 在 GCD 中有两种队列:串行队列和并发队列。两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。
    串行队列(Serial Dispatch Queue):
         * 每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
         * 对于串行队列,GCD 提供了的一种特殊的串行队列:主队列(Main Dispatch Queue),所有放在主队列中的任务,都会放到主线程中执行。
    并发队列(Concurrent Dispatch Queue):
         * 可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
    注意:并发队列的并发功能只有在异步(dispatch_async)函数下才有效
         * 对于并发队列,GCD 默认提供了全局并发队列(Global Dispatch Queue)。
    

    2.GCD使用步骤

    • 创建一个队列(串行队列或并发队列)
    • 将任务追加到任务的等待队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)

    3.队列的创建

    • 可以使用dispatch_queue_create来创建队列,需要传入两个参数,第一个参数表示队列的唯一标识符,用于 DEBUG,可为空,Dispatch Queue 的名称推荐使用应用程序 ID 这种逆序全程域名;第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT 表示并发队列。
    // 串行队列的创建方法
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
    // 并发队列的创建方法
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    

    4.任务的创建

    • GCD 提供了同步执行任务的创建方法dispatch_sync和异步执行任务创建方法dispatch_async。
    // 同步执行任务创建方法
    dispatch_sync(queue, ^{
        // 这里放同步执行任务代码
    });
    // 异步执行任务创建方法
    dispatch_async(queue, ^{
        // 这里放异步执行任务代码
    });
    

    5. 基本使用

    组合方式对比
    • 同步执行 + 并发队列


      同步执行 + 并发队列
    • 所有任务都是在当前线程(主线程)中执行的,没有开启新的线程(同步执行不具备开启新线程的能力)。
    • 所有任务都在打印的syncConcurrent---begin和syncConcurrent---end之间执行的(同步任务需要等待队列的任务执行结束)。
    • 任务按顺序执行的。按顺序执行的原因:虽然并发队列可以开启多个线程,并且同时执行多个任务。但是因为本身不能创建新线程,只有当前线程这一个线程(同步任务不具备开启新线程的能力),所以也就不存在并发。而且当前线程只有等待当前队列中正在执行的任务执行完毕之后,才能继续接着执行下面的操作(同步任务需要等待队列的任务执行结束)。所以任务只能一个接一个按顺序执行,不能同时被执行。
    • 特点:在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。
    • 异步执行 + 并发队列


      异步执行 + 并发队列
    • 除了当前线程(主线程),系统又开启了3个线程,并且任务是交替/同时执行的。(异步执行具备开启新线程的能力。且并发队列可开启多个线程,同时执行多个任务)。
    • 所有任务是在打印的syncConcurrent---begin和syncConcurrent---end之后才执行的。说明当前线程没有等待,而是直接开启了新线程,在新线程中执行任务(异步执行不做等待,可以继续执行任务)。
    • 特点:可以开启多个线程,任务交替(同时)执行。
    • 同步执行 + 串行队列


      同步执行 + 串行队列
    • 所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(同步执行不具备开启新线程的能力)。
    • 所有任务都在打印的syncConcurrent---begin和syncConcurrent---end之间执行(同步任务需要等待队列的任务执行结束)。
    • 任务是按顺序执行的(串行队列每次只有一个任务被执行,任务一个接一个按顺序执行)。
    • 特点:不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。
    • 异步执行 + 串行队列


      异步执行 + 串行队列
    • 开启了一条新线程(异步执行具备开启新线程的能力,串行队列只开启一个线程)。
    • 所有任务是在打印的syncConcurrent---begin和syncConcurrent---end之后才开始执行的(异步执行不会做任何等待,可以继续执行任务)。
      *任务是按顺序执行的(串行队列每次只有一个任务被执行,任务一个接一个按顺序执行)。
    • 特点:会开启一条新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务。

    以上如果您看不太懂,笔者还特意写了关于GCD的大部分使用情况,以及死锁,直接看代码

    #pragma mark ---自定义串行队列1
    -(void)case1{
        //创建一个队列create
        //参数1 标示,标记队列的,参数二指定串行并行的
        dispatch_queue_t queue =   dispatch_queue_create ("com.fuck.www", DISPATCH_QUEUE_SERIAL);
        for (int i = 0; i<10; i++) {
            //在队列里面执行同步任务
    
            //串行队列的同步方法,都是在主线程执行
            //这个方法没用
           // {number = 1, name = main}
            dispatch_sync(queue, ^{
                NSLog(@"同步方法,都是在主线程执行case1===sync>>>>>%@",[NSThread currentThread]);
            });
        }
    }
    #pragma mark- -----自定义串行队列2
    -(void)case2{
        //串行队列的异步方法只创建一个分线程
        //斯坦福大学比较推荐这一种创建线程的方法
        //方便调试
     
        
        dispatch_queue_t queue =  dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(queue, ^{
            NSLog(@"异步执行,都不在主线程 ==async>>>>>>>>>>>>%@",[NSThread currentThread]);
        });
        
        
        
    }
    #pragma mark ----自定义并行队列(并行队列的同步方法)
    -(void)case3{
        
        // DISPATCH_QUEUE_CONCURRENT 并行
        dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
        
        
        for (int i = 0; i<10; i++) {
            dispatch_async(queue, ^{
                NSLog(@"并行队列的同步>>>>%@",[NSThread currentThread]);
            });
        }
       
        
    }
    #pragma mark ----自定义并行队列(并行队列的异步方法)
    -(void)case4{
        
        dispatch_queue_t queue= dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
        for (int i =0; i<100; i++) {
            
            dispatch_async(queue, ^{
                NSLog(@"并行队列的异步========%@",[NSThread currentThread]);
            });
        }
    }
    #pragma mark -----全局队列(第一个参数 优先级,第二个写个0就行了)
    //全局队列
    -(void)case5{
      
         //异步方法,和并行队列的异步方法一样
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        for (int i =0; i<100; i++) {
            dispatch_async(queue, ^{
                NSLog(@"async-----%@",[NSThread currentThread]);
            });
        }
    }
    #pragma mark ----全局队列和串行队列的同步方法一样,在主线程执行
    -(void)case6{
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        for (int i = 0; i<100; i++
             ) {
            dispatch_sync(queue, ^{
                NSLog(@"syn-------%@",[NSThread currentThread]);
            });
        }
    }
    #pragma mark ----主队列 刷新ui界面 (同步方法 锁死!)
    -(void)case7{
        dispatch_queue_t queue = dispatch_get_main_queue();
        NSLog(@"只输出这句");
        //死锁(坐在公交车上等公交车)
        //    http://www.2cto.com/kf/201507/415322.html
        //主队列里面执行同步方法
    
         dispatch_sync(queue, ^{
             NSLog(@"不在输出%@",[NSThread currentThread]);
         });
        NSLog(@"更不能输出");
    }
    #pragma mark ----主队列执行异步方法,线程是在主线程
    -(void)case8{
        dispatch_queue_t queue = dispatch_get_main_queue();
        for ( int i =0; i<100; i++) {
            dispatch_async(queue, ^{
                NSLog(@"主队列执行异步方法===%@",[NSThread currentThread]);
            });
        }
    }
    //封装一个方法
    -(UIImage *)myImageWithUrl:(NSString *)strUrl{
        NSURL * url = [NSURL URLWithString:strUrl];
        NSData * data=[NSData dataWithContentsOfURL:url];
        UIImage * image = [UIImage imageWithData:data];
        
        return image;
    }
    #pragma mark   异步加载一个张图
    -(void)case9{
        dispatch_queue_t queue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 );
        
        __block UIImage * image1 =nil;
        dispatch_async(queue, ^{
          image1= [self myImageWithUrl:@"http://img3.3lian.com/2014/f1/2/d/9.jpg"];
        
            //有时候分线程能刷新UI,但是不靠谱;要回到主线程刷新UI
            //回到主线程刷新ui
            dispatch_async(dispatch_get_main_queue(), ^{
                _image1.image = image1;
            });
            
        });
        
    }
    #pragma mark  //加载两张图片
    -(void)case10{
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_async(queue, ^{
            UIImage * img1 = [self myImageWithUrl:@"https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1464070575&di=3a13253ffa34b1dc5a15210527e1c3a5&src=http://img3.3lian.com/2014/f1/2/d/11.jpg"];
            UIImage * image2=[self myImageWithUrl:@"http://img3.3lian.com/2014/f1/2/d/9.jpg"];
            
            dispatch_async(dispatch_get_main_queue(), ^{
                
                _image1.image =img1;
                _image2.image =image2;
            });
        });
    }
    #pragma mark ----case10的  优化版
    -(void)case11{
        
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_async(queue, ^{
            
            //创建一个组
            dispatch_group_t group = dispatch_group_create();
            __block UIImage * image1 = nil;
            __block UIImage * image2 = nil;
            dispatch_group_async(group, queue, ^{
                
                NSLog(@"1111111>>>>>>%@",[NSThread currentThread]);
                image1 =[self myImageWithUrl:@"https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1464070575&di=3a13253ffa34b1dc5a15210527e1c3a5&src=http://img3.3lian.com/2014/f1/2/d/11.jpg"];
        
            });
            dispatch_group_async(group, queue, ^{
                
                NSLog(@"22222222>>>>>>%@",[NSThread currentThread]);
                image2 =[self myImageWithUrl:@"http://img3.3lian.com/2014/f1/2/d/9.jpg"];
                
            });
            //获取到图片后刷新ui
            
            dispatch_group_notify(group, queue, ^{
                _image1.image = image1;
                _image2.image =image2;
                
            });
        });
    }
    #pragma mark dispatch_barrier_async 设置线程的先后执行顺序
    -(void)case12{
        
        dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
        
        dispatch_async(queue, ^{
            NSLog(@"11111");
        });
        
        dispatch_async(queue, ^{
            NSLog(@"2222");
        });
        
        dispatch_async(queue, ^{
            NSLog(@"33333");
        });
    //    >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
        //相当于栅栏,可以让123执行完毕之后再执行4 5 6
        dispatch_barrier_async(queue, ^{
            NSLog(@"barrier>>>>>>>>");
        });
        
        dispatch_async(queue, ^{
            NSLog(@"444444444");
        });
        
        dispatch_async(queue, ^{
            NSLog(@"555555555");
        });
        
        dispatch_async(queue, ^{
            NSLog(@"66666666");
        });
        
        
        
    }
    #pragma mark  dispatch_apply循环执行
    - (void)case13{
        
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        //5次
        dispatch_apply(5, queue, ^(size_t index) {
            //index索引
            NSLog(@"55555>>>%zu",index);
        });
        
    }
    
    #pragma mark       //多少秒之后执行这个方法
    
    - (void)case14{
        
        //    self performSelector:<#(nonnull SEL)#> withObject:<#(nullable id)#> afterDelay:<#(NSTimeInterval)#>
        
        //    [NSThread sleepForTimeInterval:1];
        //
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        //获取时间
        dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC);
        //多少秒之后执行这个方法
        dispatch_after(time, queue, ^{
            NSLog(@"两秒后的故事。。。。");
        });
        
        
    }
    
    

    结尾

    个人学习的观点以及demo实践,如有不符,欢迎指出,By-小开发

    相关文章

      网友评论

        本文标题:iOS 多线程

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