iOS探究 --- 多线程

2020-04-28  本文已影响0人  空空_Jiminy

事实上,多线程并不是多条线程同时执行的。在处理线程的过程中,由时间片控制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

在任一线程上调用它的的一个函数 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的影响。

上一篇下一篇

猜你喜欢

热点阅读