iOS多线程

先了解一下进程和线程吧,面试也经常问到。

  • 进程:是系统进行资源分配和调度的一个独立单位,拥有独立的内存空间,
  • 线程:线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源。
  • 关系:一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行。相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
  • 区别:进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
    1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
  1. 线程的划分尺度小于进程,使得多线程程序的并发性高。
  2. 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
  3. 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
  4. 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
  • 优缺点: 线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。
1.iOS中的多线程比较

类型|特点
---|---|---
NSThread |生命周期需要自己管理,不够方便使用,一般用来获取当前线程使用。
NSOperation|父类,抽象类,不能直接使用 基GCD的封装,使用起来面向对象一些。生命周期不用自己管理。可以设置依赖关系控制线程先后执行的顺序
GCD|基于C语言,功能强大。在block回调中执行任务,使用方便。线程执行后不能取消,生命周期不用管理

2. NSThread
  • 类方法
 //新开线程,在block中执行任务
 [NSThread detachNewThreadWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);

    }];
 //新开线程,在@selector响应方法中执行任务
 [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];

 -(void)run
 {
    NSLog(@"%@",[NSThread currentThread]);
   //退出当前线程   
   [NSThread exit];
 }
  • 对象方法
//返回创建的线程,在block中执行任务
NSThread *thread = [[NSThread alloc] initWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
//要start才能开始执行任务
 [thread start];

//新开线程,在@selector响应方法中执行任务
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread) object:nil];
[self.thread start];

 -(void)newThread
 {
       NSLog(@"newThread%@",[NSThread currentThread]);
       //退出当前线程   
       [NSThread exit];
  }

  • 直接self调用
  [self performSelector:@selector(run) onThread:self.thread withObject:nil waitUntilDone:YES];
  [self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];
2. NSOperation
  • NSOperation :父类,抽象类,不能直接使用。

  • NSOperationQueue : 队列

  • NSBlockOperation: 线程,在block回调里面处理任务

  • NSInvocationOperation : 线程,在@selector方法里面调用任务

  • NSOperationQueuePriority : 线程优先级

    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8

//创建block回调线程任务
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
       NSLog(@"blockOperation:%@",[NSThread currentThread]);
 }];
//创建在@selector方法里面调用线程任务
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation) object:nil];

//创建队列,然后将线程任务添加到队列中,自动执行
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//设置队列的最大并发线程量。默认是NSOperationQueueDefaultMaxConcurrentOperationCount
queue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount;

//设置依赖关系,invocationOperation依赖blockOperation,即是invocationOperation在blockOperation执行完成之后再执行,如果不设置的话就是并发执行。
//[invocationOperation addDependency:blockOperation];
//添加多条线程到队列中
[queue addOperations:@[blockOperation,invocationOperation] waitUntilFinished:YES];

//单独添加一条线程
//    [queue addOperation:invocationOperation];
//    [queue addOperation: blockOperation];

-(void)invocationOperation
{
   //加线程锁,单一线程资源访问,不能同时多个线程访问。
    @synchronized (self) {
         NSLog(@"invocationOperation:%@",[NSThread currentThread]);
   }
}

3.GCD
类型 说明
同步(sync) 它会阻塞当前线程(即停止当前的任务)执行新任务执行完毕,然后当前任务才会继续往下运行,都是在当前线程执行,不会另开线程。同步线程中,使用串行队列和并行队列并无区别。
异步(async) 当前线程会直接往下执行,它不会阻塞当前线程。会另开线程,在别的线程执行。
1.如果使用的是串行队列,那么只会创建一条新的线程,然后在新的线程里面依次执行任务。
2.如果是使用的并行队列,那么在每次添加了任务之后都会创建一个新的线程去并行执行,全局并发队列是系统提供的一个并发队列。
异步线程组(group_async) 基础功能和异步(async)一样,但是加了一个group的概念,在group的队列执行完毕之后会有统一的回调通知dispatch_group_notify和dispatch_group_wait。
串行队列 1.如果是在同步线程中,那么不会开辟创建新线程,在当前线程执行队列中的任务。
2.如果是在异步线程中,则开启一条新线程,在新线程中执行队列中的任务。
并发队列 1.如果是在同步线程中,那么不会开辟创建新线程,在当前线程执行队列中的任务。(和串行队列没区别)。
2.如果是在异步线程中,则每一个队列每调用一次就会开启一条新线程,在新线程中并发执行队列中的任务。
全局并发队列 系统提供的一个并发队列。可直接调用,不用创建
3.1队列的创建
//@param <#const char * _Nullable label#>指的是队列的名称,传入C语言字符串
//<#dispatch_queue_attr_t  _Nullable attr#>指的是队列的类型
//DISPATCH_QUEUE_SERIAL and NULL  串行队列
//DISPATCH_QUEUE_CONCURRENT   并行队列

dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t  _Nullable attr#>)

串行队列( DISPATCH_QUEUE_SERIAL || NULL)

