当前位置: 首页 > news >正文

GCD 深入解析:从使用到底层实现

前言

Grand Central Dispatch (GCD) 是 Apple 基于 C 语言开发的一套完整的并发编程框架。它不仅仅是一个简单的线程管理工具,而是一个高度优化的并发编程解决方案。GCD 的设计理念是将并发编程的复杂性封装在框架内部,为开发者提供简单易用的接口。本文将深入探讨 GCD 的底层实现原理,从架构设计到具体实现,帮助开发者更好地理解和使用这个强大的工具。

一、GCD 的基本概念

1.1 GCD 的架构设计

GCD 采用了精心设计的分层架构,每一层都有其特定的职责和优化策略:

  1. 用户层(User Level)

    • 提供面向开发者的 API 接口,如 dispatch_async、dispatch_sync 等
    • 负责将开发者的任务封装成可执行单元
    • 提供队列创建和管理接口
    • 实现任务优先级和 QoS(Quality of Service)管理
    • 处理异常和错误情况
  2. 运行时层(Runtime Level)

    • 管理队列和任务的生命周期
    • 实现任务调度算法
    • 处理线程池的创建和销毁
    • 管理内存分配和释放
    • 实现优先级继承和反转预防
    • 处理任务依赖关系
  3. 系统层(System Level)

    • 与操作系统内核交互
    • 管理线程创建和调度
    • 处理系统资源分配
    • 实现性能监控和优化
    • 处理系统级异常

1.2 GCD 的核心组件

1.2.1 队列(Queue)

串行队列(Serial Queue)是一种严格按照 FIFO(先进先出)顺序执行任务的队列,每个串行队列对应一个专用线程,适用于需要顺序执行的场景,可以用于资源访问控制,通过 DispatchQueue 创建,并使用 .serial 指定串行特性。

// 创建串行队列
let serialQueue = DispatchQueue(label: "com.example.serial")// 使用串行队列执行任务
serialQueue.async {// 任务1print("Task 1 executing on serial queue")
}serialQueue.async {// 任务2print("Task 2 executing on serial queue")
}

并发队列(Concurrent Queue)是一种可以并行执行多个任务的队列,任务执行顺序不保证,适用于可以并行处理的场景,系统提供多个优先级不同的全局并发队列,通过 DispatchQueue 创建,并使用 .concurrent 指定并发特性。

// 创建并发队列
let concurrentQueue = DispatchQueue(label: "com.example.concurrent", attributes: .concurrent)// 使用并发队列执行任务
concurrentQueue.async {// 任务1print("Task 1 executing on concurrent queue")
}concurrentQueue.async {// 任务2print("Task 2 executing on concurrent queue")
}

主队列(Main Queue)是一种特殊的串行队列,运行在主线程,用于 UI 更新和用户交互处理,具有最高优先级,必须用于所有 UI 相关操作,通过 DispatchQueue.main 获取。

// 使用主队列更新UI
DispatchQueue.main.async {// UI更新代码self.label.text = "Updated text"self.tableView.reloadData()
}

1.2.2 任务(Task)

Closure 是 Swift 的闭包实现,可以捕获上下文变量,支持异步执行,可以嵌套使用,通过 {} 语法创建,可以访问外部变量并修改其值(需要声明为 var)。

// 定义Closure
let simpleClosure = {print("This is a simple closure")
}// 使用Closure
DispatchQueue.global().async(execute: simpleClosure)

函数作为任务,性能开销较小,适合简单的任务,不支持上下文捕获,通过函数引用传递。

// 定义函数
func myTask(context: UnsafeRawPointer?) {print("Executing function task")
}// 使用函数
DispatchQueue.global().async(execute: myTask)

任务上下文包含执行环境信息,存储任务状态,管理资源访问,处理异常情况,通过 DispatchQueuesetSpecificgetSpecific 管理。

// MARK: - 上下文定义
// 1. 定义上下文类型和键(使用轻量结构体)
struct TaskContext {let taskId: Stringlet priority: Int
}let contextKey = DispatchSpecificKey<TaskContext>()// 2. 创建队列并绑定上下文
let processingQueue = DispatchQueue(label: "com.example.processing")// 设置上下文(关联到队列)
processingQueue.setSpecific(key: contextKey, value: TaskContext(taskId: "ABCD-1234",priority: 10
))// 3. 在队列任务中安全访问上下文
processingQueue.async {// ✅ 最佳实践:统一通过 DispatchQueue.current 获取guard let ctx = DispatchQueue.getSpecific(key: contextKey) else {print("⚠️ Context missing in current queue")return}// 4. 嵌套队列示例(默认不继承上下文)let analyticsQueue = DispatchQueue(label: "com.example.analytics")analyticsQueue.async {let analyticsCtx = DispatchQueue.getSpecific(key: contextKey)print("🔍 Analytics context: \(analyticsCtx?.taskId ?? "nil")") // 输出 nil}
}

1.2.3 调度组(Dispatch Group)

调度组用于任务分组管理,将相关任务组织在一起,跟踪任务完成状态,支持任务依赖关系,实现任务同步,通过 DispatchGroup 创建,使用 async 添加任务,waitnotify 等待完成。

// 创建调度组
let group = DispatchGroup()// 添加任务到组
DispatchQueue.global().async(group: group) {// 任务1print("Task 1 completed")
}DispatchQueue.global().async(group: group) {// 任务2print("Task 2 completed")
}// 等待所有任务完成
group.wait()// 或者使用通知
group.notify(queue: .main) {print("All tasks completed")
}

1.2.4 信号量(Semaphore)

信号量用于资源访问控制,限制并发访问数量,保护共享资源,实现互斥锁,控制资源分配,通过 DispatchSemaphore 创建,使用 waitsignal 控制访问。

// 创建信号量,初始值为1
let semaphore = DispatchSemaphore(value: 1)// 保护共享资源
DispatchQueue.global().async {semaphore.wait()// 访问共享资源sharedResource += 1semaphore.signal()
}

1.2.5 栅栏(Barrier)

栅栏用于在并发队列中创建同步点,确保特定任务顺序执行,实现读写锁,保护共享资源,通过 async(flags: .barrier)sync(flags: .barrier) 实现。

// 创建并发队列
let queue = DispatchQueue(label: "com.example.barrier", attributes: .concurrent)// 添加读取任务
queue.async {// 读取操作print("Reading data")
}// 添加栅栏任务(写操作)
queue.async(flags: .barrier) {// 写入操作print("Writing data")
}// 添加更多读取任务
queue.async {// 读取操作print("Reading data after write")
}

