GCD封装

前言

很久以前,手机的性能取决于处理器的处理速度。在经过很长的一段时间发展之后,单个处理器的处理效率几乎达到了极限。这时候多核处理器就诞生了,使得手机有了同时执行多个任务的能力。在单核时代,使用多线程技术更多时候是为了避免耗时操作堵塞了主线程。而在多核时代,多线程技术才真正完成了提升执行效率的工作。

iOS提供了包括pthread_t在内的四种多线程方案,其中GCD作为苹果爸爸重视的多线程技术从诞生为止一直受到iOS开发者的热爱,其最大的缺点可能在于基于C语言编写的代码不那么的易于编写。

早前,笔者曾经对GCD语法进行了简单的面向对象封装,之后可以通过下面的代码调用多线程方案:

[LXDQueue executeInMainQueue: ^{
    NSLog(@"Execute task in main queue");
}];

[LXDQueue executeInGlobalQueue: ^{
    NSLog(@"The code will be executed after 1 second");
} delay: 1];

随着开发经历的增长,也逐渐感觉到了这样简单的语法封装对GCD本身的利用还不够好。在学习了YYKit的源码之后,决定对日常使用的多线程方案进行一次更好的封装。

技术总结

虽然本文对于GCD的封装使用到的大部分技术点已经记载在YYKit学习笔记,但是这里还是要简单的提一下所用的技术点:

  • 锁技术

其中队列轮询查找用到了OSAtomicIncrement32原子操作

派发任务的状态修改使用dispatch_semaphore_t信号同步

  • 函数指针

虽然block无疑是一种更常用的回调手段,但是函数指针的优点在于无需考虑循环引用

  • 函数重载

这里涉及到了封装过程中遇到的一个坑,最终采用__attribute__修饰函数来实现重载

  • 懒加载

对于并发队列结构的创建总是延后创建的,只有真正用到的时候才会分配内存

  • QoS

QoS的分级对于高优先级的线程资源占据有着非常突出的表现

虽然每个技术点都有很多的东西可以发掘,但是笔者不会也暂时没有能力去深入这些技术点。

代码实现

本文的GCD封装基于YYDispatchQueuePool源码修改而成,有LXDDispatchAsync以及LXDDispatchOperation两个文件。前者提供了多个多线程函数接口;后者基于前者进行面向对象封装,提供了取消任务的接口。一个无解的问题是一旦任务开始执行了,那么就无法取消操作。目前的解决方案是block回调中提供了一个判断任务取消的标记,使用者调用时需要判断是否应该中断任务

LXDDispatchAsync.h

typedef NS_ENUM(NSInteger, LXDQualityOfService) {
    LXDQualityOfServiceUserInteractive = NSQualityOfServiceUserInteractive,
    LXDQualityOfServiceUserInitiated = NSQualityOfServiceUserInitiated,
    LXDQualityOfServiceUtility = NSQualityOfServiceUtility,
    LXDQualityOfServiceBackground = NSQualityOfServiceBackground,
    LXDQualityOfServiceDefault = NSQualityOfServiceDefault,
};

void LXDDispatchQueueAsyncBlockInQOS(LXDQualityOfService qos, dispatch_block_t block);
void LXDDispatchQueueAsyncBlockInUserInteractive(dispatch_block_t block);
void LXDDispatchQueueAsyncBlockInUserInitiated(dispatch_block_t block);
void LXDDispatchQueueAsyncBlockInBackground(dispatch_block_t block);
void LXDDispatchQueueAsyncBlockInDefault(dispatch_block_t block);
void LXDDispatchQueueAsyncBlockInUtility(dispatch_block_t block);

LXDDispatchOperation.h

@class LXDDispatchOperation;
typedef void(^LXDCancelableBlock)(LXDDispatchOperation * operation);

/*!
 *  @brief  派发任务封装
 */
@interface LXDDispatchOperation : NSObject

@property (nonatomic, readonly) BOOL isCanceled;

