Objective-C 线程 GCD 使用

1.  概念:

1.1   进程和线程的比较:
线程是 cpu 调用的最小单位
进程是 cpu 分配资源的最小单位
一个进程中至少有一个线程
进程内线程 资源共享  
   windows\mac 进程管理: 通过进程管理器, 可以直接杀死 
   linux 进程管理器:  通过ps | top | pstree | kill 

1.2. 多线程认识: 
     多线程 执行原理: 单核, cpu 多条线程切换, 多线程并发假像 
                多核 ,  
    多线程的缺点: 
      创建 线程有开销,主要是 内核数据结构(大约1KB), 栈空间(子线程512KB,主线程1KB,
      可以使用 setStackSize设置,但是必须是4K倍数,最下是16K), 创建线程大概需要花费
      90毫秒, 
      建议开闭3个线程就可以了,线程越多,开销越大

1.3.  UI线程:主线程
 功能:  刷新UI
      处理UI事件(比如点击、滚动、拖拽事件)
      不能做耗时操作
      线程id 是1  

 1.4.  IOS线程实现方案: 
 pthread:  C的api 移植过来的, 需要手动管理声明周期
NSTrhead:  OC 面向对象, 手动管理生命周期
GCD:  基于c语言的封装,自动管理声明周期
NSOperation:  基于gcd封装

 1.5.   pthread 创建线程


#import "ViewController.h"
#import <pthread.h>

@interface ViewController ()
- (IBAction)btnClick:(id)sender;

@end

@implementation ViewController

- (IBAction)btnClick:(id)sender {
    
    NSLog(@"%@",[NSThread mainThread]);
    //创建线程
    pthread_t thread;
    /*
     第一个参数:线程对象
     第二个参数:线程属性
     第三个参数:void *(*)(void *) 指向函数的指针
     第四个参数:函数的参数
     */
    pthread_create(&thread, NULL, run, NULL);
    
    pthread_t thread1;
    pthread_create(&thread1, NULL, run, NULL);
}
//void *(*)(void *)
void *run(void *param)
{
//    NSLog(@"---%@-",[NSThread currentThread]);
    for (NSInteger i =0 ; i<10000; i++) {
        NSLog(@"%zd--%@-",i,[NSThread currentThread]);
    }
    return NULL;
}

@end

1.6. NSThread 使用

-(void)touchesBegan1:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 1. 线程创建方式1
    //  第一个参数: 目标对象
    //  第二个参数: 选择器,调用哪个方法
    //  第三方参数:  前面方法需要传递的参数
  NSThread* thread  =   [[NSThread alloc] initWithTarget:self
                            selector:@selector(show) object:nil];
    // 线程名称
    thread.name=@"线程1";
    // 线程优先级 默认0.5  最高1.0 ,从 0.0 - 1.0 之间
    [thread setThreadPriority:1.0];
    // 启动线程
    [thread start];
    
    // 2. 线程创建方式2   不用start  但会不能获取线程thread
    [NSThread detachNewThreadSelector:@selector(show) toTarget:self withObject:nil];
    
    // 3. 线程创建方式3  不能获取线thread
    [self performSelectorInBackground:@selector(show) withObject:@"后台线程"];
}
-(void)show{
    NSLog(@"run.....%@",[NSThread currentThread]);
    
}

 2. 线程状态

3.  线程之间通信

-(void)touchesBegan2:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
  [NSThread detachNewThreadSelector:@selector(download) toTarget:self
                         withObject:nil];
    
}
-(void) download{
    // 1. 获取下载地址
    NSURL* url= [NSURL URLWithString:@"http://wimg.spriteapp.cn/cropx/852x480/cutbimage/5ed5b653d19fd.jpg"];
    // 2. 下载图片
    NSData* data= [NSData dataWithContentsOfURL:url];
    
    NSDate *start= [NSDate date];
    CFAbsoluteTime start1= CFAbsoluteTimeGetCurrent();
    
    // 3. 把 二进制图片 转化为图片
    UIImage* image= [UIImage imageWithData:data];
    
    NSDate* end =[ NSDate date];
    CFAbsoluteTime end1 =CFAbsoluteTimeGetCurrent();
    
    // 计算图片下载时间
    NSLog(@"++++++%f",[end timeIntervalSinceDate:start]);
    NSLog(@"++++++%f",(end1-start1));
    // 4. 回到 主线程 刷新UI
    // 1. 方式1
    // YES表示  表示等 showImage执行 完毕,在走下面的方法
    // NO 表示 直接往下走
//    [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone: YES];
 
    // 2.  直接设置 图片
//    [self.myImageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
    
    // 3.  通过 performSelector
    [self.myImageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES ];
}