二、GCD 的底层实现

2.1 队列的底层实现

GCD 的队列实现采用了精心设计的数据结构,以确保高性能和线程安全。让我们深入分析队列的核心结构:

// 队列的核心数据结构
struct dispatch_queue_s {// 队列头部信息,包含类型标识和虚函数表,用于支持多态和运行时类型检查DISPATCH_STRUCT_HEADER(dispatch_queue_s, dispatch_queue_vtable_s);// 队列类型和状态信息,定义队列的基本属性和当前状态DISPATCH_QUEUE_HEADER;// 队列标识符,用于调试和日志,帮助开发者识别和追踪队列char dq_label[DISPATCH_QUEUE_MIN_LABEL_SIZE];// 缓存行填充,优化多核访问性能,减少伪共享问题DISPATCH_QUEUE_CACHELINE_PADDING;// 任务链表管理,实现高效的任务存储和检索struct dispatch_object_s *dq_items_head; // 链表头指针,指向第一个待执行任务struct dispatch_object_s *dq_items_tail; // 链表尾指针,指向最后一个待执行任务// 队列优先级,影响任务调度顺序,确保重要任务优先执行long dq_priority;// 线程池引用,管理执行线程,实现线程的创建、复用和销毁struct dispatch_pthread_root_queue_context_s *dq_root_queue;// 队列状态标志,记录队列的当前状态(如是否正在执行、是否暂停等)uint32_t dq_state;// 原子计数器,保证多线程环境下的线程安全_Atomic(uint32_t) dq_running; // 当前正在执行的任务数_Atomic(uint32_t) dq_waiting; // 等待执行的任务数
};

队列的实现细节:

  1. 任务管理:使用双向链表存储任务,支持高效的任务插入、删除和优先级排序,同时处理任务取消和超时。

    • 任务链表采用双向链表结构,支持 O(1) 复杂度的插入和删除操作
    • 通过 dq_items_headdq_items_tail 指针快速访问链表首尾
    • 使用原子操作保证多线程环境下的线程安全
    • 实现任务优先级排序,确保高优先级任务优先执行
  2. 线程管理:动态创建和销毁线程,实现线程复用,控制线程数量并处理线程异常。

    • 通过 dq_root_queue 管理线程池,动态调整线程数量
    • 使用 dq_runningdq_waiting 计数器跟踪任务状态
    • 实现线程复用机制,减少线程创建和销毁的开销
    • 处理线程异常,确保系统稳定性
  3. 性能优化:通过缓存行对齐、无锁算法、内存访问优化和减少线程切换来提升性能。

    • 使用 DISPATCH_QUEUE_CACHELINE_PADDING 实现缓存行对齐,减少伪共享
    • 采用无锁算法实现任务调度,减少锁竞争
    • 优化内存访问模式,提高缓存命中率
    • 减少线程切换频率,降低上下文切换开销

2.2 线程池管理

GCD 的线程池实现采用了复杂的调度策略,以确保资源的高效利用。让我们深入了解线程池的核心结构:

// 线程池的核心数据结构
struct dispatch_pthread_root_queue_context_s {// 线程属性,定义线程的基本特性(如栈大小、调度策略等)pthread_workqueue_attr_t dq_attr;// 工作队列,管理线程池中的线程,实现线程的创建和调度pthread_workqueue_t dq_workqueue;// 线程数量,记录当前活跃的线程数,用于动态调整线程池大小uint32_t dq_thread_count;// 线程优先级,影响线程调度顺序,确保重要任务优先执行uint32_t dq_thread_priority;// 线程状态,使用原子操作保证线程安全,记录线程的当前状态_Atomic(uint32_t) dq_thread_state;// 任务队列,存储待执行的任务,实现任务的排队和分发struct dispatch_queue_s *dq_queue;// 线程池配置,定义线程池的行为参数(如最大线程数、空闲超时等)struct dispatch_pthread_pool_config_s *dq_config;
};

线程池的实现细节:

  1. 线程创建策略:根据任务队列长度、系统负载和 CPU 使用率动态调整线程数,通过监控线程空闲时间实现优雅退出,同时根据系统资源设置线程数上限。

    • 动态线程创建:根据任务队列长度和系统负载动态创建线程
    • 空闲线程回收:监控线程空闲时间,及时回收空闲线程
    • 资源限制:根据系统资源(CPU核心数、内存等)设置线程数上限
    • 优雅退出:实现线程的优雅退出机制,确保资源正确释放
  2. 线程优先级管理:提供五级服务质量(QOS),通过优先级继承和优先级天花板机制处理优先级反转,保证实时性和资源竞争处理。

    • QOS级别:实现从用户交互到后台的五级服务质量
    • 优先级继承:通过动态提升优先级处理优先级反转
    • 资源竞争处理:使用优先级天花板机制避免死锁
    • 实时性保证:确保高优先级任务及时执行

2.3 任务调度机制

GCD 的任务调度采用了复杂的工作窃取算法,以实现高效的负载均衡。让我们深入了解任务调度的核心结构:

// 任务的核心数据结构
struct dispatch_object_s {// 对象头部信息,包含类型标识和引用计数,支持多态和内存管理_DISPATCH_OBJECT_HEADER(object);// 下一个任务指针,用于构建任务链表,实现任务的顺序执行struct dispatch_object_s *do_next;// 目标队列,指定任务执行的队列,实现任务的定向分发struct dispatch_queue_s *do_targetq;// 任务上下文,存储任务执行所需的数据,支持任务的参数传递void *do_ctxt;// 清理函数,用于任务完成后的资源清理,确保资源正确释放void *do_finalizer;// 任务优先级,影响任务调度顺序,确保重要任务优先执行long do_priority;// 任务状态,使用原子操作保证线程安全,记录任务的当前状态_Atomic(uint32_t) do_state;
};

任务调度的实现细节:

  1. 工作窃取算法实现:每个线程维护独立队列实现快速任务获取,全局队列作为共享任务存储实现任务分发,通过从其他队列尾部窃取任务避免任务饥饿。

    • 本地队列:每个线程维护独立的任务队列,减少竞争
    • 全局队列:作为共享任务存储,实现任务分发
    • 窃取策略:从其他队列尾部窃取任务,实现负载均衡
    • 任务饥饿预防:通过工作窃取避免任务饥饿
  2. 任务调度优化:通过批量处理相似任务减少调度开销,多级队列处理优先级反转,动态监控系统负载并调整策略。

    • 批量处理:合并相似任务,减少调度开销
    • 多级队列:实现不同优先级的任务队列
    • 动态调整:根据系统负载调整调度策略
    • 性能监控:实时监控系统性能指标

