iOS GCD
GCD
任务+队列
主队列: 任务在主线程执行,主队列是一个串行队列,它主要处理 UI 相关任务,也可以处理其他类型任务,但为了性能考虑,尽量让主队列执行 UI 相关或少量不耗时间和资源的操作。
系统全局并发队列:全局并发队列,存在 5 个不同的 QoS 级别,可以使用默认优先级,也可以单独指定
并行队列: 先进先出,多个任务可以并行执行
串行队列 : 先进先出,同一时间只能执行一个任务
基本操作:
sync 同步
任务一经提交就会阻塞当前线程(当前线程可以理解为下方代码示例中执行 sync
方法所在的线程 thread0
),并请求队列立即安排其执行,执行任务的线程 thread1
默认等于 thread0
,即同步任务直接在当前线程运行,任务完成后恢复线程原任务。
async 异步
任务提交后不会阻塞当前线程,会由队列安排另一个线程执行。
1. 同步(Synchronous)
• 定义:同步意味着任务是按顺序执行的,一个任务完成后,才会开始下一个任务。在执行过程中,程序会阻塞,等待当前任务完成之后才能继续执行下一步。
• 特征:
• 阻塞:程序在执行某个任务时,会等待该任务完成,然后才会继续执行其他任务。
• 执行顺序:任务按照书写的顺序依次执行。
• 简单直观:容易理解,执行顺序清晰,但当任务较慢时会浪费时间,因为程序需要等待任务完成。
• 示例:
假设你需要从数据库中获取数据,读取数据的过程是同步的:
func fetchData() {let data = loadDataFromDatabase() // 阻塞直到数据加载完成processData(data) // 数据加载完成后才会执行
}
这意味着,如果 loadDataFromDatabase() 操作需要很长时间(比如网络请求、文件读取等),那么程序在此期间会被阻塞,不能执行其他任务。
2. 异步(Asynchronous)
• 定义:异步意味着任务的执行不阻塞主线程。任务在后台执行时,程序可以继续执行其他操作,直到任务完成时,通知程序来处理结果。
• 特征:
• 非阻塞:程序不会等待当前任务完成,允许其他操作继续执行。
• 回调:当异步任务完成时,通常会通过回调函数(比如闭包、代理等)来通知主程序,处理结果。
• 并发:多个任务可以并行执行,从而提高效率,避免长时间的阻塞。
• 示例:
假设你需要从数据库中获取数据,但希望不阻塞主线程,使用异步方法来加载数据:
func fetchData() {loadDataFromDatabaseAsync { data in // 异步加载数据,回调处理processData(data) // 数据加载完成后执行回调}
}
在这个例子中,loadDataFromDatabaseAsync 会在后台加载数据,主线程不受阻塞,其他操作可以继续进行。当数据加载完成时,回调函数会被调用,随后执行 processData(data)。
同步和异步的区别总结
特性 | 同步 (Synchronous) | 异步 (Asynchronous) |
---|---|---|
执行方式 | 按顺序执行,任务完成后再执行下一个 | 任务在后台执行,程序可以继续执行其他操作 |
程序等待 | 程序会等待任务完成,任务执行时会阻塞 | 程序不等待任务完成,可以继续执行其他任务 |
阻塞与非阻塞 | 阻塞,当前任务未完成无法继续执行其他任务 | 非阻塞,任务在后台执行,主线程不受阻塞 |
适用场景 | 适合任务简单、快速,或必须按顺序执行的场景 | 适合任务需要较长时间、可以并发处理的场景 |
举个例子:下载文件
假设你需要从互联网上下载一个文件:
1. 同步下载:如果你使用同步方式下载文件,程序会等待文件下载完成后,才会继续执行后续的操作。在文件下载的过程中,程序会被“卡住”,无法做其他事情,直到下载完成。
2. 异步下载:如果你使用异步方式下载文件,程序会立即开始下载并跳转到下一步操作,文件下载则在后台进行。下载完成后,程序会通过回调或通知来处理下载结果,这样你可以在下载文件的同时执行其他任务。
优缺点对比
• 同步:
• 优点:简单、直观,适合任务简单且必须顺序执行的场景。
• 缺点:如果一个任务耗时较长,会阻塞整个程序,导致性能瓶颈。
• 异步:
• 优点:不会阻塞主线程,适合长时间运行的任务,可以提高程序效率和响应性。
• 缺点:实现较为复杂,通常需要回调、闭包、Promise、异步/并发框架等机制来处理。
实际应用
• 同步:适合计算密集型操作或那些不需要等待的任务(如简单的计算)。
• 异步:适合网络请求、文件 I/O、数据库查询等耗时操作,这样可以避免程序界面卡顿或响应迟缓。
理解同步与异步的差异有助于提高程序的效率,尤其是在面对网络请求或处理大规模数据时。
创建队列:
提供了两种队列,系统队列(全局并发队列),自定义队列
对于自定义队列,默认是串行的
-
串行队列同一时间只会使用同一线程、运行同一任务,并严格按照任务顺序执行。
-
并行队列同一时间可以使用多个线程、运行多个任务,执行顺序不分先后。
-
同步任务会阻塞当前线程,并在当前线程执行。
-
异步任务不会阻塞当前线程,并在与当前线程不同的线程执行。
-
如何避免死锁:不要在串行或主队列中嵌套执行同步任务。
栏栅任务
栅栏任务的主要特性是可以对队列中的任务进行阻隔,执行栅栏任务时,它会先等待队列中已有的任务全部执行完成,然后它再执行,在它之后加入的任务也必须等栅栏任务执行完后才能执行。
这个特性更适合并行队列,而且对栅栏任务使用同步或异步方法效果都相同。
- 创建方式,先创建
WorkItem
,标记为:barrier
,再添加至队列中:
let queue = DispatchQueue(label: "queueName", attributes: .concurrent)
let task = DispatchWorkItem(flags: .barrier) {// do something
}
queue.async(execute: task)
queue.sync(execute: task) // 与 async 效果一样
迭代任务
并行队列利用多个线程执行任务,可以提高程序执行的效率。而迭代任务可以更高效地利用多核性能,它可以利用 CPU 当前所有可用线程进行计算(任务小也可能只用一个线程)。如果一个任务可以分解为多个相似但独立的子任务,那么迭代任务是提高性能最适合的选择。
使用 concurrentPerform
方法执行迭代任务,迭代任务的后续任务需要等待它执行完成才会继续。本方法类似于 Objc 中的 dispatch_apply
方法,创建方式如下:
DispatchQueue.concurrentPerform(iterations: 10) {(index) -> Void in // 10 为迭代次数,可修改。// do something
}
迭代任务可以单独执行,也可以放在指定的队列中:
let queue = DispatchQueue.global() // 全局并发队列
queue.async {DispatchQueue.concurrentPerform(iterations: 100) {(index) -> Void in// do something}//可以转至主线程执行其他任务DispatchQueue.main.async {// do something}
}
延迟加入队列
class AsyncAfter {typealias ExchangableTask = (_ newDelayTime: TimeInterval?,_ anotherTask:@escaping (() -> ())) -> Void/// 延迟执行一个任务,并支持在实际执行前替换为新的任务,并设定新的延迟时间。////// - Parameters:/// - time: 延迟时间/// - yourTask: 要执行的任务/// - Returns: 可替换原任务的闭包static func delay(_ time: TimeInterval, yourTask: @escaping ()->()) -> ExchangableTask {var exchangingTask: (() -> ())? // 备用替代任务var newDelayTime: TimeInterval? // 新的延迟时间let finalClosure = { () -> Void inif exchangingTask == nil {DispatchQueue.main.async(execute: yourTask)} else {if newDelayTime == nil {DispatchQueue.main.async {print("任务已更改,现在是:\(Date())")exchangingTask!()}}print("原任务取消了,现在是:\(Date())")}}dispatch_later(time) { finalClosure() }let exchangableTask: ExchangableTask ={ delayTime, anotherTask inexchangingTask = anotherTasknewDelayTime = delayTimeif delayTime != nil {self.dispatch_later(delayTime!) {anotherTask()print("任务已更改,现在是:\(Date())")}}}return exchangableTask}
}
delay
方法接收两个参数,并返回一个闭包:
- TimeInterval:延迟时间
- @escaping () -> (): 要延迟执行的任务
- 返回:可替换原任务的闭包,我们去了一个别名:
ExchangableTask
ExchangableTask
类型定义的闭包,接收一个新的延迟时间,和一个新的任务。
如果不执行返回的闭包,则在delay
方法内部,通过 dispatch_later
方法会继续执行原任务。
如果执行了返回的 ExchangableTask
闭包,则会选择执行新的任务。
线程安全和线程同步
读写操作
let queue = DispatchQueue(label: "com.example.queue", attributes: .concurrent)
var array = [Int]()// 写操作
queue.async(flags: .barrier) {array.append(1)array.append(2)
}// 读操作
queue.async {print(array)
}
什么时候用串行队列?什么时候用并发队列 + 栅栏?
• 用串行队列:
• 当你的任务量不大,或对性能要求不高时。
• 任务的顺序性更重要,所有操作按严格顺序执行。
• 用并发队列 + 栅栏:
• 当你的任务中有大量的读操作,并且需要同时保证写操作的安全性时。
• 适用于性能要求较高的场景,例如数据库、多线程缓存读写操作。
所以,串行队列的实现虽然简单,但并发队列 + 栅栏在性能和灵活性上更有优势,特别是在读多写少的场景下。
多线程典型场景
-
一个页面有三个网络请求,需要在三个网络请求都返回的时候刷新页面
使用dispatch group
-
实现一个线程安全的Array的读和写
import Foundationclass ThreadSafeArray<T> {private var array: [T] = []private let queue = DispatchQueue(label: "com.example.ThreadSafeArray", attributes: .concurrent)// 写操作:追加元素func append(_ item: T) {queue.async(flags: .barrier) {self.array.append(item)}}// 写操作:移除元素func remove(at index: Int) {queue.async(flags: .barrier) {guard index < self.array.count else { return }self.array.remove(at: index)}}// 读操作:通过索引访问func get(at index: Int) -> T? {var result: T?queue.sync {guard index < self.array.count else { return }result = self.array[index]}return result}// 读操作:获取数组的所有内容func getAll() -> [T] {var result: [T] = []queue.sync {result = self.array}return result}// 读操作:获取数组的大小var count: Int {var result = 0queue.sync {result = self.array.count}return result} }
代码的线程安全性
• 写操作:
• 使用 async(flags: .barrier) 保证写操作互斥。
• 当有一个写操作在执行时,队列会阻止其他读和写操作,确保数据一致性。
• 读操作:
• 通过 queue.sync 保证读操作线程安全。
• 即使多个读操作同时进行,也不会干扰,因为并发队列允许多个 sync 操作并行。
代码执行逻辑示例
1. 写操作的独占性
假设同时有以下两个任务:
• array.append(1)
• array.remove(at: 0)
因为写操作使用了 .barrier,它们不会同时执行,队列会保证第一个写操作完成后再执行下一个写操作。
2. 读写互斥
假设同时有以下任务:
• 读取元素 array.getAll()
• 添加新元素 array.append(1)
在这种情况下:
1. 读操作通过 sync 提交到队列中,可以立即并发执行。
2. 写操作通过 barrier 提交时,会等待所有正在进行的读操作完成后再执行。
3. 并发读
假设同时有以下任务:
• array.getAll()
• array.get(at: 0)
因为读操作是通过 sync 提交到并发队列中,它们可以并行执行,不会互相阻塞。
同步(Synchronous)和异步(Asynchronous)是编程中两种不同的执行方式,主要区别在于任务的执行顺序和程序的等待行为。
信号量
DispatchSemaphore
,通常称作信号量,顾名思义,它可以通过计数来标识一个信号,这个信号怎么用呢,取决于任务的性质。通常用于对同一个资源访问的任务数进行限制。
例如,控制同一时间写文件的任务数量、控制端口访问数量、控制下载任务数量等。
信号量的使用非常的简单:
- 首先创建一个初始数量的信号对象
- 使用
wait
方法让信号量减 1,再安排任务。如果此时信号量仍大于或等于 0,则任务可执行,如果信号量小于 0,则任务需要等待其他地方释放信号。 - 任务完成后,使用
signal
方法增加一个信号量。 - 等待信号有两种方式:永久等待、可超时的等待。
基础源码解析
队列和线程之间的关系
一个重要概念是overcommit,overcommit的队列在队列创建时会新建一个线程,非overcommit队列创建队列则未必创建线程。另外width=1意味着是串行队列,只有一个线程可用,width=0xffe则意味着并行队列,线程则是从线程池获取,可用线程数是64个。
可以看到全局队列是非overcommit的(flat保留字只能传0,如果默认优先级则是com.apple.root.default-qos,但是width=0xffe是并行队列);主队列是overcommit的com.apple.root.default-qos.overcommit,不过它是串行队列,width=1,并且运行的这个线程只能是主线程;自定义串行队列是overcommit的,默认优先级则是 com.apple.root.default-qos.overcommit,并行队列则是非overcommit的。
串行并行的判断
首先通过width判定是串行队列还是并发队列,如果是并发队列则调用_dispatch_sync_invoke_and_complete
,串行队列则调用_dispatch_barrier_sync_f
死锁的判断
_dispatch_lock_is_locked_by(dispatch_lock lock_value, dispatch_tid tid)
{// equivalent to _dispatch_lock_owner(lock_value) == tidreturn ((lock_value ^ tid) & DLOCK_OWNER_MASK) == 0;
}
通过判断队列的状态和线程的状态,把这两个进行异或操作,如果状态相同的话,就返回yes,产生死锁。
如何执行调度
DISPATCH_NOINLINE
static void
_dispatch_sync_invoke_and_complete_recurse(dispatch_queue_class_t dq,void *ctxt, dispatch_function_t func, uintptr_t dc_flagsDISPATCH_TRACE_ARG(void *dc))
{_dispatch_sync_function_invoke_inline(dq, ctxt, func);_dispatch_trace_item_complete(dc);_dispatch_sync_complete_recurse(dq._dq, NULL, dc_flags);
}// 看一下 _dispatch_sync_function_invoke_inlineDISPATCH_ALWAYS_INLINE
static inline void
_dispatch_sync_function_invoke_inline(dispatch_queue_class_t dq, void *ctxt,dispatch_function_t func)
{dispatch_thread_frame_s dtf;_dispatch_thread_frame_push(&dtf, dq);_dispatch_client_callout(ctxt, func);_dispatch_perfmon_workitem_inc();_dispatch_thread_frame_pop(&dtf);
}// 看一下 _dispatch_client_calloutvoid
_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{@try {return f(ctxt);}@catch (...) {objc_terminate();}
}
典型流程示例
假设通过 dispatch_sync(queue, ^{ ... })
提交任务:
- 任务入队:
- 如果队列空闲,直接调用
_dispatch_sync_invoke_and_complete_recurse
。
- 如果队列空闲,直接调用
- 执行任务:
_dispatch_sync_function_invoke_inline
调用用户代码。- 线程帧压栈,记录当前队列。
- 异常处理:
- 用户代码若抛出异常,进程终止。
- 清理资源:
- 线程帧弹栈,递归更新队列状态,唤醒其他任务。
异步操作:
static void
_dispatch_root_queue_poke_slow(dispatch_queue_global_t dq, int n, int floor)
{int remaining = n;int r = ENOSYS;_dispatch_root_queues_init();_dispatch_debug_root_queue(dq, __func__);_dispatch_trace_runtime_event(worker_request, dq, (uint64_t)n);#if !DISPATCH_USE_INTERNAL_WORKQUEUE#if DISPATCH_USE_PTHREAD_ROOT_QUEUESif (dx_type(dq) == DISPATCH_QUEUE_GLOBAL_ROOT_TYPE)#endif{_dispatch_root_queue_debug("requesting new worker thread for global ""queue: %p", dq);r = _pthread_workqueue_addthreads(remaining,_dispatch_priority_to_pp_prefer_fallback(dq->dq_priority));(void)dispatch_assume_zero(r);return;}#endif // !DISPATCH_USE_INTERNAL_WORKQUEUE#if DISPATCH_USE_PTHREAD_POOLdispatch_pthread_root_queue_context_t pqc = dq->do_ctxt;if (likely(pqc->dpq_thread_mediator.do_vtable)) {while (dispatch_semaphore_signal(&pqc->dpq_thread_mediator)) {_dispatch_root_queue_debug("signaled sleeping worker for ""global queue: %p", dq);if (!--remaining) {return;}}}bool overcommit = dq->dq_priority & DISPATCH_PRIORITY_FLAG_OVERCOMMIT;if (overcommit) {// 串行队列os_atomic_add2o(dq, dgq_pending, remaining, relaxed);} else {if (!os_atomic_cmpxchg2o(dq, dgq_pending, 0, remaining, relaxed)) {_dispatch_root_queue_debug("worker thread request still pending for ""global queue: %p", dq);return;}}// floor 为 0,remaining 是根据队列任务的情况处理的int can_request, t_count;// 获取线程池的大小t_count = os_atomic_load2o(dq, dgq_thread_pool_size, ordered);do {// 计算可以请求的数量can_request = t_count < floor ? 0 : t_count - floor;if (remaining > can_request) {_dispatch_root_queue_debug("pthread pool reducing request from %d to %d",remaining, can_request);os_atomic_sub2o(dq, dgq_pending, remaining - can_request, relaxed);remaining = can_request;}if (remaining == 0) {// 线程池满了,就会报出异常的情况_dispatch_root_queue_debug("pthread pool is full for root queue: ""%p", dq);return;}} while (!os_atomic_cmpxchgvw2o(dq, dgq_thread_pool_size, t_count,t_count - remaining, &t_count, acquire));pthread_attr_t *attr = &pqc->dpq_thread_attr;pthread_t tid, *pthr = &tid;#if DISPATCH_USE_MGR_THREAD && DISPATCH_USE_PTHREAD_ROOT_QUEUESif (unlikely(dq == &_dispatch_mgr_root_queue)) {pthr = _dispatch_mgr_root_queue_init();}#endifdo {_dispatch_retain(dq); // 开辟线程while ((r = pthread_create(pthr, attr, _dispatch_worker_thread, dq))) {if (r != EAGAIN) {(void)dispatch_assume_zero(r);}_dispatch_temporary_resource_shortage();}} while (--remaining);#else(void)floor;#endif // DISPATCH_USE_PTHREAD_POOL
}
这段代码是 GCD(Grand Central Dispatch)中用于管理全局队列(Global Queue)线程池的核心函数 _dispatch_root_queue_poke_slow
,其主要职责是根据任务需求动态创建或唤醒线程。以下是对代码的分层解析:
一、函数背景与核心目标
- 调用场景:当全局队列(
dispatch_queue_global_t
)中有新任务提交,但当前线程池中没有足够空闲线程处理任务时,触发此函数。 - 核心目标:通过创建新线程或唤醒休眠线程,确保任务能够被及时执行。
- 关键参数:
dq
:目标全局队列(可能是并发队列或特定优先级的队列)。n
:需要唤醒或创建的线程数量。floor
:线程池的最小保留线程数(避免过度销毁)。
二、代码逻辑分层解析
1. 初始化与调试(Initialization & Debugging)
_dispatch_root_queues_init(); // 确保全局根队列初始化
_dispatch_debug_root_queue(dq, __func__); // 调试日志
_dispatch_trace_runtime_event(worker_request, dq, (uint64_t)n); // 性能追踪
- 作用:确保根队列已初始化,记录调试信息和性能事件。
2. 内核级 Workqueue 处理(XNU Kernel Workqueue)
#if DISPATCH_USE_PTHREAD_ROOT_QUEUES
r = _pthread_workqueue_addthreads(remaining, _dispatch_priority_to_pp_prefer_fallback(dq->dq_priority));
- 条件:当使用
pthread
工作队列(如 macOS 或 iOS 的默认配置)。 - 行为:直接通过
_pthread_workqueue_addthreads
向 XNU 内核请求线程。 - 机制:
- 内核根据优先级(
dq_priority
转换为pthread_priority_t
)动态调度线程。 - 内核管理的线程生命周期更高效,避免用户态频繁创建/销毁。
- 内核根据优先级(
3. 用户态线程池管理(User-Level Thread Pool)
当不使用内核 Workqueue 时(如某些定制化配置),进入用户态线程池管理逻辑:
3.1 唤醒休眠线程
while (dispatch_semaphore_signal(&pqc->dpq_thread_mediator)) {if (!--remaining) return;
}
- 目的:尝试通过信号量唤醒线程池中休眠的线程。
- 信号量机制:休眠线程通过
dispatch_semaphore_wait
挂起,信号量signal
会唤醒一个线程。 - 优化:避免创建新线程的开销,优先复用休眠线程。
3.2 处理 Overcommit(超发线程)
if (overcommit) {os_atomic_add2o(dq, dgq_pending, remaining, relaxed);
} else {os_atomic_cmpxchg2o(dq, dgq_pending, 0, remaining, relaxed);
}
- Overcommit 标志:表示队列允许超出发起线程数限制(如串行队列的特殊处理)。
- 原子操作:
os_atomic_add2o
:直接增加挂起的线程请求数。os_atomic_cmpxchg2o
:通过 CAS(Compare-and-Swap)确保线程请求数正确。
3.3 动态调整线程请求数
do {can_request = t_count < floor ? 0 : t_count - floor;if (remaining > can_request) {remaining = can_request;}
} while (!os_atomic_cmpxchgvw2o(dq, dgq_thread_pool_size, t_count, t_count - remaining, &t_count, acquire));
- 目标:根据当前线程池大小(
dgq_thread_pool_size
)和floor
值,计算实际可创建的线程数。 - 策略:
- 若当前线程数
t_count
小于floor
,不允许创建新线程。 - 否则,可创建
t_count - floor
个线程。
- 若当前线程数
- 原子操作:通过
os_atomic_cmpxchgvw2o
更新线程池大小,避免竞态条件。
3.4 创建新线程
while ((r = pthread_create(pthr, attr, _dispatch_worker_thread, dq))) {if (r != EAGAIN) (void)dispatch_assume_zero(r);_dispatch_temporary_resource_shortage();
}
- 入口函数:
_dispatch_worker_thread
是线程的入口,负责从队列中获取并执行任务。 - 错误处理:
- 若
pthread_create
返回EAGAIN
(资源不足),调用_dispatch_temporary_resource_shortage
等待后重试。 - 其他错误直接触发断言(
dispatch_assume_zero
)。
- 若
到了这里可以清楚的看到对于全局队列使用_pthread_workqueue_addthreads
开辟线程,对于其他队列使用pthread_create
开辟新的线程。
三、代码流程图
开始
│
├─ 初始化根队列 & 记录调试信息
│
├─ 如果使用内核 Workqueue:
│ ├─ 调用 _pthread_workqueue_addthreads 请求内核创建线程
│ └─ 返回
│
├─ 否则(用户态线程池):
│ ├─ 尝试通过信号量唤醒休眠线程
│ ├─ 处理 Overcommit 标志
│ ├─ 动态调整线程请求数(基于线程池大小和 floor)
│ ├─ 循环创建新线程(处理 EAGAIN 错误)
│ └─ 更新线程池状态
│
└─ 结束
相关文章:
iOS GCD
GCD 任务队列 主队列: 任务在主线程执行,主队列是一个串行队列,它主要处理 UI 相关任务,也可以处理其他类型任务,但为了性能考虑,尽量让主队列执行 UI 相关或少量不耗时间和资源的操作。 系统全局并发队…...
Hadoop集群的常用命令
1.基本命令 启动 Hadoop: start-dfs.sh start-yarn.sh 这些命令会分别启动 Hadoop 的 DFS 和 YARN。 查看 Hadoop 目录: hdfs dfs -ls -R / 该命令会递归地列出 HDFS 根目录下的所有文件和目录。 创建目录: hdfs dfs -mkdir /example_dir 在 HDFS 中创建一个新目录。 上传文…...
Kafka 实战指南:原理剖析与高并发场景设计模式
一、介绍 Kafka是由 Apache 软件基金会开发的开源流处理平台,作为高吞吐量的分布式发布订阅消息系统,采用 Scala 和 Java 编写。 Kafka是一种消息服务(MQ),在理论上可以达到十万的并发。 代表的MQ软件—— kafka 十万…...
黑盒测试的判定表法(能对多条件依赖关系进行设计测试点)
定义: 判定表是分析和表达多逻辑条件下执行不同操作的工具。就是指把所有的输入条件、所有可能采取的动作按表格列出来,每一种条件和动作的组合构成一条规则,也即一条用例。 1.判定表法的引用 等价类边界值分析法主要关注单个输入类条件的测试并未考虑…...
SQLMesh系列教程:基于指标构建一致的分析语义层应用实践
本文深入探讨SQLMesh指标框架的核心概念、定义方法及应用场景。通过统一的语义层管理,SQLMesh解决了数据分析中指标定义不一致的痛点,实现了跨团队协作的数据一致性。文章包含指标定义语法详解、自动表连接机制解析、派生指标构建方法,并通过…...
解决Docker端口映射后外网无法访问的问题
一、前言 今天因为服务器宕机,重新启动后发现docker部署的mysql和redis都无法通过外网访问。经过排查原因是ip转发没有开启。下面教大家如何解决 二、问题排查 (1) 查看防火墙运行情况 使用firewall-cmd --state 如果防火墙处于not running,则可以排…...
如何指定运行amd64架构的ubuntu?
如何指定运行amd64架构的ubuntu 下面这个命令如何制定运行amd64架构的ubuntu? docker run -it -v $(pwd):/workspace ubuntu:20.04 bash这个命令已经非常接近正确运行一个基于 amd64 架构的 Ubuntu 容器了,但如果你想明确指定运行 amd64 架构的镜像&am…...
[MySQL]数据类型
数据类型 1.数据类型分类2.数值类型2.1 tinyint类型无符号类型举例 3.bit类型3.1 基本语法 4. 小数类型4.1 float语法4.2 decimal语法 5.字符串类型5.1 char类型5.2 varchar 6.日期类型7.enum和set查询语句 1.数据类型分类 接下来就对上面的四种类型进行介绍 2.数值类型 数值类…...
基于Python的Django框架的手机购物商城管理系统
标题:基于Python的Django框架的手机购物商城管理系统 内容:1.摘要 随着互联网的快速发展,手机购物逐渐成为人们日常生活中不可或缺的一部分。本研究的目的是开发一个基于Python的Django框架的手机购物商城管理系统,以提高购物商城的管理效率和用户体验。…...
大模型在2型糖尿病预测及围手术期管理中的应用研究
目录 一、引言 1.1 研究背景与意义 1.2 国内外研究现状 1.3 研究目的与创新点 二、大模型预测 2 型糖尿病的原理与方法 2.1 大模型概述 2.2 用于 2 型糖尿病预测的大模型类型 2.3 模型训练与数据来源 2.4 预测指标与算法 三、术前风险预测与评估 3.1 血糖控制情况预…...
JavaEE--多线程
一、认识线程 1. 什么是线程 线程(Thread)是计算机科学中的基本概念,指的是程序内部的一条执行路径。一个进程可以包含多个线程,每个线程共享进程的资源,包括内存空间、文件描述符等。线程可以同时执行多个任务&…...
自动化测试之等待方式
在自动化测试中,等待是一个重要的技术,用于处理页面加载、元素定位、元素状态改变等延迟问题。 等待能够确保在条件满足后再进行后续操作,提高自动化测试的稳定性以及可靠性。 等待方式:显示等待、隐式等待、线程睡眠 1. 显式等…...
git中用于生成commitId与其父commitId间的文件差异文件树
生成commitId与其父commitId间的文件差异文件树 #!/bin/bash # # 用于生成目标commitId与其父commitId间文件差异 # commit_id$1 # 输入目标commit的哈希值 old_dir"old_version" new_dir"new_version"# 创建目录 mkdir -p "$old_dir" "$…...
Ubuntu / Debian 创建快捷方式启动提权
简述 在 Linux 系统中,.desktop 文件是 桌面入口文件,用于在桌面环境(如 GNOME、KDE)中定义应用程序的启动方式、图标、名称等信息。当你执行 touch idea.desktop 时,实际上创建了一个空的 .desktop 文件(…...
VLA 论文精读(三)Diffusion Policy: Visuomotor Policy Learning via Action Diffusion
这篇笔记用来描述 2023年 发表在arxiv上的一篇有关VLA领域的论文,这篇笔记记录的是该论文 2024年03月的改版后。 写在最前面 为了方便你的阅读,以下几点的注意事项请务必了解: 该系列文章每个字都是我理解后自行翻译并写上去的,…...
ASP.NET Core 中实现 SSE 流式响应的简单例子
[HttpGet] public async Task<IActionResult> SseExample() {// 请求头Response.Headers.Add("Content-Type", "text/event-stream");Response.Headers.Add("Cache-Control", "no-cache");Response.Headers.Add("Connectio…...
「Unity3D」TMP_InputField关闭虚拟键盘后,再次打开虚拟键盘,此时无法回调onSelect的问题
TMP_InputField可以注册一个onSelect回调函数,在InputField选中的时候回调,但在虚拟键盘手动关闭或被返回取消的时候,此时再打开虚拟键盘时,就不会调用onSelect。 原因在于,虚拟键盘有三种关闭的操作方式:…...
手工排查后门木马的常用姿势
声明!本文章所有的工具分享仅仅只是供大家学习交流为主,切勿用于非法用途,如有任何触犯法律的行为,均与本人及团队无关!!! 1. 检查异常文件 (1)查找最近修改的文件 # 查…...
VRRP协议
基础概念 Master 路由器:“Master 路由器”在一个 VRRP 组中承担报文转发任务。在每一个 VRRP 组中,只有 Master 路由器才会响应针对虚拟 IP 地址的 ARP Request。Master 路由器会以一定的时间间隔周期性地发送 VRRP 报文,以便通知同一个 VRRP 组中的 B…...
【JavaEE】MyBatis 综合练习(图书管理系统)
目录 一、数据库表二、引入依赖:三、Model创建四、用户登录五、添加图书六、图书列表七、修改图书八、删除图书九、批量删除十、强制登录 图书管理系统 一、数据库表 我们使用两张表,一张用户表uset_test来记录登录的用户信息,一张图书表boo…...
ArkUI —— 组件导航
创建导航页 // src\main\ets\pages\Index.ets Entry Component struct Index {// 路由栈Provide(pathInfos) pathInfos: NavPathStack new NavPathStack()build() {Navigation(this.pathInfos) {}} }创建导航子页 this.navPath.pushPathByName(AccountTag, 账本分类管理)// …...
数据处理与机器学习入门
一、数据处理概述 数据处理是通过统计学、机器学习和数据挖掘方法从原始数据中提取有价值信息的过程。数据处理的目标是将杂乱无章的原始数据转化为可用于分析和建模的结构化数据。对于小规模数据处理,常用工具分为两类: • 可视化分析工具:…...
Markdown在线转word格式
1、打开网址 https://dillinger.io/ 2、输入markdown格式文章 3、直接转换为右边的word格式 4、复制粘贴即可。...
Redis延时队列在订单超时未报到场景的应用分享
一、引言 在电商、医疗预约等众多业务场景中,经常会遇到需要处理超时任务的情况。比如医疗预约订单,如果患者在支付成功后,到了预约结束时间还未报到,系统需要自动取消订单。为了实现这样的功能,我们可以利用 Redis 延…...
vue前端代码作业——待办事项
美化样式示意图: 后端IDEA代码示意图: 代码解释: 1. isAllChecked 计算属性的作用 isAllChecked 用于实现 “全选 / 全不选” 功能,它是一个 双向绑定 的计算属性(因为 v-model 需要同时支持读取和设置值)…...
docker镜像拉取失败
hub.docker.com中提供的docker pull命令在服务器拉取镜像时报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers) 这个错误通常表明Docker客户…...
Ruby 简介
Ruby 简介 引言 Ruby 是一种广泛使用的动态、开源的编程语言,自 1995 年由日本程序员 Yukihiro Matsumoto(通称 Matz)设计以来,它以其优雅的语法、强大的库支持和跨平台特性赢得了全球开发者的青睐。本文将详细介绍 Ruby 的起源、特点、应用领域以及它在现代软件开发中的…...
解决 FFmpeg 使用 C/C++ 接口时,解码没有 shell 快的问题(使用多线程)
一、问题 硬件设备为香橙派 5Plus,最近需要使用硬件视频解码来加速 YOLO 的检测,shell 窗口的FFmpeg已经调通,详见文章: 编译支持 RKmpp 和 RGA 的 ffmpeg 源码_rk3588 ffmpeg mpp-CSDN博客https://blog.csdn.net/plmm__/article…...
sqlalchemy:将mysql切换到OpenGauss
说明 之前python的项目使用的mysql,近期要切换到国产数据库OpenGauss。 之前的方案是fastapisqlalchemy,测试下来发现不用改代码,只要改下配置即可。 切换方案 安装openGauss-connector-python-psycopg2 其代码工程在:https:…...
缓存使用纪要
一、本地缓存:Caffeine 1、简介 Caffeine是一种高性能、高命中率、内存占用低的本地缓存库,简单来说它是 Guava Cache 的优化加强版,是当下最流行、最佳(最优)缓存框架。 Spring5 即将放弃掉 Guava Cache 作为缓存机…...
Qt之Service开发
一、概述 基于Qt的用于开发系统服务(守护进程)和后台服务,有以下几个优秀的开源 QtService 框架和库。 1. QtService (官方解决方案) GitHub: https://github.com/qtproject/qt-solutions/tree/master/qtservice 特点: 官方提供的服务框架 支持 Windows 服务和 Linux 守护…...
ssm框架之Spring
Spring框架介绍 Spring框架是一个轻量级的企业级应用框架 通过它可以贯穿表现层、业务层、持久层。集成方便,简单易用,具有如下特点: Spring框架特色 Spring设计理念 是面向Bean的编程 Spring两大核心技术 控制反转(IoC:Inver…...
Flutter 开发环境配置--宇宙级教学!
目录 一、安装环境(Windows)二、Android 创建Flutter项目三、VSCode 搭建环境四、补充 一、安装环境(Windows) Flutter SDK 下载 推荐使用中国镜像站点下载 Flutter SDK,速度更快:中国环境 或者从官网下载…...
音视频 YUV格式详解
前言 本文介绍YUV色彩模型,YUV的分类和常见格式。 RGB色彩模型 在RGB颜色空间中,任意色光F都可以使用R、G、B三色不同的分量混合相加而成即: F = R + G + B.。即我们熟悉的三原色模型。 RGB色彩空间根据每个分量在计算机中占用的存储字节数可以分为以下几种类型,字节数…...
力扣 第 153 场双周赛 讲题
文章目录 Q1.字符串的反转度Q2.操作后最大活跃区段数I3500.将数组分割为子数组的最小代价 Q1.字符串的反转度 签到题,直接建立一个映射表即可 class Solution:def reverseDegree(self, s: str) -> int:# 先建立映射表ss "abcdefghijklmnopqrstuvwxyz"store {}i…...
grafana 配置页面告警
添加告警规则 1.登录grafana 点击 Alerting > Alert rules 点击 New alert rule 2.填写告警规则名字 3.配置告警规则 选择数据源为 Loki 单机 Builder 单机Label brower 单机 node_name 标签,选择一个主机,选好后单机 Show logs 这时候查询语…...
Cent OS7+Docker+Dify
由于我之前安装了Dify v1.0.0,出现了一些问题:无法删除,包括:知识库中的文件、应用、智能体、工作流,都无法删除。现在把服务器初始化,一步步重新安装,从0到有。 目录 1、服务器重装系统和配置…...
【自学笔记】PHP语言基础知识点总览-持续更新
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 1. PHP 简介2. PHP 环境搭建3. 基本语法变量与常量数据类型运算符 4. 控制结构条件语句循环语句 5. 函数函数定义与调用作用域 6. 数组7. 字符串8. 表单处理9. 会话…...
Android Gradle 下载插件或依赖太慢
问题与处理策略 问题描述 Android 项目中,settings.gradle 文件中,有如下配置,Gradle 插件或依赖下载速度慢 pluginManagement {repositories {gradlePluginPortal()google()mavenCentral()} }dependencyResolutionManagement {repositori…...
python-59-基于python内置库解析html获取标签关键信息
文章目录 1 html.parser1.1 初始化和基础使用1.1.1 handle_starttag(self, tag, attrs)1.1.2 handle_endtag(self, tag)1.1.3 handle_startendtag(self, tag, attrs)1.1.4 handle_data(self, data)1.1.5 handle_comment(self, data)1.2 解析HTML文档的流程2 百度搜索关键词链接…...
elementplus的el-tabs路由式
在使用 Element Plus 的 el-tabs 组件,实现路由式的切换(即点击标签页来切换不同的路由页面)。下面是一个基于 Vue 3 和 Element Plus 实现路由式 el-tabs 的基本步骤和示例。 步骤 1: 安装必要的库 在vue3项目安装 Vue Router 和 Element …...
ArcGIS地理信息系统空间分析实验教程学习
ArcGIS 作为地理信息系统领域的经典软件,以其强大的功能和广泛的应用场景,成为了众多学者、研究人员和专业人士的首选工具。它不仅可以高效地处理和可视化地理空间数据,还能通过复杂的空间分析模型,揭示地理现象背后的规律和趋势。…...
mac部署CAT监控服务
在 Mac 上部署美团点评开源的 CAT 监控服务端,可以按照以下步骤操作: 1. 环境准备 1.1 安装依赖 确保已安装以下工具: JDK 8(建议 OpenJDK 11) MySQL 5.7(存储监控数据)(8.0不支持…...
鸿蒙OS 5 架构设计探秘:从分层设计到多端部署
文章目录 鸿蒙OS架构设计探秘:从分层设计到多端部署一、鸿蒙的分层架构设计二、模块化设计的精髓三、智慧分发设计:资源的动态调度四、一次开发,多端部署的实践总结与思考 鸿蒙OS架构设计探秘:从分层设计到多端部署 最近两年来&a…...
深入解析:ElasticSearch Query 查询方式
全文目录: 开篇语前言摘要概述ElasticSearch Query 查询方式详解1. Match 查询(全文搜索)1.1 Match 查询示例1.2 Match 查询参数扩展 2. Term 查询(精准查询)2.1 Term 查询示例2.2 Terms 查询 3. Bool 查询(…...
HTML5贪吃蛇游戏开发经验分享
HTML5贪吃蛇游戏开发经验分享 这里写目录标题 HTML5贪吃蛇游戏开发经验分享项目介绍技术栈核心功能实现1. 游戏初始化2. 蛇的移动控制3. 碰撞检测4. 食物生成 开发心得项目收获后续优化方向结语 项目介绍 在这个项目中,我使用HTML5 Canvas和原生JavaScript实现了一…...
桥接模式_结构型_GOF23
桥接模式 桥接模式(Bridge Pattern)是一种结构型设计模式,核心思想是将抽象与实现分离,使两者能独立变化。它像一座连接两岸的桥梁,让“抽象层”和“实现层”自由组合,避免因多维度变化导致的“类爆炸”问…...
卡尔曼滤波入门(二)
核心思想 卡尔曼滤波的核心就是在不确定中寻找最优,那么怎么定义最优呢?答案是均方误差最小的,便是最优。 卡尔曼滤波本质上是一种动态系统状态估计器,它回答了这样一个问题: 如何从充满噪声的观测数据中,…...
有关pip与conda的介绍
Conda vs. Pip vs. Virtualenv 命令对比 任务Conda 命令Pip 命令Virtualenv 命令安装包conda install $PACKAGE_NAMEpip install $PACKAGE_NAMEX更新包conda update --name $ENVIRONMENT_NAME $PACKAGE_NAMEpip install --upgrade $PACKAGE_NAMEX更新包管理器conda update con…...
【Portainer】Docker可视化组件安装
Portainer Portainer 是用于管理容器化环境的一体化平台工程解决方案,提供广泛的定制功能,以满足个人开发人员和企业团队的需求。 官方地址: https://www.portainer.io/ 安装 在 WSL / Docker Desktop 上使用 Docker 安装 Portainer CE 通过命令或UI页…...