-(void) showImage:(UIImage*) image{
    self.myImageView.image = image;
}

4.  线程安全 设置 @synchronized

#import "ViewController.h"

@interface ViewController ()
//售票员01
@property (nonatomic, strong) NSThread *thread01;
//售票员02
@property (nonatomic, strong) NSThread *thread02;
//售票员03
@property (nonatomic, strong) NSThread *thread03;

//总票数
@property(nonatomic, assign) NSInteger totalticket;

//@property (nonatomic, strong) NSObject *obj;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    //假设有100张票
    self.totalticket = 100;
//    self.obj = [[NSObject alloc]init];
    //创建线程
   self.thread01 =  [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread01.name = @"售票员01";
    
    self.thread02 =  [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread02.name = @"售票员02";
    self.thread03 =  [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread03.name = @"售票员03";
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    //启动线程
    [self.thread01 start];
    [self.thread02 start];
    [self.thread03 start];
}

//售票
-(void)saleTicket
{
    while (1) {
       
        //2.加互斥锁
        @synchronized(self) {
            [NSThread sleepForTimeInterval:0.03];
            //1.先查看余票数量
            NSInteger count = self.totalticket;
            
            if (count >0) {
                self.totalticket = count - 1;
                NSLog(@"%@卖出去了一张票,还剩下%zd张票",[NSThread currentThread].name,self.totalticket);
            }else
            {
                NSLog(@"%@发现当前票已经买完了--",[NSThread currentThread].name);
                break;
            }
        }
    
    }

}


@end

5.  GCD的使用 

5.1. gcd 
    GCD 为苹果的多核运行提供方案,可以充分使用CPU内核,内部实现线程管理

    gcd 核心  任务 、队列 , 把任务加入队列中即可
    gcd 中所有函数都是 dispatch_ 开头

5.2.  gcd中有2 个用来执行任务的常用  函数

 dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);

同步: 只能在当前线程中执行任务, 不具备开线程的能力,只有一个线程

 dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
异步: 可以在新的线程中执行任务,具备开新线程的能力

 5.3. 队列类型
      并发队列: 可以让多个任务并发执行同时,必须要有多个线程,执行在异步函数中
    
获取并发队列方式1:     dispatch_queue_t  queue= dispatch_queue_create("com.xiaozhi.baidu", DISPATCH_QUEUE_CONCURRENT);

方式2:   dispatch_queue_t queue = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

  串行队列: 一个任务做完以后, 接着下一个任务运行
 dispatch_queue_t  que= dispatch_queue_create("com.xiaozhi.baidu", DISPATCH_QUEUE_SERIAL);

   主队列: dispatch_queue_t que= dispatch_get_main_queue();

  5.4.1 //  并发队列+ 异步函数

//  并发队列+ 异步函数
/**
 输出结果:  开了3个线程
 {number = 8, name = (null)}    新线程
 {number = 7, name = (null)}    新线程
 {number = 9, name = (null)}    新线程
 */
-(void)touchesBegan3:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // gcd 中所有函数 dispatch开头
    
    // 获取并发队列的第一种方法:
    //第一个参数 c 语言字符串
    // 第二个参数:
    //  DISPATCH_QUEUE_CONCURRENT: 并发队列
    //  DISPATCH_QUEUE_SERIAL: 串行队列
//    dispatch_queue_t  queue= dispatch_queue_create("com.xiaozhi.baidu", DISPATCH_QUEUE_CONCURRENT);
    
    // 获取并发队列的第二种方式:
    // 系统提供给了一个获取并发 队列的 方法, 获取全局并发队列
    // 第一个参数: 队列的优先级
    // 第二个参数: 传0
    /*
     #define DISPATCH_QUEUE_PRIORITY_HIGH 2
     #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
     #define DISPATCH_QUEUE_PRIORITY_LOW (-2)
     #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
     */
    dispatch_queue_t queue = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 第一个参数 队列  第二个参数 任务

    dispatch_async(queue, ^{
        NSLog(@"任务1--%@",[NSThread currentThread]);
    });
  
    dispatch_async(queue, ^{
          NSLog(@"任务2--%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
          NSLog(@"任务3--%@",[NSThread currentThread]);
    });
}

  5.4.2  // 异步函数 + 串行 队列

/**
 输出结果:
 {number = 6, name = (null)}
 {number = 6, name = (null)}
 {number = 6, name = (null)}
  开了一个线程, 串行执行
 */
-(void)touchesBegan4:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
      dispatch_queue_t  queue= dispatch_queue_create("com.xiaozhi.baidu", DISPATCH_QUEUE_SERIAL);
    
    
    
    dispatch_async(queue, ^{
           NSLog(@"任务1--%@",[NSThread currentThread]);
       });
     
       dispatch_async(queue, ^{
             NSLog(@"任务2--%@",[NSThread currentThread]);
       });
       
       dispatch_async(queue, ^{
             NSLog(@"任务3--%@",[NSThread currentThread]);
       });
    
}

