Linux进程调度学习!

进程调度决定了哪个进程将被执行以及执行的时间,操作系统通过合理的进程调度实现资源的最大化利用。

在单片机上,常见的方式是系统初始化后进入 while(1){} 循环。当然,单片机也可以运行类似 FreeRTOS 的系统,从而实现进程切换。

在带有操作系统的 CPU 上运行的逻辑是允许多个进程(实际上是程序)“同时”运行。例如,你可以同时操作鼠标、播放音乐、进行文字编辑等。宏观上,这些任务看似并行执行,但实际上 CPU 是在不断调度每个进程,使每个进程都能得到响应,同时兼顾不同场景下的响应效率(进程的执行时间)。

进程调度器的任务是合理分配 CPU 时间给运行中的进程,创造一种所有进程并行运行的假象。这对调度器提出了要求:

调度器分配的 CPU 时间不能过长,否则会导致其他程序响应延迟,难以保证公平性。调度器分配的时间也不能过短,因为每次调度都会导致上下文切换,这种切换开销很大。调度器的任务具体包括:

立即进入“豆包AI人工智官网入口”;

立即学习“豆包AI人工智能在线问答入口”;

1、分配时间给进程;2、上下文切换。因此,调度器的任务可以明确表述为:在恰当的时刻,按照合理的调度算法,选择进程,让进程运行到它应有的时间,并切换两个进程的上下文。

I/O 消耗型和 CPU 消耗型:如果运行的进程大部分时间用于进行 I/O 请求或等待,这类进程被称为 I/O 消耗型,例如键盘操作。这种类型的进程通常处于可运行状态,但实际运行时间很短,大部分时间处于阻塞(睡眠)状态。

如果进程的大部分时间都在使用 CPU 进行运算,这类进程被称为 CPU 消耗型,例如使用 Matlab 进行大型运算。这类进程没有太多的 I/O 需求,从系统响应的角度来看,调度器不应频繁调度它们。对于 CPU 消耗型进程,调度策略通常是降低它们的执行频率,延长运行时间。

Linux 系统为了提升响应速度,倾向于优先调度 I/O 消耗型进程。

1、进程优先级调度算法中,基本的算法是根据进程的优先级来调度进程,例如 FreeRTOS 通过任务的优先级来进行进程抢占。

普通进程:在 Linux 中,普通进程的优先级通过称为 nice 值的参数来描述。nice 值的范围是 [-20, 19],默认值为 0;nice 值越低,代表优先级越高,反之,nice 值越高,优先级越低。优先级较高的普通进程在短时间内有更多的执行时间(注意,这里所说的“更多执行时间”是指在一小段观察时间内,每个可执行的进程都执行一遍的情况)。可以通过 ps -el 命令查看系统中进程的列表。实时进程:实时优先级是可配置的,默认范围是 0~99,与 nice 值相反,实时优先级数值越高,优先级越高。任何实时进程的优先级都高于普通进程的优先级。总结:实时进程优先级:数值越高,优先级越大;普通进程优先级:nice 值越高,优先级越低;任何实时进程的优先级高于普通进程。

Linux 调度算法:Linux 中有一个总的调度结构,称为调度器类(scheduler class),它允许不同的可动态添加的调度算法并存。总调度器根据调度器类的优先顺序依次调度调度器类中的进程。选择了调度器类后,再在这个调度器类中使用其调度算法(调度策略)进行内部调度。

Linux进程调度学习!

调度器的优先级顺序为:

Scheduling Class 的优先级顺序为 Stop_task > Real_Time > Fair > Idle_Task。开发者可以根据自己的设计需求,将所属的 Task 配置到不同的 Scheduling Class 中。其中,Real_time 和 Fair 是最常用的,下面主要讨论这两类。

1、Fair 调度使用的是 CFS 的调度算法,即完全公平调度器:对于一个普通进程,CFS 调度器调度它执行(SCHED_NORMAL),需要考虑两个方面:

如何选择哪一个进程进入运行状态?—— 在 CFS 中,每个进程被分配了一个虚拟时钟 vruntime(virtual runtime),这个变量并非直接等于其绝对运行时间,而是根据运行时间放大或缩小一个比例。CFS 使用这个 vruntime 来代表一个进程的运行时间。如果一个进程得以执行,那么它的 vruntime 将不断增大,直到它停止执行。未执行的进程的 vruntime 不变。调度器为了体现绝对的完全公平的调度原则,总是选择 vruntime 最小的进程,让其投入执行。它们被维护在一个以 vruntime 为顺序的红黑树 rbtree 中,每次从中取出 vruntime 最小的进程来投入运行。实际运行时间到 vruntime 的计算公式为:

