iOS多线程总结

注:本文是根据慕课网上的多线程视频总结而来.
iOS 多线程主要有四种方式:
1.pThread 2.NSThread 3.GCD 4.NSOpertation
<一> pThread
pThread 是基于 C++语言实现的,在 iOS 项目内一般很少使用,在使用这种方式的时候,需要导入<pthread.h>系统头文件.
下面是使用方式:

- (IBAction)pThread:(id)sender {
    NSLog(@"pThread___主线程");
    pthread_t pthread;
    //第一个参数为 pthread 指针,第三个参数run 是任务名称(可理解为方法名称)
    pthread_create(&pthread, NULL, run, NULL); 
}

void *run(void *data) {
    NSLog(@"pThread___子线程");
    for (int i = 1; i < 10; i++) {
        NSLog(@"pThread === %d",i);
        sleep(1);
    }
    return NULL;
}

<二> NSThread
NSThread 是苹果封装过的面向对象的一种多线程方式.
NSThread 有三种创建方式,如下:

1.通过 alloc init 方式创建
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(runNSThread) object:nil];
2.通过 detachNewThreadSelector 方式创建并执行线程
[NSThread detachNewThreadSelector:@selector(runNSThread) toTarget:self withObject:nil];
3.通过 performSelectorInBackground 方式创建并执行线程
[self performSelectorInBackground:@selector(runNSThread) withObject:nil];

NSThread的三种创建方式种,方式一可以对线程属性进行设置,便于后期调试或业务逻辑的判断,后两种方式的优势是格式比较简单,方便使用.

关于方式一的线程属性设置:

    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(runNSThread) object:nil];
    //设置线程名称,可根据线程名称做业务判断
    [thread1 setName:@"Name_thread_1"];
    //设置线程优先级,其值介于 0 ~ 1 之间,注意:优先级的设置并不代表高优先级线程执行完毕之后才执行低优先级的线程.高优先级只是优先执行,在高优先级线程执行期间,低优先级线程也会进行执行.
    [thread1 setThreadPriority:0.2];
    [thread1 start];
    
    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(runNSThread) object:nil];
    [thread2 setName:@"Name_thread_2"];
    [thread2 setThreadPriority:0.5];
    [thread2 start];

NSThread 回到主线程的方式为:

[self performSelectorOnMainThread:@selector(runMainThread) withObject:nil waitUntilDone:YES];

<三> GCD
GCD是苹果为多核设备设计的多线程解决方案,可以合理的利用设备的 CPU 性能.iOS 开发中经常使用 GCD 这种方式.

GCD 的常见用法:

dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"start task_1");
        [NSThread sleepForTimeInterval:3]; //模拟耗时任务
        
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"task_1 end, refresh UI");
        });
    });

GCD 的全局并发队列

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
         NSLog(@"start task_1");
         [NSThread sleepForTimeInterval:2];
         NSLog(@"task_1 end");
     });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        NSLog(@"start task_2");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"task_2 end");
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"start task_3");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"task_3 end");
    });

GCD 的串行队列

//串行队列,将所有任务都放在同一个线程内,先进先出,保证执行顺序 (串行队列只开辟了一个线程)
    //dispatch_queue_create(_,_) 第一个参数是线程标识符, 第二个参数是设置该线程是 串行 还是并行,默认(NULL)为串行(DISPATCH_QUEUE_SERIAL),并行为 DISPATCH_QUEUE_CONCURRENT

    dispatch_queue_t queue = dispatch_queue_create("dj.queue",  DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"start task_1");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"task_1 end");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"start task_2");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"task_2 end");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"start task_3");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"task_3 end");
    });

当需要实现多个任务全部执行完毕后再执行下一步操作时,我们可以使用 GCD 的 group 功能.

dispatch_queue_t queue = dispatch_queue_create("dj.gcd.group", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"start task_1");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"task_1 end");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"start task_2");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"task_2 end");
    });

    dispatch_group_async(group, queue, ^{
        NSLog(@"start task_3");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"task_3 end");
    });

    dispatch_group_notify(group, queue, ^{
        //所有任务结束后的回调
        NSLog(@"All tasks end");
        
        //回调所用的线程并不是新开辟的线程,系统在这里做了优化,使用了group多个任务线程的其中一个(随机选择)线程来做回调.故刷新 UI 需要回到主线程.
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"回到主线程刷新 UI");
        });
    });