5.4.3  // 同步函数+ 并发队列 ,不会开线程,在main 线程中执行

/**
  {number = 1, name = main}
  {number = 1, name = main}
  {number = 1, name = main}
 */
-(void)touchesBegan6:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    dispatch_queue_t que = dispatch_get_global_queue(0, 0);
    
    dispatch_sync(que, ^{
           NSLog(@"任务1--%@",[NSThread currentThread]);
    });
    
    
    dispatch_sync(que, ^{
           NSLog(@"任务1--%@",[NSThread currentThread]);
    });
    
    dispatch_sync(que, ^{
           NSLog(@"任务1--%@",[NSThread currentThread]);
    });
}

5.4.4  // 同步函数+ 串行队列 ,不会开线程,在main 线程中执行

// 同步函数+ 串行队列 ,不会开线程,在main 线程中执行
// 结果和上面 相同

-(void)touchesBegan7:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
   dispatch_queue_t  que= dispatch_queue_create("com.xiaozhi.baidu", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(que, ^{
           NSLog(@"任务1--%@",[NSThread currentThread]);
    });
    
    
    dispatch_sync(que, ^{
           NSLog(@"任务1--%@",[NSThread currentThread]);
    });
    
    dispatch_sync(que, ^{
           NSLog(@"任务1--%@",[NSThread currentThread]);
    });
}

5.4.5 获取主队列  就是一个运行UI 线程的 串行队列

// 异步 函数 + 主队列
// 同步函数 +  主队列:卡死
// 为什么会卡死: 加入同步函数以后会立刻执行4个任务,那么会卡死UI任务,死锁
/**
 {number = 1, name = main}
 {number = 1, name = main}
 {number = 1, name = main}
*/
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    // 获取主队列
    dispatch_queue_t que= dispatch_get_main_queue();
    
    
    dispatch_async(que, ^{
              NSLog(@"任务1--%@",[NSThread currentThread]);
       });
       
       
       dispatch_async(que, ^{
              NSLog(@"任务1--%@",[NSThread currentThread]);
       });
       
       dispatch_async(que, ^{
              NSLog(@"任务1--%@",[NSThread currentThread]);
       });
}

5.4.6 队列在实际开发中使用