vruntime = 实际运行时间 * 1024 / 进程权重

这里的 1024 代表 nice 值为 0 的进程权重。所有的进程都以 nice 为 0 的权重 1024 作为基准,计算自己的 vruntime。上面两个公式可以得出,尽管进程的权重不同,但它们的 vruntime 增长速度应该是相同的,与权重无关。由于所有进程的 vruntime 增长速度宏观上看应该是同时推进的,因此可以用 vruntime 来选择运行的进程。vruntime 值较小就说明它以前占用 CPU 的时间较短,受到了“不公平”对待,因此下一个运行的进程就是它。这样既能公平选择进程,又能保证高优先级进程获得较多的运行时间,这就是 CFS 的主要思想。

选择的进程进行运行后,它运行多久?进程运行的时间是根据进程的权重进行分配。

分配给进程的运行时间 = 调度周期 *(进程权重 / 所有进程权重之和)

CFS 调度器实体结构作为一个名为 se 的 sched_entity 结构,嵌入到进程描述符 struct task_struct 中。

struct sched_entity {    struct load_weight load;  /* for load-balancing */    struct rb_node  run_node;    struct list_head group_node;    unsigned int  on_rq;    u64   exec_start;    u64   sum_exec_runtime;    u64   vruntime;    u64   prev_sum_exec_runtime;    u64   nr_migrations;    #ifdef CONFIG_SCHEDSTATS    struct sched_statistics statistics;    #endif    #ifdef CONFIG_FAIR_GROUP_SCHED    struct sched_entity *parent;    /* rq on which this entity is (to be) queued: */    struct cfs_rq  *cfs_rq;    /* rq "owned" by this entity/group: */    struct cfs_rq  *my_q;    #endif};

2、实时调度策略:对于实时调度策略,分为两种:

SCHED_FIFO 和 SCHED_RR

这两种进程的优先级都比任何普通进程的优先级高(SCHED_NORMAL),都会比它们更先得到调度。

SCHED_FIFO:一个这种类型的进程处于可执行状态时,就会一直执行,直到它自己被阻塞或主动放弃 CPU;它不基于时间片,可以一直执行下去,只有更高优先级的 SCHED_FIFO 或 SCHED_RR 才能抢占它的任务。如果有两个同样优先级的 SCHED_FIFO 任务,它们会轮流执行,其他低优先级的只有等它们变为不可执行状态,才有机会执行。SCHED_RR:与 SCHED_FIFO 大致相同,只是 SCHED_RR 级的进程在耗尽其时间片后,不能再执行,需要接受 CPU 的调度。当 SCHED_RR 耗尽时间片后,同一优先级的其他实时进程被轮流调度。上述两种实时算法都是静态的优先级。内核不为实时优先级的进程计算动态优先级,保证给定优先级的实时进程总能够抢占比它优先级低的进程。

Linux 调度时机:1、进程切换:从进程的角度看,CPU 是共享资源,由所有进程按特定的策略轮番使用。一个进程离开 CPU、另一个进程占据 CPU 的过程,称为进程切换(process switch)。进程切换是在内核中通过调用 schedule() 完成的。

发生进程切换的场景有以下三种:

1、进程运行不下去了:比如因为要等待 I/O 完成,或者等待某个资源、某个事件,典型的内核代码如下:

// 把进程放进等待队列,把进程状态置为 TASK_UNINTERRUPTIBLEprepare_to_wait(waitq, wait, TASK_UNINTERRUPTIBLE);// 切换进程schedule();

2、进程还在运行,但内核不让它继续使用 CPU 了:比如进程的时间片用完了,或者优先级更高的进程来了,所以该进程必须把 CPU 的使用权交出来。3、进程还可以运行,但它自己的算法决定主动交出 CPU 给别的进程:用户程序可以通过系统调用 sched_yield() 来交出 CPU,内核则可以通过函数 cond_resched() 或 yield() 来做到。进程切换分为自愿切换(Voluntary)和强制切换(Involuntary),以上场景 1 属于自愿切换,场景 2 和 3 属于强制切换。自愿切换发生时,进程不再处于运行状态,比如由于等待 I/O 而阻塞(TASK_UNINTERRUPTIBLE),或者因等待资源和特定事件而休眠(TASK_INTERRUPTIBLE),又或者被 debug/trace 设置为 TASK_STOPPED/TASK_TRACED 状态;强制切换发生时,进程仍然处于运行状态(TASK_RUNNING),通常是由于被优先级更高的进程抢占(preempt),或者进程的时间片用完了。注意:进程可以通过调用 sched_yield() 主动交出 CPU,这不是自愿切换,而是属于强制切换,因为进程仍然处于运行状态。有时候内核代码会在耗时较长的循环体内通过调用 cond_resched() 或 yield(),主动让出 CPU,以免 CPU 被内核代码占据太久,给其它进程运行机会。这也属于强制切换,因为进程仍然处于运行状态。

进程自愿切换(Voluntary)和强制切换(Involuntary)的次数被统计在 /proc//status 中,其中 voluntary_ctxt_switches 表示自愿切换的次数,nonvoluntary_ctxt_switches 表示强制切换的次数,两者都是自进程启动以来的累计值。

也可以用 pidstat -w 命令查看进程切换的每秒统计值:

pidstat -w 1Linux 3.10.0-229.14.1.el7.x86_64 (bj71s060)     02/01/2018      _x86_64_       (2 CPU)12:05:20 PM   UID       PID   cswch/s nvcswch/s  Command12:05:21 PM     0      1299      0.94      0.00  httpd12:05:21 PM     0     27687      0.94      0.00  pidstat

自愿切换和强制切换的统计值在实践中有什么意义呢?大致而言,如果一个进程的自愿切换占多数,意味着它对 CPU 资源的需求不高。如果一个进程的强制切换占多数,意味着对它来说 CPU 资源可能是个瓶颈,这里需要排除进程频繁调用 sched_yield() 导致强制切换的情况。

2、调度时机:自愿切换意味着进程需要等待某种资源,强制切换则与抢占(Preemption)有关。

抢占(Preemption)是指内核强行切换正在 CPU 上运行的进程,在抢占的过程中并不需要得到进程的配合,在随后的某个时刻被抢占的进程还可以恢复运行。发生抢占的原因主要有:进程的时间片用完了,或者优先级更高的进程来争夺 CPU 了。

抢占的过程分两步,第一步触发抢占,第二步执行抢占,这两步中间不一定是连续的,有些特殊情况下甚至会间隔相当长的时间:

触发抢占:给正在 CPU 上运行的当前进程设置一个请求重新调度的标志(TIF_NEED_RESCHED),仅此而已,此时进程并没有切换。执行抢占:在随后的某个时刻,内核会检查 TIF_NEED_RESCHED 标志并调用 schedule() 执行抢占。抢占只在某些特定的时机发生,这是内核的代码决定的。

3、触发抢占的时机:每个进程都包含一个 TIF_NEED_RESCHED 标志,内核根据这个标志判断该进程是否应该被抢占,设置 TIF_NEED_RESCHED 标志就意味着触发抢占。

直接设置 TIF_NEED_RESCHED 标志的函数是 set_tsk_need_resched();触发抢占的函数是 resched_task()。

TIF_NEED_RESCHED 标志什么时候被设置呢?在以下时刻:

周期性的时钟中断:时钟中断处理函数会调用 scheduler_tick(),这是调度器核心层(scheduler core)的函数,它通过调度类(scheduling class)的 task_tick 方法检查进程的时间片是否耗尽,如果耗尽则触发抢占:

/*  * This function gets called by the timer code, with HZ frequency.  * We call it with interrupts disabled.  */void scheduler_tick(void) {    ...    curr->sched_class->task_tick(rq, curr, 0);    ...}

唤醒进程的时候:当进程被唤醒的时候,如果优先级高于 CPU 上的当前进程,就会触发抢占。相应的内核代码中,try_to_wake_up() 最终通过 check_preempt_curr() 检查是否触发抢占。新进程创建的时候:如果新进程的优先级高于 CPU 上的当前进程,会触发抢占。相应的调度器核心层代码是 sched_fork(),它再通过调度类的 task_fork 方法触发抢占:

int sched_fork(unsigned long clone_flags, struct task_struct *p) {    ...    if (p->sched_class->task_fork)        p->sched_class->task_fork(p);    ...}

进程修改 nice 值的时候:如果进程修改 nice 值导致优先级高于 CPU 上的当前进程,也会触发抢占。内核代码参见 set_user_nice()。进行负载均衡的时候:在多 CPU 的系统上,进程调度器尽量使各个 CPU 之间的负载保持均衡,而负载均衡操作可能会需要触发抢占。不同的调度类有不同的负载均衡算法,涉及的核心代码也不一样,比如 CFS 类在 load_balance() 中触发抢占:

load_balance() {    ...    move_tasks();    ...    resched_cpu();    ...}

RT 类的负载均衡基于 overload,如果当前运行队列中的 RT 进程超过一个,就调用 push_rt_task() 把进程推给别的 CPU,在这里会触发抢占。

4、执行抢占的时机:触发抢占通过设置进程的 TIF_NEED_RESCHED 标志告诉调度器需要进行抢占操作了,但是真正执行抢占还要等内核代码发现这个标志才行,而内核代码只在设定的几个点上检查 TIF_NEED_RESCHED 标志,这也就是执行抢占的时机。

抢占如果发生在进程处于用户态的时候,称为 User Preemption(用户态抢占);如果发生在进程处于内核态的时候,则称为 Kernel Preemption(内核态抢占)。

执行 User Preemption(用户态抢占)的时机:

从系统调用(syscall)返回用户态时;

源文件:arch/x86/kernel/entry_64.Ssysret_careful:        bt $TIF_NEED_RESCHED,%edx        jnc sysret_signal        TRACE_IRQS_ON        ENABLE_INTERRUPTS(CLBR_NONE)        pushq_cfi %rdi        call schedule        popq_cfi %rdi        jmp sysret_check

从中断返回用户态时:

retint_careful:        CFI_RESTORE_STATE        bt    $TIF_NEED_RESCHED,%edx        jnc   retint_signal        TRACE_IRQS_ON        ENABLE_INTERRUPTS(CLBR_NONE)        pushq_cfi %rdi        call  schedule        popq_cfi %rdi        GET_THREAD_INFO(%rcx)        DISABLE_INTERRUPTS(CLBR_NONE)        TRACE_IRQS_OFF        jmp retint_check

执行 Kernel Preemption(内核态抢占)的时机:Linux 在 2.6 版本之后就支持内核抢占了,但是请注意,具体取决于内核编译时的选项:CONFIG_PREEMPT_NONE=y 不允许内核抢占。这是 SLES 的默认选项。CONFIG_PREEMPT_VOLUNTARY=y 在一些耗时较长的内核代码中主动调用 cond_resched() 让出 CPU。这是 RHEL 的默认选项。CONFIG_PREEMPT=y 允许完全内核抢占。在 CONFIG_PREEMPT=y 的前提下,内核态抢占的时机是:

1、中断处理程序返回内核空间之前会检查 TIF_NEED_RESCHED 标志,如果置位则调用 preempt_schedule_irq() 执行抢占。preempt_schedule_irq() 是对 schedule() 的包装。

#ifdef CONFIG_PREEMPT    /* Returning to kernel space. Check if we need preemption */    /* rcx:  threadinfo. interrupts off. */ENTRY(retint_kernel)    cmpl $0,TI_preempt_count(%rcx)    jnz  retint_restore_args    bt  $TIF_NEED_RESCHED,TI_flags(%rcx)    jnc  retint_restore_args    bt   $9,EFLAGS-ARGOFFSET(%rsp)  /* interrupts off? */    jnc  retint_restore_args    call preempt_schedule_irq    jmp exit_intr#endif

2、当内核从 non-preemptible(禁止抢占)状态变成 preemptible(允许抢占)的时候;在 preempt_enable() 中,会最终调用 preempt_schedule 来执行抢占。preempt_schedule() 是对 schedule() 的包装。

文章参考:https://www.php.cn/link/a522566787b294b2e70ea3d6b8a393d6

以上就是Linux进程调度学习!的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/25813.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
电影评论区怎么加视频号?如何在手机上快速找到电影评论区的视频号?
上一篇 2025年11月2日 14:37:18
移动端显卡性能释放对比:满血版RTX 4080 Laptop vs. 残血版
下一篇 2025年11月2日 14:39:21

相关推荐

