美文网首页
iOS探究 --- 多线程

iOS探究 --- 多线程

作者: 空空_Jiminy | 来源:发表于2020-04-28 15:57 被阅读0次

    事实上,多线程并不是多条线程同时执行的。在处理线程的过程中,由时间片控制cpu执行哪条线程,不过这过程太快了,会有种线程同时进行的感觉。设置线程的优先级可以享受到更多的cpu资源。

    一.线程的优缺点

    优点:
    1.能适当提高程序的执行效率
    2.能适当提高资源的利用率(cpu,内存)
    缺点:
    1.开启线程需要占用一定的内存空间,512kb
    2.使程序更加复杂,需要控制好线程间的通信和数据共享问题。

    UI更新为什么要在主线程

    首先,这其实是苹果设计的一个规定,而非必须在主线程更新UI。
    UIKit并不是一个线程安全的类,UI操作设计到渲染访问各种View对象的属性,异步操作下,会存在读写问题,如果为其加锁则会耗费大量资源并拖慢运行速度。所以干脆把他设计为只能在主线程更新的串行事件就行了。

    二.异步渲染

    提到UI渲染,那不得不提下大名鼎鼎的Texture,原名AsyncDisplayKit。
    其实做iOS开发多年,我也一直在等哪个时机需要在实际项目中必须用到它,但是到现在为止,还没有。
    https://www.jianshu.com/p/1186a99dded5

    三.线程的生命周期

    image.png

    四.线程和Runloop的关系

    1:runloop与线程是一一对应的,一个runloop对应一个核心的线程,为什么说是核心的,是因为runloop是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局的字典里。

    2:runloop是来管理线程的,当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务。

    3:runloop在第一次获取时被创建,在线程结束时被销毁。

    4:对于主线程来说,runloop在程序一启动就默认创建好了。

    5:对于子线程来说,runloop是懒加载的,只有当我们使用的时候才会创建,所以在子线程用定时器要注意:确保子线程的runloop被创建,不然定时器不会回调。

    五.几种常用锁

    image.png

    上图为各种锁的性能排行。
    其中,性能最好的OSSpinLock已经线程不再安全了。不推荐使用。
    推荐使用:
    1.dispatch_semaphore
    2.NSLock
    3.@synchronized

    六.atomic与nonatomic

    nonatomic:非原子属性,线程不安全。
    atomic:原子属性,线程安全,自带一把自旋锁,但是耗资源。

    七.自旋锁、互斥锁、读写锁

    互斥锁
    在访问共享资源之前对进行加锁操作,在访问完成之后进行解锁操作。 加锁后,任何其他试图再次加锁的线程会被阻塞,直到当前线程解锁。 如果解锁时有一个以上的线程阻塞,那么所有该锁上的线程都被编程就绪状态, 第一个变为就绪状态的线程又执行加锁操作,那么其他的线程又会进入等待。 在这种方式下,只有一个线程能够访问被互斥锁保护的资源。

    自旋锁
    自旋锁是一种特殊的互斥锁,当资源被枷锁后,其他线程想要再次加锁,此时该线程不会被阻塞睡眠而是陷入循环等待状态(CPU不能做其它事情),循环检查资源持有者是否已经释放了资源,这样做的好处是减少了线程从睡眠到唤醒的资源消耗,但会一直占用CPU的资源。适用于资源的锁被持有的时间短,而又不希望在线程的唤醒上花费太多资源的情况。
    相对于互斥锁,自旋锁资源占用更厉害,但是唤醒时间短。

    读写锁
    只有一个线程可以占有写状态的锁,但可以有多个线程同时占有读状态锁。

    八.GCD

    GCD是Apple为多核的并行运算提出的解决方案
    GCD会自动利用更多的CPU内核(双核,四核)

    1.GCD的优缺点

    优点
    GCD会自动管理线程的生命周期(创建线程,调度任务,销毁线程),我们只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码。
    缺点
    GCD会自动管理线程的声明周期。在更复杂的线程操作中,无法自如的操作生命周期。

    2.死锁

    所谓死锁,通常指有两个线程A和B都卡住了,并等待对方完成某些操作。A不能完成是因为它在等待B完成。但B也不能完成,因为它在等待A完成。于是大家都完不成,就导致了死锁。
    https://blog.csdn.net/abc649395594/article/details/48017245

    3.栅栏

    保证顺序执行

    dispatch_barrier_sync
    当所做事件有依赖关系时,可使用dispatch_barrier_sync。
    如完成两张图片的下载后进行渲染。

    - (void)demo{
        // 栅栏函数
        dispatch_queue_t concurrentQueue = dispatch_queue_create("testLabel", DISPATCH_QUEUE_CONCURRENT);
        
        dispatch_async(concurrentQueue, ^{
            NSString *logoStr = @"https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3351002169,4211425181&fm=27&gp=0.jpg";
            NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr]];
            UIImage *image = [UIImage imageWithData:data];
            [self.mArray addObject:image];
        });
        
        dispatch_async(concurrentQueue, ^{
            NSString *logoStr = @"https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3033952616,135704646&fm=27&gp=0.jpg";
            NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr]];
            UIImage *image = [UIImage imageWithData:data];
            [self.mArray addObject:image];
        });
        
        //加载完毕了 栅栏函数上
        __block UIImage *newImage = nil;
        dispatch_barrier_async(concurrentQueue, ^{
            
            for (int i = 0; i<self.mArray.count; i++) {
                UIImage *waterImage = self.mArray[I];
                newImage =[KC_ImageTool kc_WaterImageWithWaterImage:waterImage backImage:newImage waterImageRect:CGRectMake(20, 100*(i+1), 100, 40)];
            }
        });
        
        
        dispatch_async(concurrentQueue, ^{
            dispatch_async(dispatch_get_main_queue(), ^{
                self.imageView.image = newImage;
            });
        });
    }
    
    

    !!!!!!栅栏只能使用自定义并发队列,如果使用dispatch_get_global_queue会产生调用异常。
    dispatch_get_global_queue是全局并发队列,如果加了barrier,那全局队列会产生堵塞,对于系统来说,是不允许的。

    保证线程安全

            for (int i = 0; i<2000; i++) {
            dispatch_async(concurrentQueue, ^{
                NSString *imageName = [NSString stringWithFormat:@"%d.jpg", (i % 10)];
                NSURL *url = [[NSBundle mainBundle] URLForResource:imageName withExtension:nil];
                NSData *data = [NSData dataWithContentsOfURL:url];
                UIImage *image = [UIImage imageWithData:data];
                
                //!这里不加barrier会程序会崩溃
                dispatch_barrier_async(concurrentQueue, ^{
                    [self.mArray addObject:image];
                });
            });
        }
    

    4.调度组

    dispatch_group_t
    相对于栅栏,group对于单个相同线程的需求降低了。

    - (void)groupDemo{
        
        //创建调度组
        dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_queue_t queue1 = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);
    
        // SIGNAL
        dispatch_group_async(group, queue, ^{
            NSString *logoStr = @"https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3351002169,4211425181&fm=27&gp=0.jpg";
            NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr]];
            UIImage *image = [UIImage imageWithData:data];
            [self.mArray addObject:image];
        });
        
        dispatch_group_async(group, queue1, ^{
            
            // afn --- sdk : queue: com.afn.cn
            
            NSString *logoStr = @"https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3033952616,135704646&fm=27&gp=0.jpg";
            NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr]];
            UIImage *image = [UIImage imageWithData:data];
            [self.mArray addObject:image];
        });
        
        
        __block UIImage *newImage = nil;
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"数组个数:%ld",self.mArray.count);
            for (int i = 0; i<self.mArray.count; i++) {
                UIImage *waterImage = self.mArray[I];
                newImage = [KC_ImageTool kc_WaterImageWithWaterImage:waterImage backImage:newImage waterImageRect:CGRectMake(20, 100*(i+1), 100, 40)];
            }
            self.imageView.image = newImage;
        });
    }
    

    5.信号量

    使用semaphore控制并发数。

        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        // 信号量 -- gcd控制并发数
        // 同步
        //总结:由于设定的信号值为3,先执行三个线程,等执行完一个,才会继续执行下一个,保证同一时间执行的线程数不超过3
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
        
        //任务1
        dispatch_async(queue, ^{
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"执行任务1");
            sleep(1);
            NSLog(@"任务1完成");
            dispatch_semaphore_signal(semaphore);
        });
        
        //任务2
        dispatch_async(queue, ^{
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"执行任务2");
            sleep(1);
            NSLog(@"任务2完成");
            dispatch_semaphore_signal(semaphore);
        });
        
        //任务3
        dispatch_async(queue, ^{
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"执行任务3");
            sleep(1);
            NSLog(@"任务3完成");
            dispatch_semaphore_signal(semaphore);
        });
    

    6.Source

    image.png
    • 其 CPU 负荷非常小,尽量不占用资源
    • 联结的优势

    在任一线程上调用它的的一个函数 dispatch_source_merge_data 后,会执行 Dispatch Source 事先定义好的句柄(可以把句柄简单理解为一个 block )
    这个过程叫 Custom event ,用户事件。是 dispatch source 支持处理的一种事件

    句柄是一种指向指针的指针 它指向的就是一个类或者结构,它和系统有很密切的关系
    HINSTANCE(实例句柄),HBITMAP(位图句柄),HDC(设备表述句柄),HICON(图标句柄)等。这当中还有一个通用的句柄,就是HANDLE。

    常用的dispatch_after也是使用Source实现的。

    Source创建Timer

    - (void)dispatchTimeWithPeriod:(double)period andCallback:(void (^)(dispatch_source_t timer))callback {
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_source_t timer =dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0, 0, queue);
        dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), (uint64_t)(period *NSEC_PER_SEC), 0);
        // 设置回调
        dispatch_source_set_event_handler(timer, ^{
            dispatch_async(dispatch_get_main_queue(), ^{
                //编译时block会对timer对象强引用,使timer不会被过早释放
                if (callback) callback(timer);
            });
        });
        // 启动定时器
        dispatch_resume(timer);
    }
    

    由于GCD和Runloop时平级的存在,所以以dispatch_source创造的Timer不会受到RunloopMode的影响。

    相关文章

      网友评论

          本文标题:iOS探究 --- 多线程

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