2.4 内存管理

GCD 的内存管理采用了引用计数和自动释放池,以确保内存使用的安全性和效率。让我们深入了解内存管理的核心结构:

// 内存管理的核心数据结构
struct dispatch_object_s {// 对象头部,包含类型信息和引用计数,支持多态和内存管理_DISPATCH_OBJECT_HEADER(object);// 引用计数,使用原子操作保证线程安全,实现自动内存管理_Atomic(uint32_t) do_ref_cnt;// 自动释放池,管理临时对象的生命周期,优化内存使用struct dispatch_autorelease_pool_s *do_autorelease_pool;// 内存区域,指定对象分配的内存区域,优化内存分配策略void *do_zone;// 析构函数,处理对象销毁,确保资源正确释放void (*do_finalize)(struct dispatch_object_s *);
};

内存管理的实现细节:

  1. 对象生命周期管理:通过引用计数和自动释放池管理对象生命周期,确保内存安全。

    • 引用计数:使用原子操作实现线程安全的引用计数
    • 自动释放池:管理临时对象的生命周期
    • 内存区域:优化内存分配和释放
    • 析构函数:确保资源正确释放
  2. 内存优化策略:通过对象池复用常用对象减少内存分配,优化缓存使用和内存访问延迟,实现多级缓存优化内存访问。

    • 对象池:复用常用对象,减少内存分配
    • 缓存优化:优化内存访问模式,提高缓存命中率
    • 内存对齐:使用内存对齐减少缓存失效
    • 多级缓存:实现多级缓存优化内存访问

三、GCD 的高级特性

3.1 优先级继承机制

GCD 的优先级继承机制通过以下结构实现:

struct dispatch_priority_s {long dp_priority; // 优先级值,影响任务调度顺序struct dispatch_queue_s *dp_queue; // 关联队列,实现队列级别的优先级管理struct dispatch_object_s *dp_object; // 关联对象,实现对象级别的优先级管理struct dispatch_priority_s *dp_inherit; // 继承链,实现优先级继承关系uint32_t dp_flags; // 状态标志,记录优先级状态和特性
};

优先级继承机制的核心功能:

  1. 优先级反转预防:通过监控任务等待时间和分析依赖关系检测优先级反转
  2. 优先级继承:动态提升低优先级任务的优先级,避免高优先级任务被阻塞
  3. 优先级天花板:设置优先级上限,防止优先级无限提升
  4. 死锁预防:检测循环依赖并实现超时机制

3.2 自动线程管理

GCD 的自动线程管理通过以下机制实现:

  1. 动态线程调整

    • 根据任务队列长度和系统负载动态创建线程
    • 监控线程空闲时间,实现优雅退出
    • 根据系统资源(CPU核心数、内存等)设置线程数上限
  2. 线程池优化

    • 监控系统负载和任务需求动态调整线程池大小
    • 实现任务分发和负载均衡
    • 控制内存使用和 CPU 占用

3.3 任务依赖管理

GCD 的任务依赖管理通过以下结构实现:

// 任务组结构,用于管理相关任务的执行
struct dispatch_group_s {_DISPATCH_OBJECT_HEADER(group); // 对象头部,支持多态和内存管理_Atomic(int32_t) dg_count; // 任务计数,记录组内任务数量_Atomic(uint32_t) dg_waiters; // 等待者计数,记录等待组完成的任务数struct dispatch_semaphore_s *dg_semaphore; // 信号量,实现任务同步void (*dg_notify)(struct dispatch_group_s *, dispatch_queue_t, void *); // 完成回调,处理组完成事件void *dg_ctxt; // 上下文,存储组相关数据
};// 信号量结构,用于控制资源访问
struct dispatch_semaphore_s {_DISPATCH_OBJECT_HEADER(semaphore); // 对象头部,支持多态和内存管理_Atomic(long) dsema_value; // 信号量值,控制资源访问数量_Atomic(uint32_t) dsema_orig; // 原始值,记录信号量初始状态struct dispatch_queue_s *dsema_queue; // 等待队列,管理等待资源的任务_Atomic(uint32_t) dsema_waiters; // 等待者计数,记录等待资源的任务数
};

任务依赖管理的核心功能:

  1. 任务组管理:将相关任务组织在一起,跟踪完成状态
  2. 任务同步:使用信号量控制资源访问,实现任务同步
  3. 完成通知:通过回调机制处理任务组完成事件
  4. 资源控制:限制并发访问数量,保护共享资源

四、最佳实践

4.1 队列选择策略

  1. 主队列使用:UI 更新必须使用主队列保证线程安全,避免执行耗时操作,使用 dispatch_async 进行异步处理。
  2. 全局队列使用:根据任务类型选择适当的 QOS,控制并发数量避免资源竞争,防止线程爆炸。
  3. 自定义队列使用:合理设置队列优先级,控制队列生命周期,避免队列泛滥。

4.2 性能优化

  1. 线程管理优化:根据系统资源设置线程数上限,使用线程池实现线程复用,避免频繁创建销毁。
  2. 内存优化:使用对象池复用常用对象,控制内存分配避免频繁分配,优化数据结构提高访问效率。
  3. 任务调度优化:批量处理相似任务减少调度开销,使用 dispatch_apply 实现并行迭代,优化任务粒度。

五、常见问题与解决方案

5.1 死锁问题

5.1.1 主队列死锁

// 错误示例:在主队列中同步执行任务
DispatchQueue.main.sync {// 任务代码print("This will cause a deadlock!")
}

问题分析
在主队列中同步执行任务会导致死锁,因为主队列是一个串行队列,当前任务会等待同步任务完成,而同步任务又需要等待当前任务完成,形成循环等待。

解决方案

  1. 使用异步执行
// 方案1:使用异步执行替代同步执行
DispatchQueue.main.async {// 任务代码print("This will execute safely")
}

说明:使用 async 替代 sync,让任务异步执行,避免阻塞主队列。

  1. 使用完成回调
// 方案2:使用完成回调处理异步操作
func performTask(completion: @escaping () -> Void) {// 在后台队列执行耗时操作DispatchQueue.global().async {// 耗时操作// ...// 完成后在主队列更新UIDispatchQueue.main.async {completion()}}
}

说明:将耗时操作放在后台队列执行,完成后通过回调在主队列更新UI。

  1. 使用 DispatchGroup
// 方案3:使用 DispatchGroup 管理异步任务
let group = DispatchGroup()
group.enter()  // 进入组// 在后台队列执行耗时操作
DispatchQueue.global().async {// 耗时操作// ...group.leave()  // 离开组
}// 所有任务完成后在主队列执行
group.notify(queue: .main) {// 主线程更新UIprint("All tasks completed")
}

