iOS线程与GCD

线程和进程的区别

  1. 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位)
  2. 线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)

地址空间:同⼀进程的线程共享本进程的地址空间,⽽进程之间则是独⽴的地址空间。
资源拥有:同⼀进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的
资源是独⽴的

  • ⼀个进程崩溃后,在保护模式下不会对其他进程产⽣影响,但是⼀个线程崩溃整个进
    程都死掉。所以多进程要⽐多线程健壮。

  • 进程切换时,消耗的资源⼤,效率⾼。所以涉及到频繁的切换时,使⽤线程要好于进
    程。同样如果要求同时进⾏并且⼜要共享某些变量的并发操作,只能⽤线程不能⽤进程

  • 执⾏过程:每个独⽴的进程有⼀个程序运⾏的⼊⼝、顺序执⾏序列和程序⼊⼝。但是
    线程不能独⽴执⾏,必须依存在应⽤程序中,由应⽤程序提供多个线程执⾏控制。

  • 线程是处理器调度的基本单位,但是进程不是。

  • 线程没有地址空间,线程包含在进程地址空间中

多线程的意义:

优点:能适当提高程序的执行效率;提高资源的利用率(CPU,内存);任务完成后,线程会自动销毁
缺点:开启线程需要消耗一定的内存空间(默认情况下,每个线程都占用512KB);如果开启大量线程,会占用大量的内存空间,降低程序的性能,而且CPU在调用线程上开销也大

iOS多线程的技术方案

image.png

线程的生命周期

image.png

互斥锁

多线程开发中容易出现操作同一数据,为保证一个线程的执行会添加互斥锁来保证线程对共享数据独立执行

atomic和nonatomic的使用和区别

nonatomic 非原子属性
atomic 原子属性(线程安全),针对多线程设计的。
保证同一时间只对一个线程能够写入或者取值,atomic本身就有一个锁(自旋锁)
单写多读:单个线程写入,多个线程可以读取
atomic:线程安全,需要消耗大量的资源
nonatomic:非线程安全,适合内存小的移动设备

GCD

接下来研究一下GCD的线程,GCD我们比较经常用到,而且面试也经常问到
什么是GCD呢?
GCD是C语言对线程做的一套封装,提供了很多方便的api方法,程序员只需要告诉GCD想要执行的任务,剩下的都交给GCD,方便了程序员的开发,并且GCD底层自己维护了一个线程池,这样就减少了创建线程的开销,优化了程序性能。

函数

任务使用block封装,任务的block没有参数也没有返回值
执行任务的函数
异步:dispatch_async 不用等待当前语句执行完毕,就可以执行下一条语句,会开启线程执行block任务
同步:dispatch_sync 必须等当前语句执行完毕,才会执行下一条语句,不会开启新线程

队列

GCD里有两种队列:串行队列和并发队列
什么是串行队列,什么事并发队列呢,如下图所示


image.png

串行队列:任务在一个先进先出的队列里,线程从改队列里取任务去执行
并发队列:就是多个队列,多个线程从这些队列里取任务,cpu在极短的时间内调度执行各个任务,给人一种多任务同时执行的错觉

上面都是一些概念性东西,接下来我们来用代码说明一些问题。

异步,同步函数和串行,并发队列组合

异步函数+串行队列
示例1:
    dispatch_queue_t serial = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serial, ^{
        NSLog(@"2222:%@", [NSThread currentThread]);
    });
    dispatch_async(serial, ^{
        NSLog(@"3333:%@", [NSThread currentThread]);
    });
输出:
2222:<NSThread: 0x60000181c940>{number = 7, name = (null)}
3333:<NSThread: 0x6000014c8440>{number = 7, name = (null)}
示例2:
dispatch_queue_t queue = dispatch_queue_create("cooci", NULL); // NULL 默认是串行队列
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2--%@", [NSThread currentThread]);
        dispatch_async(queue, ^{
            NSLog(@"3--%@", [NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"6--%@", [NSThread currentThread]);
        });
    });
    NSLog(@"5");
输出:
2021-08-06 14:05:01.717559+0800 线程[21151:1099399] 1
2021-08-06 14:05:01.717775+0800 线程[21151:1099399] 5
2021-08-06 14:05:01.717854+0800 线程[21151:1099502] 2--<NSThread: 0x6000027e5ac0>{number = 5, name = (null)}
2021-08-06 14:05:01.718019+0800 线程[21151:1099502] 3--<NSThread: 0x6000027e5ac0>{number = 5, name = (null)}
2021-08-06 14:05:01.718174+0800 线程[21151:1099502] 6--<NSThread: 0x6000027e5ac0>{number = 5, name = (null)}

串行队列在async里只会创建一个子线程

异步函数+并发队列
dispatch_queue_t concurrent = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrent, ^{
        NSLog(@"44444:%@", [NSThread currentThread]);
    });
    
    dispatch_async(concurrent, ^{
        NSLog(@"6666666:%@", [NSThread currentThread]);
    });
