iOS -- 多线程开发

多线程开发

1.NSThread

2.NSOperation

3.GCD

三种方式是随着iOS的发展逐渐引入的,所以相比而言后者比前者更加简单易用,并且GCD也是目前苹果官方比较推荐的方式(它充分利用了多核处理器的运算性能)。
在iOS中每个进程启动后都会建立一个主线程(UI线程),这个线程是其他线程的父线程。由于在iOS中除了主线程,其他子线程是独立于Cocoa Touch的,所以只有主线程可以更新UI界面.

NSThread

    // 方法1.  对象方法  (手动开启)  
    // (1)创建线程 ,把复杂任务放到子线程中执行  
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(blockThread:) object:nil];  
    // (2) 手动开启  
    [thread start];
       
   // 子线程    
   - (IBAction)blockThread:(id)sender {  
     @autoreleasepool {
        // 凡是子线程执行的方法,都添加到 @autoreleasepool{} 自动释放池
      } 
    }  
  // 方法2.  类方法   (无需手动开启)  
  // 方法2. 创建线程,把复杂任务放到子线程中执行  
  [NSThread detachNewThreadSelector:@selector(blockThread:) toTarget:self withObject:nil];
NSThread总结:

1.每个线程的实际执行顺序并不一定按顺序执行(虽然是按顺序启动);

2.每个线程执行时实际网络状况很可能不一致。当然网络问题无法改变,只能尽可能让网速更快,但是可以改变线程的优先级,让15个线程优先执行某个线程。线程优先级范围为0~1,值越大优先级越高,每个线程的优先级默认为0.5。

3.优先级高的执行,只是说,执行的概率变高,并不是最先执行。

4.使用NSThread在进行多线程开发过程中操作比较简单,但是要控制线程执行顺序并不容易,另外在这个过程中如果打印线程会发现循环几次就创建了几个线程,这在实际开发过程中是不得不考虑的问题,因为每个线程的创建也是相当占用系统开销的。

线程状态

线程状态分为isExecuting(正在执行)isFinished(已经完成)isCancellled(已经取消)三种。其中取消状态程序可以干预设置,只要调用线程的cancel方法即可。但是需要注意在主线程中仅仅能设置线程状态,并不能真正停止当前线程,如果要终止线程必须在线程中调用exist方法,这是一个静态方法,调用该方法可以退出当前线程。

在线程操作过程中可以让某个线程休眠等待,优先执行其他线程操作,而且在这个过程中还可以修改某个线程的状态或者终止某个指定线程。

-(NSData *)requestData:(int )index{ 
//对于多线程操作建议把线程操作放到@autoreleasepool中
 @autoreleasepool{ //对一加载线程休眠2秒 
if (index!=(ROW_COUNT*COLUMN_COUNT-1)) {
 [NSThread sleepForTimeInterval:2.0]; 
 }
 NSURL *url=[NSURL URLWithString:_imageNames[index]]; 
 NSData *data=[NSData dataWithContentsOfURL:url]; return data; 
 }
 }

NSOperation

使用NSOperation和NSOperationQueue进行多线程开发类似于C#中的线程池,只要将一个NSOperation(实际开中需要使用其子类NSInvocationOperation、NSBlockOperation)放到NSOperationQueue这个队列中线程就会依次启动。NSOperationQueue负责管理、执行所有的NSOperation,在这个过程中可以更加容易的管理线程总数和控制线程之间的依赖关系。

NSOperation有两个常用子类用于创建线程操作:NSInvocationOperation和NSBlockOperation,两种方式本质没有区别,但是是后者使用Block形式进行代码组织,使用相对方便。

注意:操作本身只是封装了要执行的相关方法,并没有开辟线程,没有主线程之分,在哪个线程中都能执行。

//  invocate 创建操作
//    NSInvocationOperation *invo = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(blockThread:) object:nil];
    // 开始任务
//    [invo start];
    
    // 1. 创建5个操作
    NSInvocationOperation *invo1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(printString:) object:@"11"];
    NSInvocationOperation *invo2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(printString:) object:@"22"];
    NSInvocationOperation *invo3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(printString:) object:@"33"];
    NSInvocationOperation *invo4 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(printString:) object:@"44"];
    NSInvocationOperation *invo5 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(printString:) object:@"55"];
    
    // 2. NSBlockOperation 创建2个block操作
    NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"666666");
    }];
    NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"777777");
    }];
    
    
    // 3. 创建 操作队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 4. 设置最大并发数
    queue.maxConcurrentOperationCount = 1; // 设置为 1,顺序执行
    
    // 最大并发数控制的是同一时间点,能够执行的任务数。如果为1,则同时执行1个,根据队列FIFO特点,一定是顺序执行。 如果不为1,则可以同时执行多个任务,同时执行任务,称为--并发执行。
    
    // 5. 添加操作--将操作添加到队列