说明:使用 DispatchGroup 管理异步任务,确保所有任务完成后在主队列执行后续操作。

5.1.2 队列嵌套死锁

// 错误示例:队列嵌套同步执行
let queue = DispatchQueue(label: "com.example.queue")
queue.sync {queue.sync {// 任务代码}
}

问题分析
在同一个队列中嵌套同步执行任务会导致死锁,因为外层任务等待内层任务完成,而内层任务又需要等待外层任务完成。

解决方案

  1. 使用异步执行
// 方案1:使用异步执行替代同步执行
let queue = DispatchQueue(label: "com.example.queue")
queue.async {queue.async {// 任务代码}
}

说明:使用 async 替代 sync,让任务异步执行,避免阻塞队列。

  1. 使用不同的队列
// 方案2:使用不同的队列避免死锁
let queue1 = DispatchQueue(label: "com.example.queue1")
let queue2 = DispatchQueue(label: "com.example.queue2")queue1.sync {queue2.sync {// 任务代码}
}

说明:使用不同的队列执行嵌套任务,避免在同一个队列中同步执行。

5.2 内存泄漏

5.2.1 循环引用

// 错误示例:强引用循环
class MyClass {private let queue = DispatchQueue(label: "com.example.queue")private var value = 0func doSomething() {queue.async { [self] inself.value += 1  // 强引用 self}}
}

问题分析
在闭包中直接使用 self 会导致循环引用,因为闭包会持有 self,而 self 又持有队列,形成循环引用链。

解决方案

  1. 使用 weak self
// 方案1:使用 weak self 打破循环引用
class MyClass {private let queue = DispatchQueue(label: "com.example.queue")private var value = 0func doSomething() {queue.async { [weak self] inguard let self = self else { return }self.value += 1}}deinit {print("MyClass deinit")}
}

说明:使用 [weak self] 创建弱引用,避免循环引用。使用 guard let 安全解包。

  1. 使用 unowned self
// 方案2:使用 unowned self(当确定对象一定存在时)
class MyClass {private let queue = DispatchQueue(label: "com.example.queue")private var value = 0func doSomething() {queue.async { [unowned self] inself.value += 1}}
}

说明:使用 [unowned self] 创建无主引用,适用于确定对象一定存在的场景。注意:如果对象被释放会导致崩溃。

5.2.2 资源未释放

// 错误示例:资源未及时释放
class ResourceManager {private var resources: [String: Any] = [:]private let queue = DispatchQueue(label: "com.example.resource")func addResource(_ resource: Any, forKey key: String) {queue.async {self.resources[key] = resource}}
}

问题分析
资源管理器没有提供资源清理机制,可能导致资源无法及时释放,造成内存泄漏。

解决方案

  1. 实现资源清理机制
// 方案1:实现完整的资源管理机制
class ResourceManager {private var resources: [String: Any] = [:]private let queue = DispatchQueue(label: "com.example.resource")// 添加资源func addResource(_ resource: Any, forKey key: String) {queue.async {self.resources[key] = resource}}// 移除单个资源func removeResource(forKey key: String) {queue.async {self.resources.removeValue(forKey: key)}}// 清理所有资源func clearAllResources() {queue.async {self.resources.removeAll()}}// 析构时清理资源deinit {clearAllResources()}
}

说明:提供完整的资源管理接口,包括添加、移除和清理功能,确保资源能够及时释放。

  1. 使用自动释放池
// 方案2:使用自动释放池管理临时对象
class ResourceManager {private var resources: [String: Any] = [:]private let queue = DispatchQueue(label: "com.example.resource")func processResources() {queue.async {autoreleasepool {// 处理资源for (key, resource) in self.resources {// 处理逻辑// ...}}}}
}

说明:使用 autoreleasepool 管理临时对象的生命周期,确保临时对象能够及时释放。

5.3 性能问题

5.3.1 线程爆炸

// 错误示例:过度创建线程
for i in 0..<1000 {DispatchQueue.global().async {// 任务代码}
}

问题分析
循环中直接创建大量并发任务会导致系统创建过多线程,造成资源浪费和性能下降。

解决方案

  1. 使用串行队列
// 方案1:使用串行队列控制并发
let serialQueue = DispatchQueue(label: "com.example.serial")
for i in 0..<1000 {serialQueue.async {// 任务代码}
}

说明:使用串行队列确保任务按顺序执行,避免创建过多线程。

  1. 使用信号量控制并发
// 方案2:使用信号量控制并发数量
let concurrentQueue = DispatchQueue(label: "com.example.concurrent", attributes: .concurrent)
let semaphore = DispatchSemaphore(value: 4) // 限制最大并发数为4for i in 0..<1000 {concurrentQueue.async {semaphore.wait()  // 获取信号量defer { semaphore.signal() }  // 确保释放信号量// 任务代码}
}

说明:使用信号量限制最大并发数,避免创建过多线程。

  1. 使用并发迭代
// 方案3:使用并发迭代优化性能
let group = DispatchGroup()
let queue = DispatchQueue.global()queue.async {DispatchQueue.concurrentPerform(iterations: 1000) { i ingroup.enter()// 任务代码group.leave()}
}

说明:使用 concurrentPerform 进行并发迭代,系统会自动优化线程使用。

5.3.2 任务调度延迟

// 错误示例:频繁创建和销毁队列
func processData(_ data: [Int]) {for item in data {let queue = DispatchQueue(label: "com.example.queue")queue.async {// 处理数据}}
}

问题分析
在循环中频繁创建和销毁队列会导致性能开销,影响任务执行效率。

解决方案

  1. 重用队列
// 方案1:重用队列提高性能
class DataProcessor {private let processingQueue = DispatchQueue(label: "com.example.processing", qos: .userInitiated,attributes: .concurrent)func processData(_ data: [Int]) {let group = DispatchGroup()for item in data {group.enter()processingQueue.async {defer { group.leave() }// 处理数据}}group.notify(queue: .main) {// 处理完成}}
}

说明:创建可重用的队列,避免频繁创建和销毁队列的开销。

  1. 批量处理
// 方案2:批量处理提高效率
class BatchProcessor {private let batchQueue = DispatchQueue(label: "com.example.batch")private var batchSize = 10func processBatch(_ data: [Int]) {// 将数据分成多个批次let chunks = stride(from: 0, to: data.count, by: batchSize).map {Array(data[$0..<min($0 + batchSize, data.count)])}// 批量处理数据for chunk in chunks {batchQueue.async {// 处理一批数据for item in chunk {// 处理逻辑}}}}
}

说明:将数据分成多个批次处理,减少任务调度次数,提高处理效率。

5.4 线程安全问题

5.4.1 数据竞争

// 错误示例:多线程访问共享资源
class Counter {private var count = 0func increment() {count += 1}
}

问题分析
多线程同时访问和修改共享资源会导致数据竞争,可能产生不可预期的结果。

解决方案

  1. 使用串行队列保护
// 方案1:使用串行队列保护共享资源
class Counter {private var count = 0private let queue = DispatchQueue(label: "com.example.counter")func increment() {queue.sync {count += 1}}var value: Int {queue.sync { count }}
}

说明:使用串行队列确保对共享资源的访问是线程安全的。

  1. 使用并发队列和栅栏
// 方案2:使用并发队列和栅栏保护共享资源
class ThreadSafeDictionary<Key: Hashable, Value> {private var dictionary: [Key: Value] = [:]private let queue = DispatchQueue(label: "com.example.dictionary", attributes: .concurrent)func set(_ value: Value, forKey key: Key) {queue.async(flags: .barrier) {self.dictionary[key] = value}}func value(forKey key: Key) -> Value? {queue.sync {dictionary[key]}}
}

说明:使用并发队列提高读取性能,使用栅栏确保写入操作的线程安全。

5.4.2 优先级反转

// 错误示例:高优先级任务被低优先级任务阻塞
let highPriorityQueue = DispatchQueue(label: "com.example.high", qos: .userInteractive)
let lowPriorityQueue = DispatchQueue(label: "com.example.low", qos: .background)
let resource = NSLock()lowPriorityQueue.async {resource.lock()// 长时间操作resource.unlock()
}highPriorityQueue.async {resource.lock()  // 可能被阻塞// 重要操作resource.unlock()
}

问题分析
优先级反转发生在低优先级任务持有共享资源时,高优先级任务需要等待该资源,导致高优先级任务被低优先级任务阻塞,仅仅设置高 QoS 并不能直接暂停低优先级任务,因为锁的获取是阻塞式的。

解决方案

  1. 使用优先级继承和超时机制
// 方案1:使用优先级继承和超时机制
class PriorityAwareResource {private let resource = NSLock()private let queue = DispatchQueue(label: "com.example.priority")func accessResource(qos: DispatchQoS, timeout: TimeInterval = 1.0, completion: @escaping (Bool) -> Void) {let workItem = DispatchWorkItem(qos: qos) {// 尝试获取锁,带超时if self.resource.lock(before: Date().addingTimeInterval(timeout)) {defer { self.resource.unlock() }completion(true)} else {// 超时处理completion(false)}}queue.async(execute: workItem)}
}// 使用示例
let resource = PriorityAwareResource()// 低优先级任务
DispatchQueue.global(qos: .background).async {resource.accessResource(qos: .background) { success inif success {// 处理资源}}
}// 高优先级任务
DispatchQueue.global(qos: .userInteractive).async {resource.accessResource(qos: .userInteractive) { success inif success {// 处理资源} else {// 处理超时情况print("Resource access timeout")}}
}

说明:通过带超时的锁机制和优先级继承,确保高优先级任务不会无限等待低优先级任务释放资源。

  1. 使用信号量和优先级继承
// 方案2:使用信号量和优先级继承
class PriorityAwareSemaphore {private let semaphore = DispatchSemaphore(value: 1)private let queue = DispatchQueue(label: "com.example.semaphore")func accessResource(qos: DispatchQoS, timeout: TimeInterval = 1.0, block: @escaping () -> Void) {let workItem = DispatchWorkItem(qos: qos) {// 尝试获取信号量,带超时let result = self.semaphore.wait(timeout: .now() + timeout)if result == .success {defer { self.semaphore.signal() }block()} else {// 超时处理print("Resource access timeout")}}queue.async(execute: workItem)}
}// 使用示例
let semaphore = PriorityAwareSemaphore()// 低优先级任务
DispatchQueue.global(qos: .background).async {semaphore.accessResource(qos: .background) {// 处理资源}
}// 高优先级任务
DispatchQueue.global(qos: .userInteractive).async {semaphore.accessResource(qos: .userInteractive) {// 处理资源}
}

说明:通过信号量和超时机制控制资源访问,确保高优先级任务能够及时获取资源或优雅处理超时情况。

最佳实践:

  1. 避免在低优先级任务中持有锁过长时间
  2. 为高优先级任务设置合理的超时时间
  3. 使用读写锁分离读写操作
  4. 优先使用并发队列和栅栏实现线程安全
  5. 在适当的时候使用优先级继承机制

六、总结

Grand Central Dispatch (GCD) 是 Apple 提供的一套强大的并发编程框架,它通过精心设计的分层架构和核心组件,为开发者提供了简单易用的并发编程接口。GCD 的核心优势在于其高效的队列管理、智能的线程池调度、灵活的任务依赖处理以及完善的内存管理机制。

在实际应用中,开发者需要根据具体场景选择合适的队列类型:主队列用于 UI 更新,全局队列用于后台任务,自定义队列用于特定业务场景。同时,通过合理使用调度组、信号量和栅栏等机制,可以有效解决并发编程中的线程安全、死锁和优先级反转等问题。

性能优化方面,GCD 提供了多种优化策略:通过工作窃取算法实现负载均衡,使用对象池减少内存分配,采用多级缓存优化内存访问,以及动态调整线程池大小等。这些特性使得 GCD 能够高效地利用系统资源,提供卓越的并发性能。

最后,遵循最佳实践是确保 GCD 应用稳定运行的关键:避免在主队列中执行耗时操作,合理控制并发数量,及时释放资源,以及正确处理线程安全问题。通过深入理解 GCD 的底层实现原理,开发者可以更好地利用这个强大的工具,构建高性能、可扩展的并发应用。


如果觉得本文对你有帮助,欢迎点赞、收藏、关注我,后续会持续分享更多 iOS 底层原理与实战经验

相关文章:

GCD 深入解析:从使用到底层实现

前言 Grand Central Dispatch (GCD) 是 Apple 基于 C 语言开发的一套完整的并发编程框架。它不仅仅是一个简单的线程管理工具&#xff0c;而是一个高度优化的并发编程解决方案。GCD 的设计理念是将并发编程的复杂性封装在框架内部&#xff0c;为开发者提供简单易用的接口。本文…...

JavaScript中的AES加密与解密:原理、代码与实战

前言 关于有js加密、js解密&#xff0c;js业务相关&#xff0c;找jsjiami官网站长v。 另外前段时间做了个单子跑单了&#xff0c;出售TEMU助手。eller点kuajingmaihuo点com的全自动化助手&#xff0c;可以批量合规&#xff0c;批量实拍图&#xff0c;批量资质上传等。 一、A…...

计算机组成原理实验(7) 堆指令部件模块实验

实验七 堆指令部件模块实验 一、实验目的 1、掌握指令部件的组成方式。 2、熟悉指令寄存器的打入操作&#xff0c;PC计数器的设置和加1操作&#xff0c;理解跳转指令的实现过程。 二、实验要求 按照实验步骤完成实验项目&#xff0c;掌握数据打入指令寄存器IR1、PC计数器的…...

Windows系统下Node.js环境部署指南:使用nvm管理多版本

Windows系统下Node.js环境部署指南&#xff1a;使用nvm管理多版本 一、Node.js介绍二、为什么需要nvm&#xff1f;三、安装前的准备工作1. 本次环境说明2. 卸载现有Node.js&#xff08;如有&#xff09; 三、nvm-windows安装步骤1. 下载安装包2. 安装过程3. 验证安装 四、使用n…...

数据结构*队列

队列 什么是队列 是一种线性的数据结构&#xff0c;和栈不同&#xff0c;队列遵循“先进先出”的原则。如下图所示&#xff1a; 在集合框架中我们可以看到LinkedList类继承了Queue类&#xff08;队列&#xff09;。 普通队列&#xff08;Queue&#xff09; Queue中的方法 …...

C语言蓝桥杯真题代码

以下是不同届蓝桥杯C语言真题代码示例&#xff0c;供参考&#xff1a; 第十三届蓝桥杯省赛 C语言大学B组 真题&#xff1a;卡片 题目&#xff1a;小蓝有很多数字卡片&#xff0c;每张卡片上都是数字1-9。他想拼出1到n的数列&#xff0c;每张卡片只能用一次&#xff0c;求最大的…...

Sharding-JDBC分库分表中的热点数据分布不均匀问题及解决方案

引言 在现代分布式应用中&#xff0c;使用Sharding-JDBC进行数据库的分库分表是提高系统性能和扩展性的常见策略。然而&#xff0c;在实际应用中&#xff0c;某些特定的数据&#xff08;如最新订单、热门商品等&#xff09;可能会成为“热点”&#xff0c;导致这些部分的数据处…...

Dagster中的Ops与Assets:数据管道构建的两种选择

Dagster是一个强大的数据编排平台&#xff0c;它提供了多种工具来帮助数据工程师构建可靠的数据管道。在Dagster中&#xff0c;Ops和Assets是两种核心概念&#xff0c;用于定义数据处理逻辑。本文将全面介绍Ops的概念、特性及其使用方法&#xff0c;特别补充了Op上下文和Op工厂…...

thonny提示自动补全功能

THONNY IDE 自动补全功能配置 在 Thonny IDE 中启用和优化自动补全功能可以显著提升编程体验。为了确保该功能正常工作&#xff0c;需要确认几个设置选项。 配置自动补全 Thonyy IDE 的自动补全默认情况下是开启的。如果发现自动补全未按预期运行&#xff0c;可以通过调整首选…...

PyTorch_阿达玛积

阿达玛积指的是矩阵对应位置的元素相乘&#xff0c;可以使用乘号运算符&#xff0c;也可以使用mul函数来完成计算。 代码 import torch import numpy as np # 1. 使用 mul 函数 def test01():data1 torch.tensor([[1, 2], [3, 4]])data2 torch.tensor([[5, 6], [7, 8]])dat…...

蓝桥杯 摆动序列

摆动序列 原题目链接 题目描述 如果一个序列的奇数项都比前一项大&#xff0c;偶数项都比前一项小&#xff0c;则称为一个摆动序列。 即对于任意整数 i&#xff08;i ≥ 1&#xff09;满足&#xff1a; a₂ᵢ < a₂ᵢ₋₁&#xff0c;a₂ᵢ₊₁ > a₂ᵢ 小明想知道&…...

AI 与生物技术的融合:开启精准医疗的新纪元

在科技飞速发展的今天&#xff0c;人工智能&#xff08;AI&#xff09;与生物技术的融合正在成为推动医疗领域变革的重要力量。精准医疗作为现代医学的重要发展方向&#xff0c;旨在通过深入了解个体的基因信息、生理特征和生活方式&#xff0c;为患者提供个性化的治疗方案。AI…...

三、shell脚本--运算符与表达式:让脚本学会“思考”

一、算术运算符&#xff1a;加减乘除取模 在我们写shell脚本时&#xff0c;做点基本的数学运算还是经常需要的。常用的算术运算符跟我们平时学的一样&#xff1a; : 加- : 减* : 乘 (小提示&#xff1a;有时候在某些命令里可能需要写成 \*)/ : 除 (在 Shell 里通常是取整数部分…...

c++ 指针参数传递的深层原理

指针参数传递的深层原理 理解为什么可以修改指针指向的内容但不能直接修改指针本身&#xff0c;需要深入理解指针在内存中的表示方式和函数参数传递机制。 1. 指针的内存表示 指针本质上是一个变量&#xff0c;它存储的是另一个变量的内存地址。在内存中&#xff1a; 假设有…...

【查看.ipynp 文件】

目录 如何打开 .ipynb 文件&#xff1f; 如果确实是 .ipynp 文件&#xff1a; .ipynp 并不是常见的 Jupyter Notebook 文件格式。通常&#xff0c;Jupyter Notebook 文件的扩展名是 .ipynb&#xff08;即 Interactive Python Notebook&#xff09;。如果你遇到的是 .ipynb 文…...

C++ 简单工厂模式详解

简单工厂模式&#xff08;Simple Factory Pattern&#xff09;是最简单的工厂模式&#xff0c;它不属于GoF 23种设计模式&#xff0c;但它是工厂方法模式和抽象工厂模式的基础。 概念解析 简单工厂模式的核心思想是&#xff1a; 将对象的创建逻辑集中在一个工厂类中 客户端不…...

ubuntu使用apt安装软件

1、使用apt list |grep jdk查看要安装的软件 此处以jdk为例 2、执行名称&#xff1a;安装指定版本的软件 sudo apt install openjdk-11-jdk...

TFT(薄膜晶体管)和LCD(液晶显示器)区别

TFT&#xff08;薄膜晶体管&#xff09;和LCD&#xff08;液晶显示器&#xff09;是显示技术中常见的术语&#xff0c;二者既有联系又有区别。以下是它们的核心区别和关系&#xff1a; 1. 基本概念 LCD&#xff08;液晶显示器&#xff09; LCD是一种利用液晶材料特性控制光线通…...

【文献阅读】中国湿地随着保护和修复的反弹

一、研究背景 滨海湿地是全球最具生态价值的生态系统之一&#xff0c;广泛分布在河口、潮间带、泻湖和盐沼等地带&#xff0c;在调节气候、水质净化、生物栖息以及防止海岸侵蚀等方面发挥着关键作用。然而&#xff0c;近年来滨海湿地正面临严峻威胁&#xff0c;全球估计约有50%…...

用Ensaio下载GIS数据

文章目录 简介重力场绘制 简介 Ensaio在葡萄牙语中是随笔的意思&#xff0c;是一个用于下载开源数据集的python库。其底层基于Pooch来下载和管理数据。 Ensaio可通过pip或者conda来安装 pip isntall ensaio conda install ensaio --channel conda-forge由于这个库功能较为单…...

【算法基础】递归算法 - JAVA

一、递归基础 1.1 什么是递归算法 递归算法是一种通过函数调用自身来解决问题的方法。简单来说&#xff0c;就是"自己调用自己"。递归将复杂问题分解为同类的更简单子问题&#xff0c;直到达到易于直接解决的基本情况。 1.2 递归的核心要素 递归算法由两个关键部…...

连续变量与离散变量的互信息法

1. 互信息法简介 互信息&#xff08;Mutual Information, MI&#xff09; 是一种衡量两个变量之间相互依赖程度的统计量&#xff0c;它来源于信息论。互信息可以用于评估特征与目标变量之间的相关性&#xff0c;无论这些变量是连续的还是离散的。互信息法是一种强大的特征选择…...

java_Lambda表达式

1、背景 lambda表达式是Java SE 8中一个重要的新特性。lambda表达式允许你通过表达式来代替功能接口。lambda表达式就和方法一样样&#xff0c;它提供了一个正常的参数列表和一个使用这些参数的主体&#xff08;body&#xff0c;可以是一个表达式和一个代码块&#xff09;。La…...

Python Cookbook-6.17 NuIl对象设计模式的实现

任务 你想减少代码中的条件声明&#xff0c;尤其是针对特殊情况的检查。 解决方案 一种常见的代表“这里什么也没有”的占位符是 None&#xff0c;但我们还可以定义一个类&#xff0c;其行为方式和这种占位符相似&#xff0c;而且效果更好: class Null(object):Null对象总是…...

Java接口全面教程:从入门到精通

目录 接口的基本概念 接口的特性 1. 访问修饰符 2. 接口中的常量 3. 接口中的方法 3.1 抽象方法&#xff08;传统用法&#xff09; 3.2 默认方法&#xff08;Java 8 引入&#xff09; 3.3 静态方法&#xff08;Java 8 引入&#xff09; 3.4 私有方法&#xff08;Java …...

Power Query精通指南3:数据库(查询折叠与数据隐私)、批量合并文件、自定义函数

文章目录 九、批量合并文件9.1 案例背景9.2 合并文件的标准流程9.3 示例&#xff1a;合并文件9.3.1 连接到文件夹9.3.1.1 连接到本地 / 网络文件夹9.3.1.2 连接到 SharePoint 文件夹9.3.1.3 连接到 OneDrive for Business9.3.1.4 连接到其他文件系统 9.3.2 筛选文件9.3.3 合并文…...

Python 学习

这里主要是为了记录我学习Python的过程&#xff0c;更多是使我规范书写Pyhton语言&#xff01; 1. 第一章 Python 定义&#xff1a;一种解释型的语言&#xff0c;区别于其他的高级语言&#xff0c;逐行翻译进行执行。 过程&#xff1a;首先编写编程语言&#xff0c;利用Pytho…...

生成式 AI 的优势

在科技飞速发展的今天,人工智能已经不再是一个遥不可及的概念,而是逐渐渗透到我们生活的方方面面。其中,生成式 AI 更是如同一颗璀璨的新星,在人工智能的浩瀚星空中闪耀着独特的光芒。它究竟有哪些令人瞩目的优势,又为何会成为我们这个时代无法忽视的存在呢? 生成式 AI …...

Hal库下备份寄存器

首先要确保有外部电源给VBAT供电 生成后应该会有这两个文件&#xff08;不知道为什么生成了好几次都没有&#xff0c;复制工程在试一次就有了&#xff09; 可以看到stm32f407有20个备份寄存器 读写函数 void HAL_RTCEx_BKUPWrite(RTC_HandleTypeDef *hrtc, uint32_t Backup…...

P1537 数字反转(升级版)详解

这个题目还是对于新手比较锻炼思维严谨性的&#xff0c;我认为是在我做过的一些题目中&#xff0c;此题算上等马 先看题目 我先说明我自己的思路&#xff0c;以及这个题目你需要特别注意的地方 1&#xff0c;数字反转&#xff0c;①可用<algorithm>库里面的reverse函数…...

operator 可以根据需要重载 == 运算符进行比较

要将 vector<AppInfo> 类型的 A 和 B 两个容器进行比较&#xff0c;并且当 B 中有 A 中没有的元素时&#xff0c;插入到数据库中&#xff0c;你可以通过以下步骤实现&#xff1a; 比较元素&#xff1a;遍历 vector<B>&#xff0c;检查每个元素是否在 vector<A&…...

网格不迷路:用 CSS 网格生成器打造完美布局

前言 你是否曾因写错 grid-template-areas 而捶键盘?是否在面对千层嵌套的复杂布局时,瞬间怀疑人生,甚至思考要不要转行去卖奶茶?别慌,CSS 网格生成器闪亮登场,像拼乐高一样,帮你轻松搭建网页结构,还能自动输出干净代码,堪称“前端界的乐高大师”。让我们放下枯燥的代…...

Go小技巧易错点100例(二十八)

本期分享&#xff1a; 1. runtime.Caller(1)获取调用者信息 2. for循环 select{}语法 正文&#xff1a; runtime.Caller(1)获取调用者信息 在 Go 语言中&#xff0c;runtime.Caller(1) 是 runtime 包提供的一个函数&#xff0c;用于获取当前 goroutine 的调用堆栈中的特定…...

Java变量简介

Java变量 -为什么需要变量? 一个程序就是一个世界 变量是程序的基本组成单位 不论是使用哪种高级程序语言编写程序,变量都是其程序的基本组成单位,比如: //变量有三个基本要素(类型+名称+值) class Test{public static void main(String [largs){int a=1;int b=3:b=89;Syst…...

Java快速上手之实验六

1. 编写ItemEventDemo.java&#xff0c;当选中或取消选中单选钮、复选钮和列表框时显示所选的结果。 2&#xff0e;编写GUIExample.java&#xff0c;当选中或取消选中单选钮、复选钮时在标签中显示相应结果。 import javax.swing.*; import java.awt.*; import java.awt.event.…...

【算法应用】基于灰狼算法优化深度信念网络回归预测(GWO-DBN)

目录 1.深度信念网络&#xff08;Deep Belief Networks, DBNs&#xff09;2.灰狼算法GWO原理3.结果展示4.参考文献5.代码获取6.读者交流 1.深度信念网络&#xff08;Deep Belief Networks, DBNs&#xff09; 深度信念网络&#xff08;Deep Belief Networks, DBNs&#xff09;是…...

基于Spring Boot实现STDIO通信的MCP Server与验证

STDIO 是一种基于标准输入输出(Standard Input/Output)的本地通信机制,旨在实现客户端与服务端之间的高效交互。 STDIO 是 MCP 协议支持的传输方式之一,通过操作系统的管道机制(stdin/stdout)进行数据传输,适用于客户端与服务端在同一台机器上的本地通信场景。 本篇基于…...

springboot基于推荐算法的景点推荐系统(源码+lw+部署文档+讲解),源码可白嫖!

摘要 本景点推荐系统采用B/S架构&#xff0c;数据库是MySQL&#xff0c;网站的搭建与开发采用了先进的Java进行编写&#xff0c;使用了协同过滤推荐算法和Spring Boot框架。该系统从两个对象&#xff1a;由管理员和用户来对系统进行设计构建。前台主要功能包括&#xff1a;用户…...

【LeetCode Hot100】栈篇

前言 本文用于整理LeetCode Hot100中题目解答&#xff0c;因题目比较简单且更多是为了面试快速写出正确思路&#xff0c;只做简单题意解读和一句话题解方便记忆。但代码会全部给出&#xff0c;方便大家整理代码思路。 20. 有效的括号 一句话题意 验证括号序列有效性。 一句话…...

IO模型和多路复用

一、IO模型的基础理解 什么是IO? IO全称是 Input/Output(输入/输出),在计算机科学里主要指程序与外部设备(硬盘、网络、用户终端等)进行数据交换的操作。首要特点是: IO通常很慢(从CPU和内存的视角看)经常需要等待外部设备响应1. 为什么要谈IO模型? 当一个程序需要…...

私人医生通过AI分析基因数据,是否有权提前告知癌症风险?

首席数据官高鹏律师团队编著 在精准医疗的浪潮中&#xff0c;私人医生借助AI技术解析基因数据、预判癌症风险&#xff0c;已成为高端医疗服务的“隐形标配”。然而&#xff0c;这一技术的光环之下&#xff0c;潜藏着法律与伦理的复杂博弈——医生是否有权基于AI的基因分析提前…...

day 11 超参数调整

一、内参与外参&#xff08;超参数&#xff09; 内参是模型为了适应训练数据而自动调整的&#xff0c;是模型内部与训练数据紧密相关的因素&#xff0c;不同的训练数据会导致模型学习到不同的参数值&#xff0c;这些参数在模型训练完成后就固定下来。 超参数是在模型训练前需…...

纯Java实现STDIO通信的MCP Server与客户端验证

在 MCP 协议中通过 STDIO(标准输入/输出)通信 是一种进程间通信(IPC)方式,服务器与客户端通过标准输入(stdin)和标准输出(stdout)交换数据。 关于STDIO 详细介绍以及如何基于Spring Boot项目实现 STDIO 的MCP服务器 以及如何调用和验证服务器可以参考: 基于Spring …...

Vue3学习笔记2——路由守卫

路由守卫 全局 router.beforeEach((to, from, next) > {})router.afterEach((to, from, next) > {}) 组件内守卫 beforeRouteEnter((to, from, next) > {})beforeRouteUpdate((to, from, next) > {})beforeRouteLeave((to, from, next) > {}) 路由独享 be…...

Three.js在vue中的使用(二)-加载、控制

在 Vue 中使用 Three.js 加载模型、控制视角、添加点击事件是构建 3D 场景的常见需求。下面是一个完整的示例&#xff0c;演示如何在 Vue 单文件组件中实现以下功能&#xff1a; 使用 GLTFLoader 加载 .glb/.gltf 模型添加 OrbitControls 控制视角&#xff08;旋转、缩放、平移…...

【堆】最大堆、最小堆以及GO语言的实现

堆是计算机科学中一种特别的完全二叉树结构&#xff0c;在优先队列、图算法和排序算法中有广泛应用。本文将从概念、原理和实现等方面详细介绍堆这一重要的数据结构。 1. 堆的基本概念 1.1 什么是堆&#xff1f; 堆&#xff08;Heap&#xff09;是一种特殊的完全二叉树&…...

动态规划之路劲问题3

解析题目&#xff1a; 跟之前路径题目大概一样&#xff0c;从左上角到右下角&#xff0c;每一步只能向下或者向右&#xff0c;而且每次走出来血量必须大于0&#xff08;注意这一点&#xff0c;否则容易导致每次出来可能小于0就可能错&#xff09; 算法分析&#xff1a; 状态…...

学习黑客网络安全法

在正式“开荒”各种黑客工具前&#xff0c;Day 4 的任务是给自己装上一副合规与伦理的“护身铠”。这一小时你将弄懂——做渗透想合法必须先拿授权、哪些法律条款碰不得、等保 2.0 与关基条例为何对企业像副“主线任务”&#xff1b;同时动手把这些要点制成一张“法律速查卡”&…...

节流 和 防抖的使用

节流&#xff08;Throttle&#xff09;是一种常用的性能优化技术&#xff0c;用于限制函数的执行频率&#xff0c;确保在一定时间内只执行一次。它常用于处理浏览器事件&#xff08;如滚动、窗口调整大小、鼠标移动等&#xff09;&#xff0c;以避免因事件触发过于频繁而导致的…...

关于项目中优化使用ConcurrentHashMap来存储锁对象

方案介绍 在开发用户创建私有空间功能时&#xff0c;我们的规则是一个用户最多只能创建一个私有空间。 在最初方案中&#xff0c;我是采用字符串常量池的方式存储锁对象useID。通过intern方法保证 同一用户ID的锁 唯一性。这一方案存在的问题是&#xff1a; 随着userId越来越…...