  • Golang JSON序列化:控制敏感字段暴露的最佳实践

    本教程探讨golang中如何高效控制结构体字段在json序列化时的可见性。当需要将包含敏感信息的结构体数组转换为json响应时,通过利用`encoding/json`包提供的结构体标签,特别是`json:”-“`,可以轻松实现对特定字段的忽略,从而避免敏感数据泄露,确保api…

    2026年5月10日
    000
  • 比特币新手教程 比特币交易平台有哪些

    比特币是一种去中心化的数字货币,基于区块链技术实现点对点交易,具有匿名性、有限发行和不可篡改等特点;新手可通过交易所购买,P2P交易获得比特币,常用平台包括Binance、OKX和Huobi;交易流程包括注册账户、实名认证、绑定支付方式、充值法币并下单购买,可选择市价单或限价单;比特币存储方式有交易…

    2026年5月10日
    000
  • c++中的SFINAE技术是什么_c++模板编程中的SFINAE原理与应用

    SFINAE 是“替换失败不是错误”的原则,指模板实例化时若参数替换导致错误,只要存在其他合法候选,编译器不报错而是继续重载决议。它用于条件启用模板、类型检测等场景,如通过 decltype 或 enable_if 控制函数重载,实现类型特征判断。尽管 C++20 引入 Concepts 简化了部分…

