iOS 多线程处理 ----NSThread, NSOperation,GCD 2019-06-26

一个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程)     

线程 : 执行任务的单元片段叫做线程,也就是真正的任务执行者,只不过系统默认把任务交给主线程来做. 大多时候为了提高用户体验需要把耗时的任务交给子线程 来做.

一个进程是由一个或多个线程组成.进程只负责资源的调度和分配.线程才是 程序的执行单元,负责代码的执行. 每个正在运行的程序至少包含一个线程(即主线程),该线程在程序启动时被创建用于执行mian函数
在多线程方法中为保证对象的即使释放,需要为每个方法手动添加自动释放池
iOS中关于UI的添加和刷新必须在主线程中操作
//多线程

1. NSthread
    优点: NSThread 比其他两个轻量级
    缺点 : 要自己管理线程的生命周期,线程同步.线程同步对数据的加锁会有系统开销
2. NSOperation
    优点: 不需要关心线程管理,数据同步的事情可以把精力放在自己需要操作的地方
 3. GCD 
    //优点: 集合了替代 NSThread, NSOperationQueue,NSInvocationOperation等的高效强大技术,使用更方便

一 . NSThread

①使用NSThread的 类方法 创建子线程
在viewDidLoad方法创建线程
 [NSThread detachNewThreadSelector:@selector(handleNetWorkRequestImage1) toTarget:self withObject:nil];
-(void) handleNetWorkRequestImage1 {
    @autoreleasepool {
        NSURL *url = [NSURL URLWithString:@"http://g.hiphotos.baidu.com/image/h%3D360/sign=4f75daaec35c10383b7ec8c48210931c/2cf5e0fe9925bc31fa45db2c5bdf8db1cb13706e.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
         UIImage *image = [UIImage imageWithData:data];
        //主线程跳转到子线程执行任务时,会直接创建子线程,执行耗时操作时直接使用该方法即可
        //当子线程执行完任务后,接下来的界面刷新操作等应交由主线程操作,使用performSelectorOnMainThread:方法操作
        //从子线程回到主线程执行任务
        [self performSelectorOnMainThread:@selector(refreashUIFirst:) withObject:image waitUntilDone:YES];
    }
}
-(void)refreashUIFirst:(UIImage *)image{
    self.imageShowFirst.image = image;
}
//② 使用NSThread 对象的--alloc-- init 初始化方法创建子线程
[[[NSThread alloc] initWithTarget:self selector:@selector(handleNetWorkImageRequest2) object:nil] start]
-(void) handleNetWorkImageRequest2{
    @autoreleasepool {
        NSURL *url = [NSURL URLWithString:@"http://images.enet.com.cn/egames/articleimage/201112/20111208025418685.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
         UIImage *image = [UIImage imageWithData:data];
      //返回主线程刷新界面
      [self performSelectorOnMainThread:@selector(refreashUISecond:) withObject:image waitUntilDone:YES];
    }
}
//刷新界面
-(void)refreashUISecond:(UIImage *)image{
    self.imageShowSecond.image = image;
}

⭐️注意 : 初始化方法创建子线程时要通过手动开启 start 和取消 cancel 子线程或者结束线程[NSThread exit]

线程互斥是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的

当多个线程同时访问一个资源时会产生线程互斥的问题,如何解决NSThread的线程同步互斥问题呢?
(1)加锁NSLock 或NSCondition (2)使用@Synchronized
****************卖票问题 解决NSThread的线程同步互斥问题********************

创建票数属性 totalTickets , 在viewDidLoad方法中添加两个线程

    totalTickets = 100;//初始化票的总张数
    self.lock = [[NSLock alloc] init];
    //窗口1
    [NSThread detachNewThreadSelector:@selector(sellTicketsWithName:) toTarget:self withObject:@"张三"];
    //窗口2
    [NSThread detachNewThreadSelector:@selector(sellTicketsWithName:) toTarget:self withObject:@"李四"];
     -(void)sellTicketsWithName:(NSString *)name{
    @autoreleasepool {
        while (YES) {
            [self.lock lock];//线程加锁
            if (totalTickets > 0) { //卖票
                [NSThread sleepForTimeInterval:0.09];
                totalTickets --;
                NSLog(@"%@卖的票,剩余%ld张" ,name,totalTickets);
            }else{//没票
                NSLog(@"%@ 票买完了",name);
                break;
            }
            [self.lock unlock];//解锁
            //线程死锁:临界资源缺少解锁,就会造成死锁,其他线程一致等待前一个线程解锁
    
           /*
            @synchronized(name) {
                if (totalTickets > 0) {
                    //卖票
                    [NSThread sleepForTimeInterval:0.09];
                    totalTickets --;
                    NSLog(@"%@卖的票,剩余%ld张" ,name,totalTickets);
                }else{
                    //没票
                    NSLog(@"%@ 票买完了",name);
                    break;
                }
            }
          */
        }
    }
}
       

二 .NSObject

//创建 异步后台执行 子线程,使用NSobject提供的方法

[self performSelectorInBackground:@selector(handleImageRequest3) withObject:nil];
-(void) handleImageRequest3{
    @autoreleasepool {
        NSURL *url = [NSURL URLWithString:@"http://image.tianjimedia.com/uploadImages/2012/243/8RM0WDLRMWNA.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
         UIImage *image = [UIImage imageWithData:data];
 //从子线程回到主线程执行任务
          [self performSelectorOnMainThread:@selector(refreashUIThird:) withObject:image waitUntilDone:YES];
    }
}

-(void)refreashUIThird:(UIImage *)image{
    self.imageShowThird.image = image;
}

三 .NSOperation

NSOperation 类在MVC中属于M,是用来封装单个任务相关的代码和数据的抽象出来的类, 他只是一个操作没有主线程,子线程之分,本身与多线程没有任何关系, 通常不直接使用而是使用其子类(NSInvocationOperation或NSBlockOperation)

使用 操作队列 NSOperationQueue来 管理一组 Operation对象 ,根据需要为 operation 开辟合适数量的线程 实现任务的并行执行
 1.线程同步 : 同步执行, 任务之间存在先后顺序,后一任务在前一任务完成后才执行
  /* 线程同步存在两种方法:
    第一种 : 设置线程并发数
    第二种 : 设置多任务的依赖关系
  */   
 2.线程并发 : 异步执行(不需要设置并发数),任务之间没有先后顺序,先执行的可能最后结束

(3.1) NSInvocationOperation 封装了执行操作的target和要执行的action

 NSInvocationOperation *operation1 =  [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleNetWorkRequestImage1) object:nil];
    NSInvocationOperation *operation2 =  [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleNetWorkImageRequest2) object:nil];
    //创建任务队列,来操作任务的执行
        //① 设置线程并发数为1 ,实现同步执行
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue setMaxConcurrentOperationCount:1];
    [queue addOperation:operation1];
    [queue addOperation:operation2];