//    [queue addOperation:invo];
    [queue addOperation:invo1];
    [queue addOperation:invo2];
    [queue addOperation:invo3];
    [queue addOperation:invo4];
    [queue addOperation:invo5];
    [queue addOperation:block1];
    [queue addOperation:block2];
    
    // 添加到队列中的任务会自动执行,队列内部会开辟子线程,任务放在子线程中执行。***********************************************
    
    //方法2:直接使用操队列添加操作,eg:block2
    [queue addOperationWithBlock:^{
        NSLog(@"777777");
    }];
}
  • 使用NSBlockOperation方法,所有的操作不必单独定义方法,同时解决了只能传递一个参数的问题。
  • 调用主线程队列的 addOperationWithBlock: 方法进行UI更新,不用再定义一个参数实体。
  • 使用NSOperation进行多线程开发可以设置最大并发线程,有效的对线程进行了控制。

线程执行顺序

使用NSThread很难控制线程的执行顺序,但是使用NSOperation就容易多了,每个NSOperation可以设置依赖线程。假设操作A依赖于操作B,线程操作队列在启动线程时就会首先执行B操作,然后执行A。

// 加载5张图片,优先加载最后一张图的需求,只要设置前面的线程操作的依赖线程为最后一个操作即可。
-(void)loadImageWithMultiThread{
     int count=ROW_COUNT*COLUMN_COUNT; //创建操作队列 
     NSOperationQueue   *operationQueue=[[NSOperationQueue alloc]init]; 
     operationQueue.maxConcurrentOperationCount=5;
     //设置最大并发线程数 
     NSBlockOperation   *lastBlockOperation=[NSBlockOperation blockOperationWithBlock:^{ 
         [self loadImage:[NSNumber numberWithInt:(count-1)]];
     }]; 
     //创建多个线程用于填充图片
     for (int i=0; i<count-1; ++i) { 
         //方法1:创建操作块添加到队列 
         //创建多线程操作 
         NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{ 
            [self loadImage:[NSNumber numberWithInt:i]];
         }];
        //设置依赖操作为最后一张图片加载操作
        [blockOperation addDependency:lastBlockOperation]; 
        [operationQueue addOperation:blockOperation]; 
      } 
      //将最后一个图片的加载操作加入线程队列 
      [operationQueue addOperation:lastBlockOperation]; 
}

加载最后一张图片的操作最后被加入到操作队列,但是它却是被第一个执行的。操作依赖关系可以设置多个,例如A依赖于B、B依赖于C…但是千万不要设置为循环依赖关系(例如A依赖于B,B依赖于C,C又依赖于A),否则是不会被执行的。

GCD

  • GCD(grand Center DisPath) 宏观中心分配(队列).
  • 是苹果开发的一种支持并行操作的机制。它的主要部件是一个FIFO队列和一个线程池,前者用来添加任务,后者用来执行任务。
  • GCD中的FIFO队列称为dispatch queue,它可以保证先进来的任务先得到执行(但不保证一定先执行结束)。
  • GCD 是一个函数级的多线程,用C语言实现的。GCD 中可以分配多个队列,每个队列都具有一定的功能。比如:串行队列,并发队列,分组队列,只执行一次队列。

dispatch queue分为下面两种:

    1. Serial Dispatch Queue -- 线程池只提供一个线程用来执行任务,所以后一个任务必须等到前一个任务执行结束才能开始。
    1. Concurrent Dispatch Queue -- 线程池提供多个线程来执行任务,所以可以按序启动多个任务并发执行。
// 1. 创建一个串行队列
    dispatch_queue_t serialQ = dispatch_queue_create("q1", DISPATCH_QUEUE_SERIAL);
    // 参数1. 队列名称
    // 参数2. 队列类型, 串行 还是 并行
    
    // 2. 给队列添加任务 -(以异步方式添加任务)
    dispatch_async(serialQ, ^{
        NSLog(@"%@",[NSThread currentThread]);
        NSLog(@"建立网络链接");
    });
// 1. 创建一个并行队列
dispatch_queue_t concurrentQ = dispatch_queue_create("eg.gcd.ConcurrentQueue", DISPATCH_QUEUE_CONCURRENT); 

// 创建并行队列的 全局队列
    dispatch_queue_t globleQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
    // 参数1. 队列的优先级, 有4种,默认default
    // 参数2. 预留参数,通常给 0.
    // 注意: 不要使用优先级使一个并行队列,变为一个串行队列。优先级高的执行,只是说,执行的概率变高,并不是最先执行。
     
dispatch_async(concurrentQ, ^{  
    // Code here  
});  
// 释放
dispatch_release(concurrentQ);  

// 并行队列的特点:虽然也遵守FIFO,但是提交时,队列中的任务并不会等待,如果前面的任务没有执行完,不妨碍后面任务的执行。
// 并行队列中会开辟多个子线程。

而系统默认就有一个串行队列main_queue和并行队列global_queue:

dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);  
dispatch_queue_t mainQ = dispatch_get_main_queue();  

通常,我们可以在global_queue中做一些long-running的任务,完成后在main_queue中更新UI,避免UI阻塞,无法响应用户操作.

提交到队列中的不同方式:

  • (1) dispatch_once 这个函数,它可以保证整个应用程序生命周期中某段代码只被执行一次.
static dispatch_once_t onceToken;  
dispatch_once(&onceToken, ^{  
    // code to be executed once  
});
  • (2) dispatch_after 延时执行