上面的方案只有在执行同步操作时才会生效,当我们执行异步操作时,会发现在任务执行结束前,我们就已经收到了 notify 的回调,这是因为我们在执行异步操作时,代码会脱离dispatch_group_async 的范围,导致提前收到 notify 回调.
下面是在 group 内执行异步操作时的解决方案.

//解决 group 在执行异步代码时遇到的提前收到 notify 回调的问题
    dispatch_queue_t queue = dispatch_queue_create("dj.gcd.group", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_enter(group); //进入
    dispatch_group_async(group, queue, ^{
        [self sendRequest1:^{
            NSLog(@"request1 done");
            dispatch_group_leave(group); //离开
        }];
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        [self sendRequest2:^{
            NSLog(@"request2 done");
            dispatch_group_leave(group);
        }];
    });
    
    dispatch_group_notify(group, queue, ^{
        //所有任务结束后的回调
        NSLog(@"All tasks end");
        
        //回调所用的线程并不是新开辟的线程,系统在这里做了优化,使用了group多个任务线程的其中一个(随机选择)线程来做回调.故刷新 UI 需要回到主线程.
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"回到主线程刷新 UI");
        });
    });
}

- (void)sendRequest1:(void(^)())block {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"start task_1");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"task_1 end");
        
        dispatch_async(dispatch_get_main_queue(), ^{
            if (block) {
                block();
            }
        });
    });
}

- (void)sendRequest2:(void(^)())block {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"start task_2");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"task_2 end");
        
        dispatch_async(dispatch_get_main_queue(), ^{
            if (block) {
                block();
            }
        });
    });
}

有时候我们需要在项目中执行延时操作,这时我们可以使用 dispatch_after 功能来实现.

//GCD 延迟操作
- (IBAction)GCDAfter:(id)sender {
    NSLog(@"---begin---");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"delay excute");
        //GCDAfter 弊端:当调用dispatch_after时,该方法无法取消,必被执行. 例如:采用 dispatch_after 在 2秒后执行操作,但在 2秒未到时退出界面,那么dispatch_after操作仍会被执行.此时由于控制器已被 dealloc, 那么调用 dispatch_after 的业务逻辑就可能会造成程序 crash.
    });
}

GCD 的另一个常用功能是在单例中的使用

+(instancetype)instance {
    static dispatch_once_t onceToken;
    
    static TestSingle *single = nil;
    dispatch_once(&onceToken, ^{
        single = [[TestSingle alloc] init];
        
        NSLog(@"init testSingle");
    });
    
    return single;
}

<四> NSOperation
NSOperation 里常用的包括:NSInvocationOperation, NSBlockOperation, NSOperationQueue.
NSOperation有两种执行方式,第一种是采用 start 的方式执行,该方式是同步执行,会阻塞当前所在线程(主线程或子线程). 第二种方式是采用 NSOperationQueue 的方式,该方式是异步执行.

  1. NSInvocationOperation
NSInvocationOperation *invocationOper = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(method name) object:nil];
[invocationOper start]; //以start方式执行时是同步执行,会阻塞当前所在线程(主线程或子线程)
  1. NSBlockOperation
NSBlockOperation *blockOper = [NSBlockOperation blockOperationWithBlock:^{
        //do something
    }];
[blockOper start]; //以start方式执行时是同步执行,会阻塞当前线程(主线程或子线程)

3.NSOperationQueue

@property (nonatomic, strong) NSOperationQueue *opertationQueue;
if (!self.opertationQueue) {
        self.opertationQueue = [[NSOperationQueue alloc] init];线程数
    }
[self.opertationQueue addOperation:blockOper]; //通过NSOperationQueue 方式执行时,是异步执行

NSOperationQueue可以设置最大同时执行的线程数

[self.opertationQueue setMaxConcurrentOperationCount:4];

4.自定义 NSOperation

   //自定义
    CustomOpertation *cusA = [[CustomOpertation alloc] initWithName:@"operA"];
    CustomOpertation *cusB = [[CustomOpertation alloc] initWithName:@"operB"];
    CustomOpertation *cusC = [[CustomOpertation alloc] initWithName:@"operC"];
    CustomOpertation *cusD = [[CustomOpertation alloc] initWithName:@"operD"];
    
    //依赖关系的设置  [A addDependency:B];  A 的执行依赖于 B, 需要等待 B 执行结束后才执行.使用依赖时需注意避免循环依赖.
    [cusD addDependency:cusA];
    [cusA addDependency:cusC];
    [cusC addDependency:cusB];
    
    [self.opertationQueue addOperation:cusA];
    [self.opertationQueue addOperation:cusB];
    [self.opertationQueue addOperation:cusC];
    [self.opertationQueue addOperation:cusD];
