美文网首页
第十二篇:iOS里多线程

第十二篇:iOS里多线程

作者: 坚持才会看到希望 | 来源:发表于2022-05-23 23:27 被阅读0次

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

    • 线程是进程的基本执行单元,一个进程的所有任务都在线程中执行
    • 进程要想执行任务,必须得有线程,进程至少要有一条线程
    • 程序启动会默认开启一条线程,这条线程被称为主线程或 UI 线程
    • 进程是指在系统中正在运行的一个应用程序

    • 每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存空间内

    • 通过“活动监视器”可以查看 Mac 系统中所开启的进程

    • 一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.

    • 相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。

    • 所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)

    • 地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。

    • 资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。

    • 执行过程:每个独立的进程程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

    • 根本区别:进程是操作系统进行资源分配的基本单位,而线程是操作系统进行任务调度和执行的最小单位。

    多线程的意义

    • 优点
      • 能适当提高程序的执行效率
      • 能适当提高资源的利用率(CPU,内存)
      • 线程上的任务执行完成后,线程会自动销毁
    • 缺点
      • 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占 512 KB)
      • 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
      • 线程越多,CPU 在调用线程上的开销就越大
      • 程序设计更加复杂,比如线程间的通信、多线程的数据共享

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

    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

    相关文章

      网友评论

          本文标题:第十二篇:iOS里多线程

          本文链接:https://www.haomeiwen.com/subject/wxnrprtx.html