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

Linux 竞争与并发(学习总结)

    在Linux驱动开发中,“并发”和“竞争”是两个重要的概念,它们涉及到多任务环境下资源的管理和使用。

并发 (Concurrency)

        并发指的是在同一时间段内,多个任务看似同时运行的现象。实际上,在单核处理器上,这通常是通过快速的任务切换来实现的,而在多核或多处理器环境中,则可以真正实现并行执行。并发可以提高系统的响应能力和资源利用率。在Linux系统中,并发不仅限于用户空间的应用程序,同样也存在于内核空间,特别是驱动程序中。驱动程序可能需要处理来自不同进程的请求,甚至是中断处理程序的并发执行。

竞争 (Contention)

        竞争指的是多个任务或进程试图同时访问或修改同一个共享资源时所发生的情况。如果没有适当的同步机制来协调这些访问,则可能导致竞态条件(race condition),即最终结果依赖于相对执行顺序的不确定性。例如,如果两个线程同时读取和修改同一个变量,那么根据它们执行的相对顺序,可能会导致数据不一致或其他未定义的行为。

处理方法        

处理竞争与并发的方法多种多样,这些方法旨在确保多个执行单元(如进程、线程或中断处理程序)在访问共享资源时的有序性和一致性。以下是一些主要的方法及其使用例子:

1. 原子操作(atomic operation)

定义:原子操作是指在执行过程中不会被其他线程或中断打断的操作。

使用例子:在Linux内核中,原子操作常用于对全局变量的增减操作,如计数器。假设有一个全局整型变量count,多个线程可能同时对其进行增减操作。通过使用原子操作API,如atomic_inc(&count)atomic_dec(&count),可以确保每次增减操作都是原子的,从而避免竞争条件。

以下是一些常见的原子整形操作API函数

  1. 定义原子变量初始化ATOMIC_INIT
    • 作用:定义并初始化一个原子变量,为其赋予一个特定的初始值。
    • 原型:ATOMIC_INIT(int i);
    • 参数:通常是一个整型值。
  2. 原子设置(atomic_set)
    • 作用:设置原子变量的值。
    • 原型:void atomic_set(atomic_t *v, int i);
    • 参数:v是指向原子变量的指针,i是要设置的值。
  3. 原子读取(atomic_read)
    • 作用:读取原子变量的值。
    • 原型:int atomic_read(const atomic_t *v);
    • 参数:v是指向原子变量的指针。
    • 返回值:返回原子变量的当前值。
  4. 原子增加(atomic_add)
    • 作用:给原子变量增加一个值。
    • 原型:void atomic_add(int i, atomic_t *v);
    • 参数:i是要增加的值,v是指向原子变量的指针。
  5. 原子减少(atomic_sub)
    • 作用:从原子变量中减少一个值。
    • 原型:void atomic_sub(int i, atomic_t *v);
    • 参数:i是要减少的值,v是指向原子变量的指针。
  6. 原子增加并返回(atomic_add_return / atomic_inc_return)
    • 作用:给原子变量增加一个值,并返回增加后的值。
    • 原型:int atomic_add_return(int i, atomic_t *v);
    • 或:int atomic_inc_return(atomic_t *v);(增加1并返回)
    • 参数:i是要增加的值(对于atomic_inc_return,此参数为隐式的1),v是指向原子变量的指针。
    • 返回值:返回增加后的值。
  7. 原子减少并返回(atomic_sub_return / atomic_dec_return)
    • 作用:从原子变量中减少一个值,并返回减少后的值。
    • 原型:int atomic_sub_return(int i, atomic_t *v);
    • 或:int atomic_dec_return(atomic_t *v);(减少1并返回)
    • 参数:i是要减少的值(对于atomic_dec_return,此参数为隐式的1),v是指向原子变量的指针。
    • 返回值:返回减少后的值。
  8. 原子增加如果非负(atomic_add_unless)
    • 作用:如果原子变量的当前值是非负的,则给它增加一个值。
    • 原型:int atomic_add_unless(atomic_t *v, int a, int u);
    • 参数:v是指向原子变量的指针,a是要增加的值,u是除非当前值等于此值时才进行操作的值(通常用于避免负值)。
    • 返回值:如果操作成功,则返回1;否则返回0。