//  实际开发中 应用
-(void)touchesBegan9:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 1. 获取下载地址
            NSURL* url= [NSURL URLWithString:@"http://wimg.spriteapp.cn/cropx/852x480/cutbimage/5ed5b653d19fd.jpg"];
               // 2. 下载图片
            NSData* data= [NSData dataWithContentsOfURL:url];
               // 3. 把 二进制图片 转化为图片
            UIImage* image= [UIImage imageWithData:data];
           
           dispatch_async(dispatch_get_main_queue(), ^{
               self.myImageView.image= image;
           });
    });
}

6.  gcd 其他函数功能

6.1. 栅栏函数
首先把并发队列中 任务1 和 任务 2 执行完毕  以后, 在执行栅栏 函数
之后再执行 任务3 和 任务

//  栅栏函数
// 首先把并发队列中 任务1 和 任务 2 执行完毕  以后, 在执行栅栏 函数
// 之后再执行 任务3 和 任务4
-(void)touchesBegan10:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    // 并发队列
    dispatch_queue_t  queue= dispatch_queue_create("com.xiaozhi.baidu", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        for(NSInteger i=0;i<10;i++){
            NSLog(@"%zd---download1--%@",i,[NSThread currentThread]);
        }
    });
    
    
    dispatch_async(queue, ^{
        for(NSInteger i=0;i<10;i++){
            NSLog(@"%zd---download2--%@",i,[NSThread currentThread]);
        }
    });
    
    
    dispatch_barrier_sync(queue, ^{
        NSLog(@"----------我是一个栅栏函数-----------");
    });
    
    dispatch_async(queue, ^{
           for(NSInteger i=0;i<10;i++){
               NSLog(@"%zd---download3--%@",i,[NSThread currentThread]);
           }
       });
    
    
    dispatch_async(queue, ^{
              for(NSInteger i=0;i<10;i++){
                  NSLog(@"%zd---download4--%@",i,[NSThread currentThread]);
              }
          });
}

6.2.  延迟 执行 , 一次性质函数

// 延迟 执行 , 一次性质函数
-(void)touchesBegan11:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 1. 在2 s 以后调用 run
 //   [self performSelector:@selector(run) withObject:nil afterDelay:2.0];
    
    // 2. 方式2
//    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self
//                                   selector:@selector(run) userInfo:nil repeats:NO];
    
    // 3. 方式3
    // 在主线中执行
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"main===%@", [NSThread currentThread]);
    });
    
    // 在子线程中执行
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
          NSLog(@"child===%@", [NSThread currentThread]);
      });
    
    // 程序启动过程中执行一次,以后都不会执行了,一次性代码
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
    });
}

-(void)run{
    NSLog(@"%@",@"run");
}

6.3.   快速 迭代函数

案例1 :比较  for循环和 快速迭代函数

-(void)touchesBegan12:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 在 main  线程中 串行 执行  从  0 -10
    // 缺点,在main 线程中做耗时操作如果图片比较大,单线程串行执行
//        for (NSInteger i=0; i<10; i++) {
//        NSLog(@"%zd--%@",i,[NSThread currentThread]);
//    }
    
    // 快速迭代函数, 多个线程并发执行
    
    
        //创建队列(并发队列),穿件多个队列线程并发执行
    dispatch_queue_t queue = dispatch_queue_create("com.downloadqueue", DISPATCH_QUEUE_CONCURRENT);
    /*
     第一个参数:迭代的次数
     第二个参数:在哪个队列中执行
     第三个参数:block要执行的任务
     */
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"%zd--%@",index,[NSThread currentThread]);
   });
}

案例2:  实际应用,快速迭代函数, 文件拷贝