dispatch_queue_t serialQueue1 = dispatch_queue_create("syncQueue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serialQueue2 = dispatch_queue_create("syncQueue2", NULL);

并行队列 (DISPATCH_QUEUE_CONCURRENT)

dispatch_queue_t concurrent1 = dispatch_queue_create("concurrent1", DISPATCH_QUEUE_CONCURRENT);

全局队列

  • 第一个参数是优先级
    • DISPATCH_QUEUE_PRIORITY_HIGH 高优先级
    • DISPATCH_QUEUE_PRIORITY_DEFAULT 默认优先级
    • DISPATCH_QUEUE_PRIORITY_LOW 低优先级
    • DISPATCH_QUEUE_PRIORITY_BACKGROUND 在background执行

+第二个参数 flag Reserved for future use 预留的参数,传0就可以了

dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

主队列

 dispatch_get_main_queue()
3.2同步线程 (sync)

注意同步线程操作传入的队列不能是当前队列,如果是当前队列会造成线程死锁的状态,下面会讲到

   //DISPATCH_QUEUE_SERIAL 串行队列
    dispatch_queue_t serialQueue1 = dispatch_queue_create("syncQueue1", DISPATCH_QUEUE_SERIAL);
   //创建同步线程,在队列block回调中添加执行任务
    dispatch_sync(serialQueue1, ^{
        //添加执行的任务
        NSLog(@"serialQueue1-->%@",[NSThread currentThread]);
    });
3.3异步线程 (async)

**异步线程串行队列和异步线程并发队列的差别主要是: **
**只有一条队列的情况下,如果是串行队列那么只会开启一条新的线程,然后任务依次执行。如果是并发队列的话,那么每添加一次任务都会开启一条新的线程,然后个人线程并发执行
**

  • 异步线程串行队列
    //DISPATCH_QUEUE_SERIAL 串行队列
    dispatch_queue_t serialQueue1 = dispatch_queue_create("syncQueue1", DISPATCH_QUEUE_SERIAL);
    //创建异步线程,开启一条新线程执行任务
    dispatch_async(serialQueue1, ^{
        //串行队列执行
        NSLog(@"serialQueue1-->%@",[NSThread currentThread]);
        for (int i = 10; i<20; i++) {
            NSLog(@"%d",i);
        }
    });


  • 异步线程并发队列
    //DISPATCH_QUEUE_CONCURRENT 并行队列
    dispatch_queue_t concurrent1 = dispatch_queue_create("concurrent1", DISPATCH_QUEUE_CONCURRENT);
    //创建异步线程,在队列block回调中添加执行任务
    dispatch_async(concurrent1, ^{
        //并行队列执行
        NSLog(@"global1-->%@",[NSThread currentThread]);
        for (int i = 40; i<50; i++) {
            NSLog(@"%d",i);
        }
    });

     //全局队列 flag Reserved for future use 预留的参数,传0就可以了
      dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     //创建异步线程,在队列block回调中添加执行任务
      dispatch_async(global, ^{
        NSLog(@"global1-->%@",[NSThread currentThread]);
        for (int i = 40; i<50; i++) {
            NSLog(@"%d",i);
        }
    });

  • 线程卡死问题
    在原来的线程上添加一个同步串行任务,如果添加的串行队列就是当前队列的话就会造成线程卡死,因为原本线程是在该队列中执行的,这里又创建了一个任务在该队列中,原先的任务就会停止,等待这个队列的新任务执行完毕后再执行主队列的任务,但是这里创建的队列又是原先队列,任务是添加该队列中,同一线程中相互等待,造成线程死锁状态。
  //用这个是会堵塞死线程,因为原本线程是在主队列中执行的,这里又创建了一个队列主队列的任务就会停止,等待这个队列的任务执行完毕后再执行主队列的任务,但是这里创建的队列又是主队列,任务是添加在主队列中,所有main线程中相互等待,造成线程死锁状态
 //主线程
-(void)test
{
    NSLog(@"我是主线程");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"在这里我会被卡死,我不会被log出来");
    });
    NSLog(@"上面已经卡死了,所以我也不会被log出来");
}


3.4Dispatch Groups的使用
  • dispatch_group_async 异步队列组。也是一种创建异步线程执行的方法,和dispatch_async一样
  • dispatch_group主要是来进行线程同步,例如我们通常一个页面中有多个网络请求,然后我们需要管理他们的先后请求顺序(当然这里我们可以使用异步串行也可以实现),使用dispatch_group的好处是group中的任务都执行完毕以后会有一个通知回调,我们可以在通知回调里面做我们的操作。例如刷新UI页面,不过这个回调是异步线程里面的,需要回到主线程刷新。
  • dispatch_group_notify group中所有任务都执行完毕有的回到