输出:
2021-08-06 11:37:48.845963+0800 线程[20253:1038090] 44444:<NSThread: 0x6000008019c0>{number = 6, name = (null)}
2021-08-06 11:37:48.845963+0800 线程[20253:1038091] 6666666:<NSThread: 0x60000084a940>{number = 5, name = (null)}

并发队列在异步函数里会根据需要创建线程,可能创建多个线程

同步函数+串行队列
dispatch_queue_t serial = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serial, ^{
        NSLog(@"2222:%@", [NSThread currentThread]);
    });

    dispatch_sync(serial, ^{
        NSLog(@"3333:%@", [NSThread currentThread]);
    });
输出:
2021-08-06 11:39:49.282057+0800 线程[20271:1039333] 2222:<NSThread: 0x600003fec5c0>{number = 1, name = main}
2021-08-06 11:39:49.282252+0800 线程[20271:1039333] 3333:<NSThread: 0x600003fec5c0>{number = 1, name = main}

同步函数+串行队列并不会创建线程,而是在当前线程通过阻塞方式一步步执行

同步函数+并发队列
dispatch_queue_t concurrent = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(concurrent, ^{
        NSLog(@"44444:%@", [NSThread currentThread]);
    });

    dispatch_sync(concurrent, ^{
        NSLog(@"66666:%@", [NSThread currentThread]);
    });
输出:
2021-08-06 11:42:47.816410+0800 线程[20291:1041144] 44444:<NSThread: 0x6000037d4080>{number = 1, name = main}
2021-08-06 11:42:47.816628+0800 线程[20291:1041144] 66666:<NSThread: 0x6000037d4080>{number = 1, name = main}

同步函数+并发队列 也不会创建子线程,而是在当前线程执行队列任务

gcd的面试分析

dispatch_queue_t queue = dispatch_queue_create("cooci", NULL); // NULL 默认是串行队列
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_async(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
输出结果:
2021-08-06 14:08:51.274887+0800 线程[21180:1101811] 1
2021-08-06 14:08:51.275086+0800 线程[21180:1101811] 5
2021-08-06 14:08:51.275103+0800 线程[21180:1101917] 2
2021-08-06 14:08:51.275243+0800 线程[21180:1101917] 4
2021-08-06 14:08:51.275414+0800 线程[21180:1101917] 3

首先这是一个串行队列+异步函数,所以只会创建一个线程,在这里有两个线程,主线程和一个异步线程,1,5在主线程执行,2,3,4在异步线程里面执行,1 肯定是先输出的,后面两个线程相当于两个分支5和2,3,4,所以5和2,3,4谁都可能先执行,所以结果不一定是上面打印的,也可能会是1,2,5,4,3。但是2 ,4,3是固定的,因为在一个线程里面执行多个任务,而这个任务队列是串行队列,所以是一个个任务来执行
变换一下:

dispatch_queue_t queue = dispatch_queue_create("cooci", NULL); // NULL 默认是串行队列
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_sync(queue, ^{  
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");

上面会产生死锁,为什么呢?
首先这是一个串行对列,所以任务是要一个一个的执行,代码块2执行完才能执行代码块3,但是现在2还没完全执行完,3开始执行争夺cpu资源而造成的死锁

- (void)MTDemo{
    while (self.num < 5) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            self.num++;
        });
    }
    NSLog(@"end : %d",self.num); // 0-5  0
}
输出结果:
2021-08-06 14:30:40.972553+0800 线程[21290:1112969] end : 5

- (void)KSDemo{
   
    for (int i= 0; i<10000; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            self.num++;
        });
    }
    NSLog(@"end : %d",self.num); // > 10000
}

输出结果:
2021-08-06 14:30:41.149223+0800 线程[21290:1112969] end : 9970

首先MTDemo里面输出的一定>=5, 因为这边有一个条件while (self.num < 5)一定是等到self.num的值>=5才会跳出循环
在KSDemo中并没有对self.num进行条件判断,所以当for循环结束也就直接打印self.num,这边比10000小这么多是因为,多个线程同时操作self.num,当线程1对self.num+1的时候,线程2可能也对self.num+1操作,也就是说可能有多个线程从2加到3,所以肯定会比10000来的小。

dispatch_queue_t queue = dispatch_queue_create("com.lg.cooci.cn", DISPATCH_QUEUE_SERIAL);

    dispatch_async(queue, ^{
        NSLog(@"1");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"2");
    });

    dispatch_sync(queue, ^{ NSLog(@"3"); });
    
    NSLog(@"0");

    dispatch_async(queue, ^{
        NSLog(@"7");
    });
    dispatch_async(queue, ^{
        NSLog(@"8");
    });
    dispatch_async(queue, ^{
        NSLog(@"9");
    });

     A: 1230789
     B: 1237890
     C: 3120798
     D: 2137890

这边答案是A
为什么呢?我们来分析一下,首先3一定在0之前,0一定在7,8,9之前,所以排除掉BD,
因为是串行队列,所以任务是一个一个来执行,并不会并发执行,1,2,3是陆续添加到队列里,所以1,2,3顺序执行,打印1,2,3;同理7,8,9也是如此,所以答案是A

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

推荐阅读更多精彩内容