// from 文件夹 到 to 文件夹
// 缺点: 在主线程中比较 耗时
-(void)moveFile
{
    //文件在哪个地方(文件夹)
    NSString *form = @"/Users/denganzhi/Desktop/from";
    //要剪切到什么地方
    NSString *to = @"/Users/denganzhi/Desktop/to";
    
    NSFileManager *manager = [NSFileManager defaultManager];
    NSArray *subpaths =  [manager subpathsAtPath:form];
//    NSLog(@"%@",subpaths);
    
    NSInteger count = [subpaths count];
    
    for (NSInteger i = 0; i<count; i++) {
        //拼接文件全路径
//        NSString *fullPath = [form stringByAppendingString:c]
        
        NSString *subpath = subpaths[i];
        
        NSString *fullPath = [form stringByAppendingPathComponent:subpath];
        
        //拼接目标文件全路径
        NSString *fileName = [to stringByAppendingPathComponent:subpath];
        
        //剪切操作
        [manager moveItemAtPath:fullPath toPath:fileName error:nil];
        
        NSLog(@"%@--%@",fullPath,fileName);
    }
}

/// 快速 迭代函数 ,开启多个子线程 ,实现快速拷贝
/**
 输出的线程:
 {number = 7, name = (null)}
 {number = 6, name = (null)}
 {number = 4, name = (null)}
 {number = 1, name = main}
 */
-(void)moveFile1
{
  //文件在哪个地方(文件夹)
    NSString *form = @"/Users/denganzhi/Desktop/from";
    //要剪切到什么地方
    NSString *to = @"/Users/denganzhi/Desktop/to";
    
    NSFileManager *manager = [NSFileManager defaultManager];
    NSArray *subpaths =  [manager subpathsAtPath:form];

    //创建队列(并发队列)
    dispatch_queue_t queue = dispatch_queue_create("com.downloadqueue", DISPATCH_QUEUE_CONCURRENT);

    NSInteger count = [subpaths count];
    dispatch_apply(count, queue, ^(size_t index) {

        NSString *subpath = subpaths[index];

        NSString *fullPath = [form stringByAppendingPathComponent:subpath];

        //拼接目标文件全路径
        NSString *fileName = [to stringByAppendingPathComponent:subpath];

        //剪切操作
        [manager moveItemAtPath:fullPath toPath:fileName error:nil];

        NSLog(@"%@",[NSThread currentThread]);
    });
}
 

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self moveFile1];
}

6.4. 队列组:  类似RxJava任务合并

-(void)group
{
    //下载图片1
    
    //创建队列组
    dispatch_group_t group =  dispatch_group_create();
    
    //1.开子线程下载图片
    //创建队列(并发)
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_async(group, queue, ^{
        //1.获取url地址
        NSURL *url = [NSURL URLWithString:@"http://img5.mtime.cn/mg/2019/06/27/231348.59732586_120X90X4.jpg"];
        
        //2.下载图片
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        //3.把二进制数据转换成图片
        self.image1 = [UIImage imageWithData:data];
        
        NSLog(@"1---%@",self.image1);
    });
  
    //下载图片2
    dispatch_group_async(group, queue, ^{
        //1.获取url地址
        NSURL *url = [NSURL URLWithString:@"http://img5.mtime.cn/mg/2019/06/27/224744.68512147_120X90X4.jpg"];
        
        //2.下载图片
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        //3.把二进制数据转换成图片
        self.image2 = [UIImage imageWithData:data];
        NSLog(@"2---%@",self.image2);
        
    });
    
    //合成, 2 张图片 都下载完毕
    dispatch_group_notify(group, queue, ^{
        
        //创建一个基于位图的上下文 默认透明,一个画布
        //1. 开启图形上下文
        UIGraphicsBeginImageContext(CGSizeMake(200, 200));
        
        //画1,把图1 画上去
        [self.image1 drawInRect:CGRectMake(0, 0, 200, 100)];
        //画2,把图2 画上去
        [self.image2 drawInRect:CGRectMake(0, 100, 200, 100)];
        
        //2. 根据图形上下文拿到图片,得到合成的图片
        UIImage *image =  UIGraphicsGetImageFromCurrentImageContext();
        
        //3. 关闭上下文
        UIGraphicsEndImageContext();
        
        dispatch_async(dispatch_get_main_queue(), ^{
            self.myImageView.image = image;
            NSLog(@"%@--刷新UI",[NSThread currentThread]);
        });
    });
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    [self group];
}

合成图片效果: 

猜你喜欢

转载自blog.csdn.net/dreams_deng/article/details/106522897