    2026年5月10日
    000
  • Go语言mgo查询构建:深入理解bson.M与日期范围查询的正确实践

    本文旨在解决go语言mgo库中构建复杂查询时,特别是涉及嵌套`bson.m`和日期范围筛选的常见错误。我们将深入剖析`bson.m`的类型特性,解释为何直接索引`interface{}`会导致“invalid operation”错误,并提供一种推荐的、结构清晰的代码重构方案,以确保查询条件能够正确…

    2026年5月10日
    100
  • 修复点击时按钮抖动:CSS垂直对齐实践

    本文探讨了在Web开发中,交互式按钮(如播放/暂停按钮)在点击时发生意外垂直位移的问题。通过分析CSS样式变化对元素布局的影响,我们发现这是由于按钮不同状态下的边框样式和内边距改变,以及默认的垂直对齐行为共同作用所致。核心解决方案是利用CSS的vertical-align属性,将其设置为middle…

    2026年5月10日
    100
  • Golang goroutine与channel调试技巧

    使用go run -race检测数据竞争,结合runtime.NumGoroutine监控协程数量,通过pprof分析阻塞调用栈,利用select超时避免永久阻塞,有效排查goroutine泄漏、死锁和数据竞争问题。 Go语言的goroutine和channel是并发编程的核心,但它们也带来了调试上…