(3.2) NSBlockOperation 封装了要执行的代码块

NSBlockOperation * operation3 = [NSBlockOperation blockOperationWithBlock:^{
        [self handleImageRequest3];
    }];
    NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
        [self handleImageRequestFromNet4];
    }];

//网络请求图片
-(void) handleImageRequest3{
    @autoreleasepool {
        NSURL *url = [NSURL URLWithString:@"http://image.tianjimedia.com/uploadImages/2012/243/8RM0WDLRMWNA.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
         UIImage *image = [UIImage imageWithData:data];
        //从子线程回到主线程执行任务
          [self performSelectorOnMainThread:@selector(refreashUIThird:) withObject:image waitUntilDone:YES];
    }
}
-(void) handleImageRequestFromNet4{
    @autoreleasepool {
        NSURL *url = [NSURL URLWithString:@"http://f.hiphotos.baidu.com/image/h%3D200/sign=a4d0f7d46409c93d18f209f7af3ff8bb/024f78f0f736afc314f682bfb019ebc4b6451275.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];
          [self performSelectorOnMainThread:@selector(refreashUIFourth:) withObject:image waitUntilDone:YES];
    }
}
//刷新界面
-(void)refreashUIThird:(UIImage *)image{
    self.imageShowThird.image = image;
}
-(void)refreashUIFourth:(UIImage *)image{
    self.imageShowFourth.image = image;
}

四 .GCD处理多线程

GCD的工作原理是:让程序平行排队的特定任务,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务。任务可以是一个函数(function)或者是一个block。 GCD的底层依然是用线程实现,不过这样可以让程序员不用关注实现的细节。 
GCD完全可以处理诸如 数据锁定和资源泄漏等复杂的异步编程问题 
//串行队列
- (IBAction)chuanXing:(UIButton *)sender {
//1.创建串行队列
    //(1)获取系统创建好的串行队列,在主线程中实现线程同步
    dispatch_queue_t queue1 = dispatch_get_main_queue();
    //(2)自己创建串行队列,任务在子线程是线程同步
    //参数一 : 队列名称 也是队列的唯一标示,苹果建议采用反域名形式编写
    //参数二 : 指定为什么类型的队列
    //DISPATCH_QUEUE_SERIAL ------指定为串行队列
    dispatch_queue_t  queue2 = dispatch_queue_create("com.lanou3g.www", DISPATCH_QUEUE_SERIAL);
//2.往队列中添加任务
    dispatch_async(queue2, ^{
        NSLog(@"任务一%@",[NSThread currentThread]);
    });
    dispatch_async(queue2, ^{
        NSLog(@"任务二%@",[NSThread currentThread]);
    });
    dispatch_async(queue2, ^{
        NSLog(@"任务三%@",[NSThread currentThread]);
    });
    dispatch_async(queue2, ^{
        NSLog(@"任务四%@",[NSThread currentThread]);
    });
    //释放 ------MRC
//    dispatch_release(queue2);

}
//并行队列
- (IBAction)bingXing:(UIButton*)sender {
//1.创建并行队列
    //(1)使用系统创建好的并行队列
    //参数一 : 优先级 系统提供四种
    //参数二 : 预留参数 现在未使用 给 0 即可
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //(2)自己创建并发队列
    dispatch_queue_t  queue3 = dispatch_queue_create("com.lanou3g.www", DISPATCH_QUEUE_CONCURRENT);
    //往队列中添加任务
    dispatch_async(queue3, ^{
        NSLog(@"任务一%@",[NSThread currentThread]);
    });
    dispatch_async(queue3, ^{
        NSLog(@"任务二%@",[NSThread currentThread]);
    });
    dispatch_async(queue3, ^{
        NSLog(@"任务三%@",[NSThread currentThread]);
    });
    dispatch_async(queue3, ^{
        NSLog(@"任务四%@",[NSThread currentThread]);
        //请求到数据后要回到主线程刷新界面
        dispatch_async(dispatch_get_main_queue(), ^{
            //此处写想在主线程中执行的代码段  --- 比如:刷新UI界面等操作
        });
    });
    //MRC 时释放
    // dispatch_release(queue3);

}
//分组队列

- (IBAction)fenZu:(UIButton *)sender {
//1.创建并行队列
    dispatch_queue_t queue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //2. 创建分组
    dispatch_group_t group = dispatch_group_create();
    //3.往分组队列添加任务
    dispatch_group_async(group, queue4, ^{
        NSLog(@"任务一  ,请求0-10M的数据");
    });
    dispatch_group_async(group, queue4, ^{
        NSLog(@"任务二 ,请求10-20M的数据");
    });
    dispatch_group_async(group, queue4, ^{
        NSLog(@"任务三 ,请求20-30M的数据");
    });
    dispatch_group_async(group, queue4, ^{
        NSLog(@"任务四 ,请求20-40M的数据");
    });
    //当分组所有任务完成后出发的方法
    dispatch_group_notify(group, queue4, ^{
        //数据拼接
        NSLog(@"数据拼接");
    });
    //MRC 时释放
    // dispatch_release(group);
}
//一次
- (IBAction)Once:(UIButton *)sender {

}

//障碍队列
- (IBAction)zhangAi:(UIButton *)sender {
//障碍任务的作用 : 可以保证障碍之后的并发的任务在障碍之后并发的任务执行完毕之后去执行
    //注意: 如果添加障碍任务必须使用自己创建的并发队列
    //1.创建并发队列
    dispatch_queue_t quee = dispatch_queue_create("com.lanou.henan", DISPATCH_QUEUE_CONCURRENT);
    //2.往队列中添加任务
    dispatch_async(quee, ^{
        NSLog(@" A 写入");
    });
    dispatch_async(quee, ^{
        NSLog(@" B 写入");
    });
    dispatch_async(quee, ^{
        NSLog(@" C 写入");
    });
    dispatch_async(quee, ^{
        NSLog(@" D 写入");
    });
    //添加障碍任务
    dispatch_barrier_async(quee, ^{
        NSLog(@"此处是坑,障碍");
    });
    
    dispatch_async(quee, ^{
        NSLog(@" A 读取");
    });
    dispatch_async(quee, ^{
        NSLog(@" B 读取");
    });
    dispatch_async(quee, ^{
        NSLog(@" C 读取");
    });
    dispatch_async(quee, ^{
        NSLog(@" D 读取");
    });
    //MRC ---释放
    //dispatch_release(quee);
}

//延迟
- (IBAction)yanChi:(UIButton *)sender {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"蛮王好持久啊啊啊!");
    });
    //dispatch_get_main_queue()在主线程中执行  如果想在子线程执行, 此处改为子线程即可
}
//重复执行
- (IBAction)chongFu:(UIButton *)sender {
    dispatch_queue_t quuee = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_apply(10, quuee, ^(size_t index) {
        NSLog(@"反复执行的次数 %ld , 当前线程 %@",index,[NSThread currentThread]);
   //注意: size_t之后手写上参数名 比如 index
        //重复的任务在执行的过程中至少一次是在主队列中
    });
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容