多线程
如有错误欢迎指正,谢谢。
1.线程与进程
地址空间:进程之间的地址相对独立,同一个进程中的线程共享本进程的地址空间
资源拥有:进程之间的资源相对独立,同一个进程中的线程共享本进程的资源,如内存、I/O、CPU等
2.多线程作用
优点
提高程序的执行效率
提高资源的利用率———CPU
缺点
开辟线程需要占用一定的内存空间
增加了程序复杂度
开启了大量线程,会占用大量的内存空间,降低了程序性能
3.多线程原理
Cpu在单位时间片里在各个线程之间切换
多线程“同时”进行,多个线程之间切换。
4.线程的生命周期
新建:start
就绪:runnable
运行:running
堵塞:blocked或者sleep、等待同步锁、从可调度线程池移除
死亡:任务执行完成、强制退出
5.线程安全
例子:for循环里面对数组进行增删
三种锁,尽量使用nslock
Gcd控制并发量,信号量 dispatch_semaphore
@synchronize 递归锁
nslock
6.
自旋锁 适合代码量较小,耗时较少,(忙等,就是里面有人,一直在外面转)
互斥锁 (睡觉,就是里面有人,他睡觉,没人需要醒来操作)
读写锁 set get,在set方法里面加一个锁(如@synchronize),读不会影响写,写会影响读
7.runloop和线程一一对应
线程依赖于runloop
nstimer依赖于runloop
问题
为什么UI要在主线程更新?
Uikit 设计机制,比如写代码为什么会报警告,违反了苹果的设计标准,(异步渲染)
GCD
任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。
执行任务有两种方式:『同步执行』和『异步执行』。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
串行与并行针对的是队列,而同步与异步,针对的则是线程。
同步执行:比如这里的dispatch_sync,这个函数会把一个block加入到指定的队列中,而且会一直等到执行完blcok,这个函数才返回。因此在block执行完之前,调用dispatch_sync方法的线程是阻塞的。
异步执行:一般使用dispatch_async,这个函数也会把一个block加入到指定的队列中,但是和同步执行不同的是,这个函数把block加入队列后不等block的执行就立刻返回了。
串行队列:比如这里的dispatch_get_main_queue。这个队列中所有任务,一定按照先来后到的顺序执行。不仅如此,还可以保证在执行某个任务时,在它前面进入队列的所有任务肯定执行完了。对于每一个不同的串行队列,系统会为这个队列建立唯一的线程来执行代码。
并发队列:比如使用dispatch_get_global_queue。这个队列中的任务也是按照先来后到的顺序开始执行,注意是开始,但是它们的执行结束时间是不确定的,取决于每个任务的耗时。对于n个并发队列,GCD不会创建对应的n个线程而是进行适当的优化
函数
队列
死锁:死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
dispatch_queue_t queue = dispatch_queue_create("com.demo.queue", DISPATCH_QUEUE_SERIAL);//串行队列
NSLog(@"1"); // 任务1
dispatch_async(queue, ^{
NSLog(@"2"); // 任务2
dispatch_sync(queue, ^{
NSLog(@"3"); // 任务3
});
NSLog(@"4"); // 任务4
});
NSLog(@"5");
控制台输出
1
5
2
分析:
1.执行任务1;
2.遇到异步线程,将【任务2、同步线程、任务4】加入串行队列中。因为是异步线程,所以在主线程中的任务5不必等待异步线程中的所有任务完成;
3.因为任务5不必等待,而开辟线程需要耗时,所以5先于2执行
4.任务2执行完以后,遇到同步线程,这时,将任务3加入串行队列;
5.又因为任务4比任务3早加入串行队列,所以,任务3要等待任务4完成以后,才能执行(遵循先进先出原则);但是任务3所在的同步线程需要先执行完才能走任务4,因为这个同步线程和任务4都在一个串行队列中;造成死锁。
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
因为dispatch_sync是同步的,本身就会阻塞当前线程,此刻阻塞了主线程。而当前block又在等待主线程执行完毕,从而形成了主线程等待主线程,自己等自己的情况,形成了死锁。
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // 异步执行 + 串行队列
dispatch_sync(queue, ^{ // 同步执行 + 当前串行队列
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
});
执行上面的代码会导致串行队列中追加的任务和串行队列中原有的任务两者之间相互等待,阻塞了『串行队列』,最终造成了串行队列所在的线程(子线程)死锁问题。
队列
dispatch_get_main_queue()主队列
dispatch_get_global_queue()全局并发队列 dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>),第一个参数标识队列的优先级,一般写0,第二个也写0
栅栏函数(只对自定义的并发队列有效果,对全局并发队列无效果),为什么对全局并发队列无效果?全局并发队列是全局的,栅栏函数相当于堵塞,这个会导致系统GG
A、B、C、D任务有先后顺序,先A、B,后C、D无顺序,A、B是请求,获取到某数据才走C、D
ABCD在同一个异步并发队列中
A
B
dispatch_barrier_async dispatch_barrier_sync ,加a与不加的区别:不加a会堵塞不在当前队列的任务,1,2会堵塞在栅栏函数后。
nslog(@“1”);
C
nslog(@“2”);
D
这样A、B会无顺序执行,A、B执行完成之后才会走C、D
栅栏函数作用
1.顺序执行
2.线程安全
3.不优秀:(1)依赖于一个queue,必须要求堵塞在同一个队列,如果是不同队列就没用了,不利于封装
/**
* 栅栏方法 dispatch_barrier_async
*/
- (void)barrier {
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_barrier_async(queue, ^{
// 追加任务 barrier
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 4
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@",[NSThread currentThread]); // 打印当前线程
});
}
在执行完栅栏前面的操作之后,才执行栅栏操作,最后再执行栅栏后边的操作。
dispatch_after延时执行方法
dispatch_after 方法并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after 方法是很有效的
GCD 一次性代码(只执行一次):dispatch_once
创建单例、或者有整个程序运行过程中只执行一次的代码时
staticdispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
<#code to be executed once#>//只执行 1 次的代码(这里面默认是线程安全的)
});
dispatch_apply快速迭代方法
通常我们会用 for 循环遍历,但是 GCD 给我们提供了快速迭代的方法 dispatch_apply。dispatch_apply 按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。
如果是在串行队列中使用 dispatch_apply,那么就和 for 循环一样,按顺序同步执行。但是这样就体现不出快速迭代的意义了。
NSLog(@"apply---start");
dispatch_apply(6, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSLog(@"%zu---%@",index,[NSThread currentThread]);
});
NSLog(@"apply---end");
输出
2021-04-20 19:18:01.716987+0800 RecativeCocoaDemo[13193:722316] apply---start
2021-04-20 19:18:01.717123+0800 RecativeCocoaDemo[13193:722316] 0---<NSThread: 0x600002892dc0>{number = 1, name = main}
2021-04-20 19:18:01.717124+0800 RecativeCocoaDemo[13193:722465] 4---<NSThread: 0x600002894e00>{number = 4, name = (null)}
2021-04-20 19:18:01.717124+0800 RecativeCocoaDemo[13193:722464] 3---<NSThread: 0x6000028f84c0>{number = 6, name = (null)}
2021-04-20 19:18:01.717127+0800 RecativeCocoaDemo[13193:722463] 1---<NSThread: 0x6000028e8040>{number = 3, name = (null)}
2021-04-20 19:18:01.717131+0800 RecativeCocoaDemo[13193:722469] 2---<NSThread: 0x60000289db80>{number = 5, name = (null)}
2021-04-20 19:18:01.717142+0800 RecativeCocoaDemo[13193:722466] 5---<NSThread: 0x6000028e1ec0>{number = 7, name = (null)}
2021-04-20 19:18:01.717282+0800 RecativeCocoaDemo[13193:722316] apply---end
因为是在并发队列中异步执行任务,所以各个任务的执行时间长短不定,最后结束顺序也不定。但是 apply---end 一定在最后执行。这是因为 dispatch_apply 方法会等待全部任务执行完毕。
GCD 队列组:dispatch_group
有时候我们会有这样的需求:分别异步执行2个耗时任务,然后当2个耗时任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组。
/**
* 队列组 dispatch_group_notify
*/
- (void)groupNotify {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行下边任务
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---end");
});
}
调度组
dispatch_group_async
ispatch_group_enter 进组 signal +1
dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1
dispatch_group_leave 出租 signal -1
dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1。
signal=0时 调用dispatch_group_notify,上面的任务都做完了就会走这里
/**
* 队列组 dispatch_group_enter、dispatch_group_leave
*/
- (void)groupEnterAndLeave {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程.
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---end");
});
}
dispatch_group_wait
暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。
/**
* 队列组 dispatch_group_wait
*/
- (void)groupWait {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
// 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
//DISPATCH_TIME_NOW:超时时间为0,表示忽略信号量,直接运行
//** DISPATCH_TIME_FOREVER**:超时时间为永远,表示会一直等待信号量为正数,才会继续运行
NSLog(@"group---end");
}
信号量
GCD控制并发数
当锁,一次控制一个
dispatch_semaphore_t
dispatch_source_t的应用
响应式
timer
NSOperation 抽象类,依赖子类实现
特性:灵活、自如
1:创建操作 直接start,会在主线程执行。(走2,3,还走start,会崩溃)
2:创建队列
3:操作加入队列
NSInvocationOperation
NSBlockOperation //功能在一起,代码可读性更强
应用:
GCD:系统自己管理生命周期,
cancel,可以取消(已经添加到队列的任务取消掉)
队列设置优先级
控制并发数 NSOperationQueue的maxConcurrentOperationCount
依赖 addDependency
队列的挂起、继续、取消(挂起后为什么还会有任务执行?已经被调度的任务无法被挂起,相当于已经在执行了)
Sdwebimage(模拟网络延迟:开发模式、开代理、pref软件)
先从内存取,内存有结束,内存无从沙盒取,沙盒也没有走下载
沙盒取过之后存到内存,下次取直接从内存取,没有走下载
内存和沙盒都没有,子线程下载,下载完毕内存缓存和沙盒缓存,从内存做存取比沙盒快
重复下载的问题
具体操作
UIimageview分类:任务
1.判断url非空,url重复性,保存
manager,下发下载任务
接受内存警告的通知(尽量不走系统的警告)
添加的时候清理图片,超过多大(比如100M)就清理旧的图片
加一个最大图片缓存大小,比如100M,超过100M我就给清理一下,清理到60M(按图片时间更新时间,没有用过的图片,用的频率小的),
下载图片:
nsoperation + 队列
重写start方法