// 延迟3s 后,改变背景颜色
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        self.view.backgroundColor = [UIColor redColor];
    });
  • (3) dispatch_apply 执行某个代码片段若干次。
dispatch_apply(10, globalQ, ^(size_t index) {  
    // 参数1. 重复提交的次数
    // 参数2. 提交的队列
    // 参数3. 当前提交的次数  
}); 
  • (4) Dispatch Group 机制监听一组任务是否完成
// 1.创建队列
    dispatch_queue_t concurrentQ = dispatch_queue_create("q1", DISPATCH_QUEUE_CONCURRENT);
    
    // 2. 创建一个组队列
    // 组队列用途-- 把一系列的任务放到同一 组中,再提交到队列。
    dispatch_group_t group = dispatch_group_create();
    
    // 3.添加任务 到组中
    dispatch_group_async(group, concurrentQ, ^{
        NSLog(@"上传照片");
    });
    dispatch_group_async(group, concurrentQ, ^{
        NSLog(@"获取授权");
    });
    dispatch_group_async(group, concurrentQ, ^{
        NSLog(@"保存信息");
    });
    
    // 需求:上面的三个操作可以并发执行,最后提交信息,必须最后执行。
    // 组通知提交方式, 通知提交方式的任务,等到组中,若有任务执行完毕后,才能执行。
    dispatch_group_notify(group, concurrentQ, ^{
        NSLog(@"最后提交信息");
    });
  • (5) dispatch_ barrier_ async 障碍提交
// 障碍提交方式一般用在,并行队列中,当障碍提交方式的任务执行时,后面的任务等待。
// 障碍提交方式:只是当前执行到此任务时,后面的任务等待。被障碍分割的上部分 和 下部分 执行顺序 不确定。 可能是上部分先执行,也可能是下部分先执行。
dispatch_async(concurrentQ, blk0);  
dispatch_async(concurrentQ, blk1);  
 // 添加障碍,执行写入操作,写入没有执行完之前,不允许读取数据。
dispatch_barrier_async(concurrentQ, blk_barrier);  
dispatch_async(concurrentQ, blk2);  
  • (6) dispatch_ async_ f 提交函数
// 声明一个函数
void string(void *s)
{
    printf("%s\n",s);
}

- (IBAction)asyncf:(id)sender {
    
    dispatch_async_f(dispatch_get_main_queue(), "aaaa", string);
    // 参数1. 队列
    // 参数2. 传递给函数的参数
    // 参数3. 函数名
    
}
  • (7) dispatch_sync 同步提交
// 同步提交方式 --提交的block,如果没有执行完成,那么后面的所有代码都不会执行。
// 也就是说,提交操作,在哪个线程中,就会阻塞哪个线程

// 注意:不管同步提交方式是提交到哪个线程,一定会阻塞当前线程,执行也一定是在当前线程中。
dispatch_queue_t conQ = dispatch_queue_create("q1", DISPATCH_QUEUE_CONCURRENT);
    
    // 2. 同步提交 --
    dispatch_sync(conQ, ^{
        
        for (int i = 0; i < 65000; i++) {
            NSLog(@"%@",[NSThread currentThread]);
            NSLog(@"%d",i);
        }
    });

线程互斥:

  • 多个线程同时访问同一个资源,产生的资源争夺问题.
static int ticket = 10;
- (IBAction)threadConflict:(id)sender {
    
    dispatch_queue_t concurrentQ = dispatch_queue_create("q1", DISPATCH_QUEUE_CONCURRENT);
    dispatch_apply(10, concurrentQ, ^(size_t i) {
        [self sellTicket];
    });
}

-(void)sellTicket
{
    // 添加同步锁,如果有一个线程正在访问,那么其他线程等待
    NSLock *lock = [[NSLock alloc] init];
    
    // 加锁
    [lock lock];
    NSLog(@"剩余票数%d",--ticket);
    
    [lock unlock]; // 解锁
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,681评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,710评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,623评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,202评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,232评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,368评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,795评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,461评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,647评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,476评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,525评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,226评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,785评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,857评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,090评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,647评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,215评论 2 341

推荐阅读更多精彩内容

  • 概览 大家都知道,在开发过程中应该尽可能减少用户等待时间,让程序尽可能快的完成运算。可是无论是哪种语言开发的程序最...
    周末年安阅读 1,867评论 1 50
  • NSThread 第一种:通过NSThread的对象方法 NSThread *thread = [[NSThrea...
    攻城狮GG阅读 780评论 0 3
  • 多线程概念 线程线程指的是:1个CPU执行的CPU命令列为一条无分叉路径 多线程这种无分叉路径不止一条,存在多条即...
    我系哆啦阅读 569评论 0 5
  • 一、NSThread 1、创建和启动线程 2、其他创建线程方式 上述2种创建线程方式的优缺点优点:简单快捷缺点:无...
    小辉辉___阅读 600评论 0 18
  • 点斑已经整两周了。 现在脸上还有十来个痂没掉。掉痂后的皮肤呈有些凹陷的黑红色,也不好看,但是比起曾经满脸的黑圈,我...
    一念见花开阅读 136评论 0 0