以下是一些常见的原子操作API函数:

  1. 原子位设置(atomic_set_bit)
    • 作用:设置指定位置上的位。
    • 原型:void atomic_set_bit(int nr, void *addr);(在某些内核版本中,可能是void set_bit(int nr, volatile unsigned long *addr);
    • 参数:nr是要设置的位的位置(从0开始计数),addr是指向包含该位的内存地址的指针。
  2. 原子位清除(atomic_clear_bit)
    • 作用:清除指定位置上的位。
    • 原型:void atomic_clear_bit(int nr, void *addr);(在某些内核版本中,可能是void clear_bit(int nr, volatile unsigned long *addr);
    • 参数:与atomic_set_bit相同。
  3. 原子位翻转(atomic_flip_bit)
    • 作用:翻转(即取反)指定位置上的位。
    • 原型:int atomic_flip_bit(int nr, void *addr);(返回翻转前的位值;在某些内核版本中,可能没有直接的翻转函数,但可以通过其他方式实现)
    • 参数:与atomic_set_bit相同。
    • 返回值:返回翻转操作前该位的值。
  4. 原子位测试并设置(atomic_test_and_set_bit)
    • 作用:测试指定位置上的位,如果位为0则设置为1,并返回测试结果。
    • 原型:int atomic_test_and_set_bit(int nr, void *addr);(在某些内核版本中,可能是int test_and_set_bit(int nr, volatile unsigned long *addr);
    • 参数:与atomic_set_bit相同。
    • 返回值:如果位原来是0,则返回0,并且位被设置为1;如果位原来是1,则返回非0值。
  5. 原子位测试并清除(atomic_test_and_clear_bit)
    • 作用:测试指定位置上的位,如果位为1则清除为0,并返回测试结果。
    • 原型:int atomic_test_and_clear_bit(int nr, void *addr);(在某些内核版本中,可能是int test_and_clear_bit(int nr, volatile unsigned long *addr);
    • 参数:与atomic_set_bit相同。
    • 返回值:如果位原来是1,则返回非0值,并且位被清除为0;如果位原来是0,则返回0。
  6. 原子位测试(atomic_test_bit)
    • 作用:测试指定位置上的位是否为1。
    • 原型:int atomic_test_bit(int nr, void *addr);(在某些内核版本中,可能是通过test_bit宏或其他方式实现)
    • 参数:与atomic_set_bit相同。
    • 返回值:如果位是1,则返回非0值;如果位是0,则返回0

2. 自旋锁(Spinlock)

定义:自旋锁是一种轻量级的锁,用于保护临界区资源。当一个线程获得自旋锁后,其他线程将处于忙等待状态,直到锁被释放。

使用例子:在设备驱动中,当多个线程需要访问同一个硬件设备时,可以使用自旋锁来保护对硬件寄存器的访问。例如,在访问网络设备的发送队列时,可以先加自旋锁,访问完成后释放自旋锁。这样可以确保在任一时刻只有一个线程能够访问发送队列,从而避免竞争条件。

常见的自旋锁API函数的详细介绍:

1. 初始化自旋锁

spin_lock_init(spinlock_t *lock)

  • 作用:动态初始化一个自旋锁,将其设置为未锁状态。
  • 参数:指向spinlock_t类型变量的指针,该变量表示要初始化的自旋锁。
  • 注意事项:通常在动态分配的自旋锁或需要在运行时初始化的场景中使用。

DEFINE_SPINLOCK(name)

  • 作用:静态初始化一个自旋锁,并为其指定一个名字。
  • 参数:自旋锁的名字,该名字将用作变量名。
  • 注意事项:这通常用于全局或静态自旋锁的初始化,它在编译时分配并初始化自旋锁。

2. 加锁操作

spin_lock(spinlock_t *lock)

  • 作用:尝试获取自旋锁。如果锁当前未被持有,则立即获取锁;如果锁已被持有,则调用线程将忙等待直到锁被释放。
  • 参数:指向要获取的自旋锁的指针。
  • 注意事项:在持有锁期间,应避免调用可能导致线程休眠的函数,以免引发死锁。

spin_trylock(spinlock_t *lock)

  • 作用:尝试获取自旋锁,但不会忙等待。
  • 参数:指向要尝试获取的自旋锁的指针。
  • 返回值:如果成功获取锁,则返回非零值(真);如果锁已被持有,则返回零值(假)。
  • 注意事项:这个函数适用于那些不希望无限等待锁的场景。

3. 解锁操作

spin_unlock(spinlock_t *lock)

  • 作用:释放自旋锁。
  • 参数:指向要释放的自旋锁的指针。
  • 注意事项:必须与spin_lockspin_trylock配对使用。

4. 其他辅助函数

spin_is_locked(spinlock_t *lock)

  • 作用:检查自旋锁是否已被持有。
  • 参数:指向要检查的自旋锁的指针。
  • 返回值:如果锁被持有,则返回非零值(真);否则返回零值(假)。
  • 注意事项:这个函数通常用于调试或状态检查。

使用自旋锁的注意事项

  1. 锁持有时间:自旋锁的持有时间应尽可能短,以避免CPU资源的浪费和性能下降。
  2. 避免死锁:在自旋锁保护的临界区内,不应调用任何可能导致线程休眠的API函数,如mallocprintk(在某些情况下)、copy_from_user等。
  3. 中断和抢占
    • 在中断服务例程中使用自旋锁时,应注意中断的嵌套和优先级,以避免中断上下文中的死锁。
    • 在支持抢占的系统中,自旋锁会自动禁止内核抢占,但在单CPU系统中,应确保在持有锁期间不会触发抢占。
  4. 递归申请:应避免递归申请同一个自旋锁,因为这会导致死锁。
  5. 锁粒度:应合理设置锁的粒度,以平衡并发性和性能。过粗的锁粒度会导致不必要的等待和性能下降,而过细的锁粒度则可能增加锁管理的开销和复杂性。

3. 信号量(Semaphore)

定义:信号量是一种更通用的同步机制,它允许多个线程同时访问共享资源,但可以限制同时访问资源的数量。

使用例子:在数据库连接池中,可以使用信号量来限制同时访问数据库连接的线程数量。假设数据库连接池最大允许10个连接,则可以初始化一个信号量,其初始值为10。每当一个线程需要获取数据库连接时,它会尝试从信号量获取一个许可(通过sem_wait())。如果信号量的值大于0,则线程可以成功获取连接,并将信号量的值减1;如果信号量的值为0,则线程将被阻塞,直到有其他线程释放连接(通过sem_post())并增加信号量的值。

信号量API函数:

信号量的使用注意事项

  1. 适用场景
    • 信号量适用于那些占用资源比较久的场合,因为它可以使等待资源的线程进入休眠状态,从而避免忙等待带来的CPU资源浪费。
  2. 中断环境限制
    • 信号量不能用于中断中,因为中断处理例程不能休眠,而信号量的等待操作可能会导致休眠。
  3. 性能考虑
    • 如果共享资源的持有时间比较短,则不适合使用信号量,因为频繁的休眠和线程切换带来的开销会远大于信号量带来的同步优势。在这种情况下,应考虑使用自旋锁等轻量级的同步机制。
  4. 资源管理
    • 在使用信号量进行同步时,应确保在不再需要信号量时销毁它,以避免资源泄露。对于有名信号量,还需要注意信号量集的创建和删除操作,以确保资源得到正确管理。
  5. 原子性操作
    • 在多处理器系统中,信号量的操作通常是原子的,但访问共享资源的其他操作可能不是原子的。因此,在使用信号量进行同步时,还需要考虑其他可能的同步问题,并确保对共享资源的访问是安全的。

4. 互斥锁(Mutex)

定义:互斥锁是一种用于保护临界区资源的同步机制,确保在任何时候只有一个线程能够访问被保护的资源。

使用例子:在多线程程序中,当多个线程需要访问同一个全局变量时,可以使用互斥锁来防止竞争条件。例如,在一个银行转账系统中,当两个线程分别尝试从同一个账户中转账时,可以使用互斥锁来保护对该账户余额的访问。在访问账户余额之前,线程会先尝试获取互斥锁;如果获取成功,则进入临界区进行转账操作;操作完成后释放互斥锁。这样可以确保在任一时刻只有一个线程能够修改账户余额。

API函数:

互斥锁(mutex)的注意事项:

  1. 中断环境限制
    • 互斥锁可以导致休眠,因此它不能在中断处理例程中使用。中断处理例程需要快速执行完毕,并且不能进入休眠状态。在中断中应使用自旋锁等不会引起休眠的同步机制。
  2. 临界区调用限制
    • 和信号量类似,互斥锁保护的临界区内可以调用可能引起阻塞的API函数。这是因为互斥锁在等待资源可用时会将线程置于休眠状态,直到资源被释放并唤醒该线程。
  3. 互斥锁的持有与释放
    • 一次只有一个线程可以持有互斥锁,这保证了临界区内的资源访问是独占的。因此,必须由持有互斥锁的线程来释放它。如果其他线程尝试释放未持有的互斥锁,将导致未定义行为。
    • 互斥锁不能递归上锁和解锁。递归上锁意味着同一个线程尝试多次获取同一个互斥锁,这将导致死锁。同样地,如果一个线程没有持有互斥锁却尝试解锁它,也会导致错误。
  4. 避免死锁
    • 在设计多线程程序时,应确保互斥锁的使用不会导致死锁。死锁是一种情况,其中多个线程相互等待对方释放资源,从而无法继续执行。
  5. 性能考虑
    • 虽然互斥锁提供了强大的同步能力,但它们也可能引入一定的性能开销。特别是在高争用情况下,互斥锁可能导致频繁的上下文切换和线程休眠。因此,在性能敏感的场景中,应谨慎使用互斥锁,并考虑使用其他轻量级的同步机制(如自旋锁、读写锁等)。
  6. 资源泄露避免
    • 和信号量一样,在使用互斥锁时也应确保在不再需要时正确释放它,以避免资源泄露。资源泄露可能导致系统资源耗尽,从而影响系统的稳定性和性能。

相关文章:

Linux 竞争与并发(学习总结)

在Linux驱动开发中,“并发”和“竞争”是两个重要的概念,它们涉及到多任务环境下资源的管理和使用。 并发 (Concurrency) 并发指的是在同一时间段内,多个任务看似同时运行的现象。实际上,在单核处理器上,这通常是通过…...

SaaS初创企业需求建模指南

所以你已经准备好进入市场,你有宏大的目标,并且充满激情。 但等等。 你要如何 实现 这些目标呢? 你设置了 正确的 目标吗? 而且你的目标是 可实现的吗? 那么,如何回答这些问题呢? 进入需求…...

MySQL最左匹配原则

MySQL索引的加左原则,也被称为最左匹配原则(Leftmost Prefix Rule)或最左前缀规则(Leftmost Prefixes),是指在创建复合索引时,应将经常用于查询的列放在索引的最左边,以便MySQL能够更…...

日常开发1:居中处理

开发的时候总会遇到两个空间上下两层,然后居中排放,如果只是知道下方或者上方控件的具体位置点,但是不知道另外一个控件的集体点位,应该怎么处理呢? 如上图所示,知道imageview 下方中间的点的位置(这里暂时定义image的宽高已知),上方是textview,那么如何布局呢? 简单解决方法…...

css弹性盒子——flex布局

目录 ​编辑 一、flex容器的样式属性(父元素属性) display:flex 弹性盒子,实现水平排列,在父盒子设置,适用于单行/单列 justify-content 二、flex元素的样式属性(子元素属性) 1.flex-grow 2.flex-shrink 3.flex-basis 4.flex组合属性 flex:flex-…...

亚马逊云科技 Gen BI 2024-09-04 上海站QuickSight

机缘 我又来了,感觉不上班比上班还要忙 天天像特种工一天,今天有度过的充实的一天,上午去图书馆,下午去了 亚马逊云科技 Gen BI 技术体验日 。 具体照片可以去 这里看 哈哈,这个就是我了 商业智能的趋势 根据艾瑞咨…...

【Qt】Qt和JavaScript使用QWebChannel交互

问题 问题一: 问题描述:运行时,Qt向Js端发送消息没有问题,Js端向Qt端发送消息时失败 报错:Cannot invoke unknown method of index -1 on object webTransport(0x…) 原因及解决办法:使用Qt 5.11.2编译生…...

码住!15个爆好用知识库软件工具分享

市场趋势:全球知识库管理软件的市场规模发展速度非常快,并且未来几年内仍将继续保持增长。据Verified Market Research预测,2028年知识库管理软件的全球市场份额将增长到588.1亿美元,复合年增长率达12.67%。 知识库软件可以帮助企…...

MybatisPlus中@EnumValue注解介绍、应用场景和示例代码

EnumValue注解详细介绍 功能概述: EnumValue注解标记在枚举类型的字段上,表示该字段是枚举值在数据库中存储的实际值。这对于枚举的持久化是关键,确保枚举在数据库中的表示与Java枚举类的一致性。 主要用途: 字段指定:…...

【计算机网络】描述TCP建立连接与断开的过程

一、TCP连接的建立与断开 1、建立连接——三次握手 1、A的TCP向B发出连接请求报文段 其首部中的同步位SYN 1,并选择序号seq x,表明传送数据时的第一个数据字节的序号是 x 2、B的TCP收到连接请求报文段后,如同意,则发回确认。 B …...

CSS学习14[重点]

定位 前言一、定位二、定位模式1. 静态定位 static2. 相对定位 relative3. 绝对定位 absolute4. 子绝父相5. 绝对定位的盒子水平居中 6. 固定定位(fixed)7. 叠放次序(z)三、四种定位总结四、定位模式转换 前言 为什么学习定位&am…...

力扣 | 递归 | 区间上的动态规划 | 486. 预测赢家

文章目录 一、递归二、区间动态规划 LeetCode&#xff1a;486. 预测赢家 一、递归 注意到本题数据范围为 1 < n < 20 1<n<20 1<n<20&#xff0c;因此可以使用递归枚举选择方式&#xff0c;时间复杂度为 2 20 1024 ∗ 1024 1048576 1.05 1 0 6 2^{20…...

黑白格

题目描述 小杨有一个 n 行 m 列的网格图&#xff0c;其中每个格子要么是白色&#xff0c;要么是黑色。 小杨想知道至少包含 k 个黑色格子的最小子矩形包含了多少个格子。 输入格式 第一行包含三个正整数 n,m,k&#xff0c;含义如题面所示。 之后 n 行&#xff0c;每行⼀个…...

数据链路层(MAC地址)

文章目录 数据链路层&#xff08;MAC地址&#xff09;1、以太网2、以太网帧格式3、MAC地址4、对比理解 MAC 地址和 IP 地址5、最大传输单元&#xff08;MTU&#xff09;6、MTU 对 IP 协议的影响7、MTU 对 UDP 协议的影响8、MTU 对 TCP 协议的影响9、查看硬件地址和 MTU10、ARP …...

【ruby java】登陆功能/邮件发送模版240903

Rails 风格登录系统添加全面而详细的注释&#xff0c;解释每个部分的功能和用途。​​​​​​​​​ 详细注释&#xff0c;解释了每个文件和代码块的功能。以下是一些关键点的总结&#xff1a; 1. 控制器&#xff08;Controllers&#xff09;: - ApplicationController: …...

告别格式不兼容烦恼!ape转换mp3,分享3个简单方法

各位读者们&#xff0c;你们是否有过这种体验&#xff1a;满怀期待地在网上下载一首好听的歌曲&#xff0c;结果怎么点击手机都播放不了&#xff0c;定睛一看&#xff0c;弹窗显示“无法播放该音频文件”。这是为什么呢&#xff1f;原来那首歌的音频格式是ape&#xff0c;不被手…...

Java核心知识体系-并发与多线程:线程基础

1 先导 Java线程基础主要包含如下知识点&#xff0c;相信我们再面试的过程中&#xff0c;经常会遇到类似的提问。 1、线程有哪几种状态? 线程之间如何转变&#xff1f; 2、线程有哪几种实现方式? 各优缺点&#xff1f; 3、线程的基本操作&#xff08;线程管理机制&#xff…...

KRaft模式下的Kafka启动指南:摆脱Zookeeper依赖

一、背景介绍 多年来&#xff0c;人们一直在同时使用Apache ZooKeeper和Apache Kafka。但是自Apache Kafka 3.3发布以来&#xff0c;它就可以在没有ZooKeeper的情况下运行。同时它包含了新的命令kafka-metadata-quorum和kafka-metadata-shell?该如何安装新版kafka&#xff0c…...

【数据库】MySQL-基础篇-函数

专栏文章索引&#xff1a;数据库 有问题可私聊&#xff1a;QQ&#xff1a;3375119339 目录 一、简介 二、字符串函数 三、数值函数 四、日期函数 五、流程函数 一、简介 函数 是指一段可以直接被另一段程序调用的程序或代码。 也就意味着&#xff0c;这一段程序或代码在 M…...

dp练习【4】

最长数对链 646. 最长数对链 给你一个由 n 个数对组成的数对数组 pairs &#xff0c;其中 pairs[i] [lefti, righti] 且 lefti < righti 。 现在&#xff0c;我们定义一种 跟随 关系&#xff0c;当且仅当 b < c 时&#xff0c;数对 p2 [c, d] 才可以跟在 p1 [a, b…...

ES6从入门到精通:前言

ES6简介 ES6&#xff08;ECMAScript 2015&#xff09;是JavaScript语言的重大更新&#xff0c;引入了许多新特性&#xff0c;包括语法糖、新数据类型、模块化支持等&#xff0c;显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var&#xf…...

CTF show Web 红包题第六弹

提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框&#xff0c;很难让人不联想到SQL注入&#xff0c;但提示都说了不是SQL注入&#xff0c;所以就不往这方面想了 ​ 先查看一下网页源码&#xff0c;发现一段JavaScript代码&#xff0c;有一个关键类ctfs…...

ubuntu搭建nfs服务centos挂载访问

在Ubuntu上设置NFS服务器 在Ubuntu上&#xff0c;你可以使用apt包管理器来安装NFS服务器。打开终端并运行&#xff1a; sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享&#xff0c;例如/shared&#xff1a; sudo mkdir /shared sud…...

模型参数、模型存储精度、参数与显存

模型参数量衡量单位 M&#xff1a;百万&#xff08;Million&#xff09; B&#xff1a;十亿&#xff08;Billion&#xff09; 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的&#xff0c;但是一个参数所表示多少字节不一定&#xff0c;需要看这个参数以什么…...

YSYX学习记录(八)

C语言&#xff0c;练习0&#xff1a; 先创建一个文件夹&#xff0c;我用的是物理机&#xff1a; 安装build-essential 练习1&#xff1a; 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件&#xff0c;随机修改或删除一部分&#xff0c;之后…...

条件运算符

C中的三目运算符&#xff08;也称条件运算符&#xff0c;英文&#xff1a;ternary operator&#xff09;是一种简洁的条件选择语句&#xff0c;语法如下&#xff1a; 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true&#xff0c;则整个表达式的结果为“表达式1”…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)

可以使用Sqliteviz这个网站免费编写sql语句&#xff0c;它能够让用户直接在浏览器内练习SQL的语法&#xff0c;不需要安装任何软件。 链接如下&#xff1a; sqliteviz 注意&#xff1a; 在转写SQL语法时&#xff0c;关键字之间有一个特定的顺序&#xff0c;这个顺序会影响到…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)

笔记整理&#xff1a;刘治强&#xff0c;浙江大学硕士生&#xff0c;研究方向为知识图谱表示学习&#xff0c;大语言模型 论文链接&#xff1a;http://arxiv.org/abs/2407.16127 发表会议&#xff1a;ISWC 2024 1. 动机 传统的知识图谱补全&#xff08;KGC&#xff09;模型通过…...

TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案

一、TRS收益互换的本质与业务逻辑 &#xff08;一&#xff09;概念解析 TRS&#xff08;Total Return Swap&#xff09;收益互换是一种金融衍生工具&#xff0c;指交易双方约定在未来一定期限内&#xff0c;基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度​WebSocket图片帧​定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐​RTMP推流​TRTC/即构SDK推流❌ 付费方案 &#xff08;部分有免费额度&#x…...