美文网首页
第十二篇: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