#import "CustomOpertation.h"

@interface CustomOpertation ()

@property (nonatomic, copy) NSString *operName;
@property (nonatomic, assign) BOOL over;
@end

@implementation CustomOpertation

- (instancetype)initWithName:(NSString *)name {
    if (self = [super init]) {
        self.operName = name;
    }
    return self;
}

- (void)main {
    //模拟耗时操作
//    for (int i = 0; i < 3; i++) {
//        NSLog(@"%@ %d",self.operName, i);
//        [NSThread sleepForTimeInterval:1];
//    }
    
    //模拟异步操作,此时 main 函数很快执行完毕,则依赖关系也失效
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [NSThread sleepForTimeInterval:1];
        if (self.cancelled) {
            return ;
        } else {
            NSLog(@"%@",self.operName);
            
            //关闭异步操作
            self.over = YES;
        }
    });
    
    //占用异步操作,使main 函数无法立即结束(避免依赖关系失效)
    while (!self.over && !self.cancelled) {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }
}

5.多线程资源抢占的解决方案

#import "TicketManager.h"

#define total 50
@interface TicketManager()

@property int tickets; //剩余票数
@property int saleCount; //已卖票数
@property (nonatomic, strong) NSThread *threadBJ; //线程1
@property (nonatomic, strong) NSThread *threadSH; //线程2
@property (nonatomic, strong) NSCondition *ticketCondition; //加锁操作
@end
@implementation TicketManager

- (instancetype)init {
    if (self = [super init]) {
        self.ticketCondition = [[NSCondition alloc] init];
        self.tickets  = total;
        self.threadBJ = [[NSThread alloc] initWithTarget:self selector:@selector(sale) object:nil];
        [self.threadBJ setName:@"BJ"];
        
        self.threadSH = [[NSThread alloc] initWithTarget:self selector:@selector(sale) object:nil];
        [self.threadSH setName:@"SH"];
    }
    return self;
}

- (void)sale {
    /**
     多个线程同时操作一个数据时,可能会造成数据混乱,此时可通过加锁的方式对数据进行保护,使之每次只能有一个线程对数据进行操作,其他线程需等待操作数据的线程操作结束后才能对数据进行操作.
     
     线程锁有多种方式,常见的有: @synchronized (self) {} 及 NSCondition 两种方式.
     synchronized保护其内部的数据操作
     NSCondition 在操作前通过 lock 方法加锁,在操作完成后通过 unlock 进行解锁. lock 与 unlock 必须一一对应.
     */
    while (true) {
        //@synchronized (self) { //加锁
            [self.ticketCondition lock]; //加锁 --- NSCondition
            
            if (self.tickets > 0) {
                [NSThread sleepForTimeInterval:0.5];
                self.tickets--;
                self.saleCount = total - self.tickets;
                
                NSLog(@"余票 === %d , 卖出票数 === %d  ,线程名称 === %@",self.tickets,self.saleCount,[NSThread currentThread].name);
            }
            [self.ticketCondition unlock]; //解锁 --- NSCondition
        //}
    }
}

- (void)startToSaleTickets {
    [self.threadBJ start];
    [self.threadSH start];
}

@end

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

推荐阅读更多精彩内容

  • 进程 什么是进程 进程是指在系统中正在运行的一个应用程序 每个进程之间是独立的,每个进程均运行在其专用且受保护的内...
    45b645c5912e阅读 466评论 0 5
  • 目录 简述 NSThread GCD操作与队列异步操作并行队列同步操作并行队列同步操作串行队列异步操作串行队列队列...
    鱼王00阅读 497评论 0 2
  • NSThread 第一种:通过NSThread的对象方法 NSThread *thread = [[NSThrea...
    攻城狮GG阅读 795评论 0 3
  • 首先看下高德默认的小蓝点效果 再来看下我们产品要实现的效果 要自定义定位点,首先要展示当前定位点,用到这个属性,设...
    SAW_阅读 10,840评论 24 28
  • 七月的风,热烈而轻狂,在塔里木的土地上肆虐,吹散了黄沙,吹不尽荒凉。 塔里木,遥远的如一个传说、苍凉的如一个梦...
    娟娟新月阅读 1,549评论 84 90