线程和进程的区别
- 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位)
- 线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)
地址空间:同⼀进程的线程共享本进程的地址空间,⽽进程之间则是独⽴的地址空间。
资源拥有:同⼀进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的
资源是独⽴的
⼀个进程崩溃后,在保护模式下不会对其他进程产⽣影响,但是⼀个线程崩溃整个进
程都死掉。所以多进程要⽐多线程健壮。进程切换时,消耗的资源⼤,效率⾼。所以涉及到频繁的切换时,使⽤线程要好于进
程。同样如果要求同时进⾏并且⼜要共享某些变量的并发操作,只能⽤线程不能⽤进程执⾏过程:每个独⽴的进程有⼀个程序运⾏的⼊⼝、顺序执⾏序列和程序⼊⼝。但是
线程不能独⽴执⾏,必须依存在应⽤程序中,由应⽤程序提供多个线程执⾏控制。线程是处理器调度的基本单位,但是进程不是。
线程没有地址空间,线程包含在进程地址空间中
多线程的意义:
优点:能适当提高程序的执行效率;提高资源的利用率(CPU,内存);任务完成后,线程会自动销毁
缺点:开启线程需要消耗一定的内存空间(默认情况下,每个线程都占用512KB);如果开启大量线程,会占用大量的内存空间,降低程序的性能,而且CPU在调用线程上开销也大
iOS多线程的技术方案
线程的生命周期
互斥锁
多线程开发中容易出现操作同一数据,为保证一个线程的执行会添加互斥锁来保证线程对共享数据独立执行
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里有两种队列:串行队列和并发队列
什么是串行队列,什么事并发队列呢,如下图所示
串行队列:任务在一个先进先出的队列里,线程从改队列里取任务去执行
并发队列:就是多个队列,多个线程从这些队列里取任务,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