    2026年5月10日
    000
  • 使用 Jupyter Notebook 进行探索性数据分析

    Jupyter Notebook通过单元格实现代码与Markdown结合,支持数据导入(pandas)、清洗(fillna)、探索(matplotlib/seaborn可视化)、统计分析(describe/corr)和特征工程,便于记录与分享分析过程。 Jupyter Notebook 是进行探索性…

    2026年5月10日
    000
  • 《魔兽世界》将于6月11日开启国服回归技术测试

    《魔兽世界》将于6月11日开启国服回归技术测试《魔兽世界》将于6月11日开启国服回归技术测试《魔兽世界》将于6月11日开启国服回归技术测试《魔兽世界》将于6月11日开启国服回归技术测试

    《%ign%ignore_a_1%re_a_1%》官方宣布,将于6月11日开启国服回归技术测试,时间为7天,并称可以在6月内正式开服,玩家们可以访问官网下载战网客户端并预下载“巫妖王之怒”客户端,技术测试详情见下图。 WordAi WordAI是一个AI驱动的内容重写平台 53 查看详情 以上就是《…

    2026年5月10日 用户投稿
    200
  • 如何在HTML中插入表单元素_HTML表单控件与输入类型使用指南

    HTML表单通过标签构建,包含action和method属性定义数据提交目标与方式,常用input类型如text、password、email等适配不同输入需求,配合label、required、placeholder提升可用性,结合textarea、select、button等控件实现完整交互,是…

    2026年5月10日
    100
  • 创建指定大小并填充特定数据的Golang文件教程

    本文将介绍如何使用Golang创建一个指定大小的文件,并用特定数据填充它。我们将使用 `os` 包提供的函数来创建和截断文件,从而实现快速生成大文件的目的。示例代码展示了如何创建一个10MB的文件,并将其填充为全零数据。掌握这些方法,可以方便地在例如日志系统或磁盘队列等场景中,预先创建测试文件或初始…

    2026年5月10日
    000
  • Python命令怎样使用profile分析脚本性能 Python命令性能分析的基础教程

    使用Python的cProfile模块分析脚本性能最直接的方式是通过命令行执行python -m cProfile your_script.py,它会输出每个函数的调用次数、总耗时、累积耗时等关键指标,帮助定位性能瓶颈;为进一步分析,可将结果保存为文件python -m cProfile -o ou…

    2026年5月10日
    000
  • 如何插入查询结果数据_SQL插入Select查询结果方法