+ (instancetype)dispatchOperationWithBlock: (dispatch_block_t)block;
+ (instancetype)dispatchOperationWithBlock: (dispatch_block_t)block inQoS: (NSQualityOfService)qos;

+ (instancetype)dispatchOperationWithCancelableBlock:(LXDCancelableBlock)block;
+ (instancetype)dispatchOperationWithCancelableBlock:(LXDCancelableBlock)block inQos: (NSQualityOfService)qos;

- (void)start;
- (void)cancel;

@end

为了保证代码能有更高的效率,YYKit里面经常使用结构体+内联函数的组合来替代类+方法,笔者同样效仿了这点(EOC也提到过static inline的技巧)

#define LXD_INLINE static inline

typedef struct __LXDDispatchContext {
    const char * name;
    void ** queues;
    uint32_t queueCount;
    int32_t offset;
} *DispatchContext, LXDDispatchContext;

LXD_INLINE dispatch_queue_t __LXDQualityOfServiceToDispatchQueue(LXDQualityOfService qos, const char * queueName) {
    if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) {
        dispatch_qos_class_t qosClass = __LXDQualityOfServiceToQOSClass(qos);
        dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, qosClass, 0);
        return dispatch_queue_create(queueName, attr);
    } else {
        dispatch_queue_t queue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL);
        dispatch_set_target_queue(queue, dispatch_get_global_queue(__LXDQualityOfServiceToDispatchPriority(qos), 0));
        return queue;
    }
}

封装差异

YYDispatchQueuePool向外暴露了类对象,类对象持有一个并发队列结构。在每次使用到的时候重新创建并发队列结构,类对象在不用的时候释放这个结构的内存:

- (instancetype)initWithName:(NSString *)name queueCount:(NSUInteger)queueCount qos:(NSQualityOfService)qos {
    if (queueCount == 0 || queueCount > MAX_QUEUE_COUNT) return nil;
    self = [super init];
    _context = YYDispatchContextCreate(name.UTF8String, (uint32_t)queueCount, qos);
    if (!_context) return nil;
    _name = name;
    return self;
}

- (void)dealloc {
    if (_context) {
        YYDispatchContextRelease(_context);
        _context = NULL;
    }
}

设计理念是很典型的懒加载思想,用到了就才保留内存。由于大多数情况下,如果应用中用到了多线程技术,那么总是会多次使用。因此基于YYKit的思路,对并发队列结构的生成同样做了懒加载处理,加载后使用静态变量全局保存。相较于原实现,多占用了一些内存,但是更适用于笔者这种多线程重度使用患者:

LXD_INLINE DispatchContext __LXDDispatchContextGetForQos(LXDQualityOfService qos) {
    static DispatchContext contexts[5];
    int count = (int)[NSProcessInfo processInfo].activeProcessorCount;
    count = MIN(1, MAX(count, LXD_QUEUE_MAX_COUNT));
    switch (qos) {
        case LXDQualityOfServiceUserInteractive: {
            static dispatch_once_t once;
            dispatch_once(&once, ^{
                contexts[0] = __LXDDispatchContextCreate("com.sindrilin.user_interactive", count, qos);
            });
            return contexts[0];
        }

        case LXDQualityOfServiceUserInitiated: {
            static dispatch_once_t once;
            dispatch_once(&once, ^{
                contexts[1] = __LXDDispatchContextCreate("com.sindrilin.user_initated", count, qos);
            });
            return contexts[1];
        }

        case LXDQualityOfServiceUtility: {
            static dispatch_once_t once;
            dispatch_once(&once, ^{
                contexts[2] = __LXDDispatchContextCreate("com.sindrilin.utility", count, qos);
            });
            return contexts[2];
        }

        case LXDQualityOfServiceBackground: {
            static dispatch_once_t once;
            dispatch_once(&once, ^{
                contexts[3] = __LXDDispatchContextCreate("com.sindrilin.background", count, qos);
            });
            return contexts[3];
        }

        case LXDQualityOfServiceDefault:
        default: {
            static dispatch_once_t once;
            dispatch_once(&once, ^{
                contexts[4] = __LXDDispatchContextCreate("com.sindrilin.default", count, qos);
        });
            return contexts[4];
        }
    }
}

