iOS 多线程整理

多线程

如有错误欢迎指正,谢谢。

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方法

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

推荐阅读更多精彩内容

  • 一.进程与线程 进程是操作系统分配资源的最小单元,线程是操作系统调度的最小单元。一个程序至少要有进程,一个进程至少...
    诗酒丶趁年华阅读 185评论 0 0
  • 知识点梳理 1.线程进程的区别: 2.队列种类: 串行队列、并发队列、主队列(有经过特殊处理的串行队列)、全局队列...
    iOS猿_员阅读 619评论 1 7
  • 1.GCD队列有哪几种类型?有哪几种队列? GCD队列分为串行队列、并行队列两种类型;队列有主串行队列、全局并行队...
    oc123阅读 1,755评论 0 7
  • 一、前言 多线程这个词对于大家来说并不陌生,但是真正能够熟知多线程的坑点只在少数,iOS中并行也一直被认为是恶魔,...
    和珏猫阅读 306评论 0 0
  • 深入浅出iOS多线程(一)——线程的概念深入浅出iOS多线程(二)——pthraed和NSThread的使用深入浅...
    struggle3g阅读 1,278评论 0 6