第十二篇:iOS里多线程

2022-05-23  本文已影响0人  坚持才会看到希望

首先我们来看下线程和进程的区别以及介绍:

多线程的意义

通过如下代码可以知道当前支持的最大线程并发数量:

WechatIMG1987.jpeg

线程的生命周期

image.png

线程池

image.png

我们来研究下线程池,我们写如下代码,然后进行运行打印,我们发现如果在理论上当我们点击屏幕的时候,线程会按顺序1,2,3....这种顺序去创建和打印,但是实际上并没有,同时也会出现两条同序列号的线程,这个是因为其实GCD在维护一个线程池,它会在线程池里拿到缓存的并没有执行的任务的线程进行返回,所以没有必要调用dispatch_async(dispatch_queue_create就去创建线程,造成没必要的浪费。线程的复用机制。GCD的线程池里缓存了64条线程。之前我们打印线程数是指当前并发的线程数,CUP多少核指的是最大并发线程数。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"%@",[NSThread currentThread]);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    for (int i = 0; i < 200; i ++) {
        dispatch_async(dispatch_queue_create("hpw", DISPATCH_QUEUE_SERIAL), ^{
            NSLog(@"%@",[NSThread currentThread]);
        });
    }
}
2022-05-23 18:29:07.521031+0800 001--Test[10014:210251] <_NSMainThread: 0x600002e680c0>{number = 1, name = main}
2022-05-23 18:29:12.601367+0800 001--Test[10014:210934] <NSThread: 0x600002e25480>{number = 5, name = (null)}
2022-05-23 18:29:12.601411+0800 001--Test[10014:210939] <NSThread: 0x600002e29a40>{number = 7, name = (null)}
2022-05-23 18:29:12.601429+0800 001--Test[10014:210935] <NSThread: 0x600002e4ea00>{number = 6, name = (null)}
2022-05-23 18:29:12.601452+0800 001--Test[10014:210936] <NSThread: 0x600002e4e000>{number = 3, name = (null)}
2022-05-23 18:29:12.601667+0800 001--Test[10014:211152] <NSThread: 0x600002e649c0>{number = 11, name = (null)}
2022-05-23 18:29:12.601572+0800 001--Test[10014:210937] <NSThread: 0x600002e5ee00>{number = 4, name = (null)}
2022-05-23 18:29:12.601791+0800 001--Test[10014:211154]

GCD线程

GCD 简介
什么是GCD?
全称是 Grand Central Dispatch
纯 C 语言,提供了非常多强大的函数
GCD的优势
GCD 是苹果公司为多核的并行运算提出的解决方案
GCD 会自动利用更多的CPU内核(比如双核、四核)
GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码

下面来看几个金典的GCD打印输出:

下面这个打印首先是1,2在3前面,3在4前面,2和5没有顺序

   dispatch_queue_t queue = dispatch_queue_create("hpw", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_sync(queue, ^{//同步的是不开线程的
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
2022-05-23 18:50:39.861532+0800 002--GCD面试题[10463:226575] 1
2022-05-23 18:50:39.861588+0800 002--GCD面试题[10463:226575] 5
2022-05-23 18:50:39.861593+0800 002--GCD面试题[10463:226703] 2
2022-05-23 18:50:39.861638+0800 002--GCD面试题[10463:226703] 3
2022-05-23 18:50:39.861671+0800 002--GCD面试题[10463:226703] 4

下面这个当运行时候,会出现线程死锁,这个我们画个图来解释下原因,因为这个是串行队列,那么它就需要满足FIFO也就是先进先执行的原则,因为56到60行这些代码是先放到queue里的,57行代码也是放在queue队列里,但是57行代码是之后放的。所以在队列里第一个线程要先执行完,才能执行后面57行,但是56到60行执行完就要执行里面的57,这样就违背了FIFO原则,先进先执行,所以会造成死锁。


WechatIMG1989.jpeg WechatIMG1988.jpeg

那如果把代码sync改成async后就可以运行了,这个是因为 NSLog(@"2")和 NSLog(@"4")是先加载到第一个queue里的,然后 NSLog(@"3") ,所以在里面先输出2,4再输出3。

- (void)test2 {
    
    dispatch_queue_t queue = dispatch_queue_create("hpw", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_async(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}
2022-05-23 19:58:47.784562+0800 002--GCD面试题[11410:256488] 1
2022-05-23 19:58:47.784637+0800 002--GCD面试题[11410:256488] 5
2022-05-23 19:58:47.784665+0800 002--GCD面试题[11410:256586] 2
2022-05-23 19:58:47.784787+0800 002--GCD面试题[11410:256586] 4
2022-05-23 19:58:47.784905+0800 002--GCD面试题[11410:256586] 3
队列没有执行能力,如果是串行队列只能决定执行的顺序,但是具体执行任务需要线程来操作的,线程的切换大概是90us

下面这个当运行时候,打印为2,4,6,3. 3的输出始终在6后面,这个是因为标注1和标注3里代码先加载到queue里。标注2后加,所以标注3里代码比标注2里代码先运行,因为这个是串行队列,按加入顺序执行。

- (void)test2 {
    
    dispatch_queue_t queue = dispatch_queue_create("hpw", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{//标注1
        NSLog(@"2");
        dispatch_async(queue, ^{标注2
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    dispatch_async(queue, ^{//标注3
        NSLog(@"6");
    });
}
2022-05-23 21:35:31.333036+0800 002--GCD面试题[13190:312368] 2
2022-05-23 21:35:31.333083+0800 002--GCD面试题[13190:312368] 4
2022-05-23 21:35:31.333120+0800 002--GCD面试题[13190:312368] 6
2022-05-23 21:35:31.333150+0800 002--GCD面试题[13190:312368] 3

队列

队列和线程之间没有任何关系的,队列是用来存储任务的。队列没有能力去调度任务,只有线程才可以调度任务。
队列有串行队列,并行队列,全局并发队列,主队列

1.dispatch_get_main_queue探究

dispatch_get_main_queue(void)
{
    return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
}
struct dispatch_queue_static_s _dispatch_main_q = {
    DISPATCH_GLOBAL_OBJECT_HEADER(queue_main),
#if !DISPATCH_USE_RESOLVERS
    .do_targetq = _dispatch_get_default_queue(true),
#endif
    .dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) |
            DISPATCH_QUEUE_ROLE_BASE_ANON,
    .dq_label = "com.apple.main-thread",
    .dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1),
    .dq_serialnum = 1,
};

在上面 DQF_WIDTH(1)这个是串行队列标志

dispatch_queue_attr_info_t
_dispatch_queue_attr_to_info(dispatch_queue_attr_t dqa)
{
    dispatch_queue_attr_info_t dqai = { };

    if (!dqa) return dqai;

#if DISPATCH_VARIANT_STATIC
    if (dqa == &_dispatch_queue_attr_concurrent) {
        dqai.dqai_concurrent = true;
        return dqai;
    }
#endif

    if (dqa < _dispatch_queue_attrs ||
            dqa >= &_dispatch_queue_attrs[DISPATCH_QUEUE_ATTR_COUNT]) {
#ifndef __APPLE__
        if (memcmp(dqa, &_dispatch_queue_attrs[0],
                sizeof(struct dispatch_queue_attr_s)) == 0) {
            dqa = (dispatch_queue_attr_t)&_dispatch_queue_attrs[0];
        } else
#endif // __APPLE__
        DISPATCH_CLIENT_CRASH(dqa->do_vtable, "Invalid queue attribute");
    }

通过上面的dqa知道,当传入的是NULL时候,其返回的是串行队列。

队列总结

串行队列和并行队列都是FIFO先进先执行
DQF_WIDTH(大于1) 并发队列 --- 理解成多车道
DQF_WIDTH(1) 串行队列 --- 理解成单行道

画个图来理解下,我们可以这样理解,串行队列口子小,只有等上面执行完,才能执行下面的,因为口子被堵住了。并行队列就不是,其有不同的口子,所以可以多个口子执行任务。


WechatIMG1990.jpeg
上一篇下一篇

猜你喜欢

热点阅读