另外,文件对外只暴露C函数接口调起任务派发,替换YYDispatchQueuePool的类调用。

踩坑经历

GCD封装要考虑到线程竞争频发的可能性,使用一个静态的信号量变量来控制线程同步操作是很有必要的。当然重复的wait和signal函数看着也不够优雅,于是决定生成内联函数来完成线程同步的工作。

最开始函数接收一个dispatch_block_t类型的参数,后来想到可以增加一个等待超时参数,方便以后做其他修改。因此采用Objective-C++的方式实现函数默认参数,文件改为LXDDispatchOperation.mm

LXD_INLINE void __LXDLockExecute(dispatch_block_t block, dispatch_time_t threshold = dispatch_time(DISPATCH_TIME_NOW, DISPATCH_TIME_FOREVER)) {
    if (block == nil) { return ; }
    static dispatch_semaphore_t lxd_queue_semaphore;
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        lxd_queue_semaphore = dispatch_semaphore_create(0);
    });
    dispatch_semaphore_wait(lxd_queue_semaphore, threshold);
    block();
    dispatch_semaphore_signal(lxd_queue_semaphore);
}

执行的时候报了个Apple Mach-O Linker Error的错误,坑爹的是这种错误没有更多的具体信息。

783864-ed3fa8d4d8f31751.jpg

一旦注释掉文件中的C函数调用,这个错误又没了。个人才猜想是:mm文件中不允许调用文件外声明的C函数(如果有错,还望在评论中指出如何修改)于是笔者只能改回m后缀,结合Clang Attributes 黑魔法小记中的技巧,最终通过函数重载实现默认参数功能:

#define LXD_FUNCTION_OVERLOAD __attribute__((overloadable))

LXD_INLINE LXD_FUNCTION_OVERLOAD void __LXDLockExecute(dispatch_block_t block) {
    __LXDLockExecute(block, dispatch_time(DISPATCH_TIME_NOW, DISPATCH_TIME_FOREVER));
}

LXD_INLINE LXD_FUNCTION_OVERLOAD void __LXDLockExecute(dispatch_block_t block, dispatch_time_t threshold) {
    if (block == nil) { return ; }
    static dispatch_semaphore_t lxd_queue_semaphore;
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        lxd_queue_semaphore = dispatch_semaphore_create(0);
    });
    dispatch_semaphore_wait(lxd_queue_semaphore, threshold);
    block();
    dispatch_semaphore_signal(lxd_queue_semaphore);
}

线程获取

这几天使用过程中发现在适配性上还有些不足,主要是在将多线程方案移植到LXDAppMonitor后,想要获取派发任务所在线程有些麻烦,于是添加了只读的disaptch_queue_t属性,在使用的时候可以随时访问:

@interface LXDDispatchOperation : NSObject

@property (nonatomic, readonly) BOOL isCanceled;
@property (nonatomic, readonly) dispatch_queue_t queue;

+ (instancetype)dispatchOperationWithBlock: (dispatch_block_t)block;
+ (instancetype)dispatchOperationWithBlock: (dispatch_block_t)block inQoS: (NSQualityOfService)qos;

+ (instancetype)dispatchOperationWithCancelableBlock:(LXDCancelableBlock)block;
+ (instancetype)dispatchOperationWithCancelableBlock:(LXDCancelableBlock)block inQos: (NSQualityOfService)qos;

- (void)start;
- (void)cancel;

@end

最后

通过这段时间简单的阅读,从YYKit中学习了大量的技巧,也巩固了自己的知识点。毫无疑问,YYKit是国内最优秀的源码,非常值得想要深入iOS开发的小伙伴们去仔细研读。

本文demo:LXDDispatchOperation

深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值