线程组串行队列
    //DISPATCH_QUEUE_SERIAL 串行队列
    dispatch_queue_t serialQueue1 = dispatch_queue_create("syncQueue1", DISPATCH_QUEUE_SERIAL);
   //创建group
    dispatch_group_t group = dispatch_group_create();
   //添加队列到group中,执行任务
    dispatch_group_async(group, serialQueue1, ^{
        NSLog(@"你大爷-->%@",[NSThread currentThread]);
    });
    dispatch_group_async(group, serialQueue1, ^{
        NSLog(@"你二大爷-->%@",[NSThread currentThread]);
    });
    //group中的任务都执行完毕了的回调,注意这里的回调不是主线程
    dispatch_group_notify(group, concurrent1, ^{
    //在这里面可以做刷新UI页面等操作,不过要回到主线程中
         NSLog(@"你大爷和你二大爷都执行完毕了-->%@",[NSThread currentThread]);
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
        NSLog(@"dispatch_group_wait 结束");
    });


看看执行这段代码打印的,这里只开启了一条线程串行执行任务,执行完毕后有通知回调


WechatIMG5.jpeg
线程组并行队列
    //DISPATCH_QUEUE_CONCURRENT 并行队列
    dispatch_queue_t concurrent1 = dispatch_queue_create("concurrent1", DISPATCH_QUEUE_CONCURRENT);

   //创建group
    dispatch_group_t group = dispatch_group_create();
   //添加队列到group中,执行任务
    dispatch_group_async(group, concurrent1, ^{
        for (int i = 0; i<100; i++) {
            NSLog(@"你大爷-->%@",[NSThread currentThread]);
        }
    });
    dispatch_group_async(group, concurrent1, ^{
         for (int i = 0; i<100; i++) {
            NSLog(@"你二大爷-->%@",[NSThread currentThread]);
          }   
     });
    //group中的任务都执行完毕了的回调,注意这里的回调不是主线程
    dispatch_group_notify(group, concurrent1, ^{
    //在这里面可以做刷新UI页面等操作,不过要回到主线程中
         NSLog(@"你大爷和你二大爷都执行完毕了-->%@",[NSThread currentThread]);
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
        NSLog(@"dispatch_group_wait 结束");
    });

log中可以看出这里面开启了两条线程异步执行任务,执行完毕后有通知回调(log太多没有截图完整)。


WechatIMG6.jpeg
4.GCD的一些其他的使用
单例
 static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
    });
延迟执行
 //<#delayInSeconds#>延迟时间
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
    });
线程挂起和重启
  • dispatch_suspend 挂起 **在创建线程前挂起才有效,当创建的线程已经在执行了就无效了,即GCD无法终止执行中的线程 **
  • dispatch_resume 开启
    dispatch_queue_t serialQueue = dispatch_queue_create("syncQueue1", DISPATCH_QUEUE_SERIAL);
 //挂起线程,此时下面的线程不会开启
    dispatch_suspend(serialQueue);
    dispatch_async(serialQueue, ^{
        //并行队列执行
        for (int i = 40; i<50; i++) {
            NSLog(@"嘻嘻嘻嘻-->%@",[NSThread currentThread]);
        }
    });
    //休眠4秒后重启线程,这时候才会执行block里面代码块
    sleep(4.0);
    dispatch_resume(serialQueue2);
dispatch_source_t 实现一个简单的计时器
 //倒计时时间
    __block int timeout= 5;
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, concurrent1);
  //这里面四个参数
  //1.dispatch_source_t source 传入上面的source
  //2.dispatch_time_t start 开始时间
  //3.uint64_t interval  间隔时间
  //4.uint64_t leeway 落后时间(设置了貌似没什么效果)
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC,0);
    dispatch_source_set_event_handler(timer, ^{
        if (timeout > 0) {
            //在这里执行轮询
            NSLog(@"循环中");
            timeout--;
        }else{
            //关闭计时器
            dispatch_source_cancel(timer);
        }
    });
    //开启
    dispatch_resume(timer);

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

推荐阅读更多精彩内容

  • 本篇博客共分以下几个模块来介绍GCD的相关内容: 多线程相关概念 多线程编程技术的优缺点比较? GCD中的三种队列...
    有梦想的老伯伯阅读 1,016评论 0 4
  • 欢迎大家指出文章中需要改正或者需要补充的地方,我会及时更新,非常感谢。 一. 多线程基础 1. 进程 进程是指在系...
    xx_cc阅读 7,171评论 11 70
  • .一.进程 进程:是指在系统中正在运行的一个应用程序,每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空...
    IIronMan阅读 4,471评论 1 33
  • 多线程 在iOS开发中为提高程序的运行效率会将比较耗时的操作放在子线程中执行,iOS系统进程默认启动一个主线程,用...
    郭豪豪阅读 2,586评论 0 4
  • 我是个幸运的孩子,出身在一个幸福的家庭,爸妈平凡幸福的婚姻一直是我所向往的,加上高中谈过恋爱,决定大学要找一个自己...
    镜裔君阅读 357评论 0 2