    如何插入查询结果数据_SQL插入Select查询结果方法如何插入查询结果数据_SQL插入Select查询结果方法如何插入查询结果数据_SQL插入Select查询结果方法如何插入查询结果数据_SQL插入Select查询结果方法

    使用INSERT INTO…SELECT语句可高效插入数据,通过NOT EXISTS、LEFT JOIN、MERGE语句或唯一约束避免重复;表结构不一致时可通过别名、类型转换、默认值或计算字段处理;结合存储过程可提升可维护性,支持参数化与动态SQL。 将查询结果数据插入到另一个表中,可以…

    2026年5月10日 用户投稿
    000
  • 使用 WebCodecs VideoDecoder 实现精确逐帧回退

    本文档旨在解决在使用 WebCodecs VideoDecoder 进行视频解码时,实现精确逐帧回退的问题。通过比较帧的时间戳与目标帧的时间戳,可以避免渲染中间帧,从而提高用户体验。本文将提供详细的解决方案和示例代码,帮助开发者实现精确的视频帧控制。 在使用 WebCodecs VideoDecod…

    2026年5月10日
    000
  • Discord.py 交互按钮超时与持久化解决方案

    本教程旨在解决Discord.py中交互按钮在一段时间后出现“This Interaction Failed”错误的问题。我们将深入探讨视图(View)的超时机制,并提供通过正确设置timeout参数以及利用bot.add_view()方法实现按钮持久化的具体方案,确保您的机器人交互功能稳定可靠,即…

    2026年5月10日
    000
  • Debian Copilot的社区活跃度如何

    debian copilot是codeberg社区维护的ai助手,旨在为debian用户提供服务。尽管搜索结果中没有直接提供关于debian copilot社区支持活跃度的具体数据,但我们可以通过debian社区的整体活跃度和特点来推断其活跃性。 Debian社区的一般情况: Debian拥有详尽的…

    2026年5月10日
    000
  • JavaScript 动态菜单点击高亮效果实现教程

    本教程详细介绍了如何使用 JavaScript 实现动态菜单的点击高亮功能。通过事件委托和状态管理,当用户点击菜单项时,被点击项会高亮显示(绿色),同时其他菜单项恢复默认样式(白色)。这种方法避免了不必要的DOM操作,提高了性能和代码可维护性,确保了无论点击方向如何,功能都能稳定运行。 动态菜单高亮…

    2026年5月10日
    200
  • c++如何实现UDP通信_c++基于UDP的网络通信示例

    UDP通信基于套接字实现,适用于实时性要求高的场景。1. 流程包括创建套接字、绑定地址(接收方)、发送(sendto)与接收(recvfrom)数据、关闭套接字;2. 服务端监听指定端口,接收客户端消息并回传;3. 客户端发送消息至服务端并接收响应;4. 跨平台需处理Winsock初始化与库链接,编…

    2026年5月10日
    100
  • 谷歌浏览器如何截图 谷歌浏览器页面截图技巧

    谷歌浏览器如何截图 谷歌浏览器页面截图技巧谷歌浏览器如何截图 谷歌浏览器页面截图技巧谷歌浏览器如何截图 谷歌浏览器页面截图技巧谷歌浏览器如何截图 谷歌浏览器页面截图技巧

    使用谷歌浏览器的开发者工具截图步骤:1. 按ctrl+shift+i(windows/linux)或cmd+option+i(mac)打开开发者工具。2. 点击右上角三个点,选择”更多工具”,再选择”截图”。3. 选择截取整个页面。推荐的谷歌浏览器扩展…

    2026年5月10日 用户投稿
    100
  • JavaScript函数中插入加载动画(Spinner)的正确方法

    本文旨在解决在JavaScript函数中插入加载动画(Spinner)时遇到的异步问题。通过引入async/await和Promise.all,确保在数据处理完成前后正确显示和隐藏加载动画,提升用户体验。我们将提供两种实现方案,并详细解释其原理和优势。 在Web开发中,当执行耗时操作时,显示加载动画…

    2026年5月10日
    100
  • Golang空接口如何应用在项目中

    空接口可用于接收任意类型值,常见于日志函数、通用数据结构、JSON动态解析及配置驱动逻辑,提升代码灵活性,但需配合类型断言确保安全,避免滥用以降低维护成本。 空接口 interface{} 在 Go 语言中是一个非常灵活的类型,它可以存储任何类型的值。虽然它牺牲了一部分类型安全,但在实际项目中合理使…

    2026年5月10日
    100

发表回复

登录后才能评论
关注微信