设计模式之单例模式(C++)
作者:翟天保Steven
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处
一、单例模式是什么?
单例模式是一种创建型的软件设计模式,在工程项目中非常常见。通过单例模式的设计,使得创建的类在当前进程中只有一个实例,并提供一个全局性的访问点,这样可以规避因频繁创建对象而导致的内存飙升情况。
实现单例模式的三个要点:
1)私有化构造函数:这样外界就无法自由地创建类对象,进而阻止了多个实例的产生。
2)类定义中含有该类的唯一静态私有对象:静态变量存放在全局存储区,且是唯一的,供所有对象使用。
3)用公有的静态函数来获取该实例:提供了访问接口。
单例模式一般分为懒汉式和饿汉式。
1)懒汉式:在使用类对象(单例实例)时才会去创建它,不然就懒得去搞。
2)饿汉式:单例实例在类装载时构建,有可能全局都没使用过,但它占用了空间,就像等着发救济粮的饿汉提前排好队等吃的一样。
二、懒汉式实现
2.1 懒汉基础实现
最基本的懒汉实现方法。
//Singleton.h
/****************************************************/
#include <iostream>using namespace std;// 单例模式演示类
class Singleton
{
public:// 公有接口获取唯一实例static Singleton* getInstance(){// 若为空则创建if (instance == nullptr) {cout << "实例为空,开始创建。" << endl;instance = new Singleton();cout << "创建结束。" << endl;}else {cout << "已有实例,返回。" << endl;}return instance;}
private:// 私有构造函数Singleton(){cout << "构造函数启动。" << endl;};// 私有析构函数~Singleton(){cout << "析构函数启动。" << endl;};
private:// 静态私有对象static Singleton* instance;
};// 初始化
Singleton* Singleton::instance = nullptr;
//main.h
/****************************************************/
#include <iostream>
#include "Singleton.h"using namespace std;int main()
{cout << "main开始" << endl;Singleton* s1 = Singleton::getInstance();Singleton* s2 = Singleton::getInstance();Singleton* s3 = Singleton::getInstance();cout << "main结束" << endl;return 0;
}
执行代码,让我们看看结果。
从结果中可以看出这样设计主要有两个问题,一个是线程安全,另一个是内存泄漏。
线程安全是因为在多线程场景下,有可能出现多个线程同时进行new操作的情况,没通过加锁来限制。
内存泄漏是因为使用了new在堆上分配了资源,那么在程序结束时,也应该进行delete,确保堆中数据释放。
接下来,我们先解决线程安全问题,对懒汉式实现进行改进。
2.2 基于双重检测锁的懒汉实现
通过双重检测锁,可以确保线程安全。
//Singleton.h
/****************************************************/
#include <iostream>
#include <mutex>using namespace std;// 单例模式演示类
class Singleton
{
public:// 公有接口获取唯一实例static Singleton* getInstance(){// 若为空则创建if (instance == nullptr) {// 加锁保证线程安全// 如果两个线程同时进行到这一步,一个线程继续向下执行时,另一个线程被堵塞// 等锁解除后,被堵塞的线程就会跳过下面的if了,因为此时实例已经构建完毕lock_guard<mutex> l(m_mutex);if (instance == nullptr) {cout << "实例为空,开始创建。" << endl;instance = new Singleton();cout << "地址为:" << instance << endl;cout << "创建结束。" << endl;}}else {cout << "已有实例,返回。" << endl;}return instance;}
private:// 私有构造函数Singleton(){cout << "构造函数启动。" << endl;};// 私有析构函数~Singleton(){cout << "析构函数启动。" << endl;};
private:// 静态私有对象static Singleton* instance;// 锁static mutex m_mutex;
};// 初始化
Singleton* Singleton::instance = nullptr;
mutex Singleton::m_mutex;
//main.h
/****************************************************/
#include <iostream>
#include "Singleton.h"using namespace std;int main()
{cout << "main开始" << endl;thread t1([] {Singleton* s1 = Singleton::getInstance();});thread t2([] {Singleton* s2 = Singleton::getInstance();});t1.join();t2.join();Singleton* s3 = Singleton::getInstance();cout << "地址为:" << s3 << endl;cout << "main结束" << endl;return 0;
}
执行代码,让我们看看结果。
这样看来没有问题,那如果取消双重检测锁,在多线程下看看会发生什么。将代码部分函数修改为下。把锁注释掉,再查看地址信息。
// 公有接口获取唯一实例
static Singleton* getInstance(){// 若为空则创建if (instance == nullptr) {// 加锁保证线程安全// 如果两个线程同时进行到这一步,一个线程继续向下执行时,另一个线程被堵塞// 等锁解除后,被堵塞的线程就会跳过下面的if了,因为此时实例已经构建完毕//lock_guard<mutex> l(m_mutex);if (instance == nullptr) {cout << "实例为空,开始创建。" << endl;instance = new Singleton();cout << "地址为:" << instance << endl;cout << "创建结束。" << endl;}}else {cout << "已有实例,返回。" << endl;}return instance;
}
此时结果中可以看出,两个线程进行了两次new操作,但是最后只能捕捉到最后一次new的地址信息了,前面的那个丢失了。。。。。
这个测试也是让大家直观地感受下双重检测锁的用处。
接下来,我们再解决内存泄漏(资源释放)问题,对懒汉式实现进行进一步的改进。
2.3 基于双重检测锁和资源管理的懒汉实现
在2.2的基础上,我们加入资源管理机制,以达到对资源的释放的目的,解决方法有两个:智能指针&静态嵌套类。
2.3.1 智能指针方案
将实例指针更换为智能指针,另外智能指针在初始化时,还需要人为添加公有的毁灭函数,因为析构函数私有化了。
//Singleton.h
/****************************************************/
#include <iostream>
#include <mutex>using namespace std;// 单例模式演示类
class Singleton
{
public:// 公有接口获取唯一实例static shared_ptr<Singleton> getInstance(){// 若为空则创建if (instance == nullptr) {// 加锁保证线程安全// 如果两个线程同时进行到这一步,一个线程继续向下执行时,另一个线程被堵塞// 等锁解除后,被堵塞的线程就会跳过下面的if了,因为此时实例已经构建完毕lock_guard<mutex> l(m_mutex);if (instance == nullptr) {cout << "实例为空,开始创建。" << endl;instance.reset(new Singleton(), destoryInstance);cout << "地址为:" << instance << endl;cout << "创建结束。" << endl;}}else {cout << "已有实例,返回。" << endl;}return instance;}// 毁灭实例static void destoryInstance(Singleton* x) {cout << "自定义释放实例" << endl;delete x;}
private:// 私有构造函数Singleton(){cout << "构造函数启动。" << endl;};// 私有析构函数~Singleton(){cout << "析构函数启动。" << endl;};
private:// 静态私有对象static shared_ptr<Singleton> instance;// 锁static mutex m_mutex;
};// 初始化
shared_ptr<Singleton> Singleton::instance;
mutex Singleton::m_mutex;
//main.h
/****************************************************/
#include <iostream>
#include "Singleton.h"using namespace std;int main()
{cout << "main开始" << endl;thread t1([] {shared_ptr<Singleton> s1 = Singleton::getInstance();});thread t2([] {shared_ptr<Singleton> s2 = Singleton::getInstance();});t1.join();t2.join();shared_ptr<Singleton> s3 = Singleton::getInstance();cout << "地址为:" << s3 << endl;cout << "main结束" << endl;return 0;
}
应用智能指针后,在程序结束时,它自动进行资源的释放,解决了内存泄漏的问题。
2.3.2 静态嵌套类方案
类中定义一个嵌套类,初始化该类的静态对象,当程序结束时,该对象进行析构的同时,将单例实例也删除了。
//Singleton.h
/****************************************************/
#include <iostream>
#include <mutex>using namespace std;// 单例模式演示类
class Singleton
{
public:// 公有接口获取唯一实例static Singleton* getInstance() {// 若为空则创建if (instance == nullptr) {// 加锁保证线程安全// 如果两个线程同时进行到这一步,一个线程继续向下执行时,另一个线程被堵塞// 等锁解除后,被堵塞的线程就会跳过下面的if了,因为此时实例已经构建完毕lock_guard<mutex> l(m_mutex);if (instance == nullptr) {cout << "实例为空,开始创建。" << endl;instance = new Singleton();cout << "地址为:" << instance << endl;cout << "创建结束。" << endl;}}else {cout << "已有实例,返回。" << endl;}return instance;}
private:// 私有构造函数Singleton() {cout << "构造函数启动。" << endl;};// 私有析构函数~Singleton() {cout << "析构函数启动。" << endl;};// 定义一个删除器class Deleter {public:Deleter() {};~Deleter() {if (instance != nullptr) {cout << "删除器启动。" << endl;delete instance;instance = nullptr;}}};// 删除器是嵌套类,当该静态对象销毁的时候,也会将单例实例销毁static Deleter m_deleter;
private:// 静态私有对象static Singleton* instance;// 锁static mutex m_mutex;
};// 初始化
Singleton* Singleton::instance = nullptr;
mutex Singleton::m_mutex;
Singleton::Deleter Singleton::m_deleter;
main.h同2.2中的一致,结果如下,可以看出,当嵌套类Deleter对象销毁时,其析构函数执行的实例删除操作也完成了。
2.4 基于局部静态对象的懒汉实现
C++11后,规定了局部静态对象在多线程场景下的初始化行为,只有在首次访问时才会创建实例,后续不再创建而是获取。若未创建成功,其他的线程在进行到这步时会自动等待。注意C++11前的版本不是这样的。
因为有上述的改动,所以出现了一种更简洁方便优雅的实现方法,基于局部静态对象实现。
//Singleton.h
/****************************************************/
#include <iostream>
#include <mutex>using namespace std;// 单例模式演示类
class Singleton
{
public:// 公有接口获取唯一实例static Singleton& getInstance() {cout << "获取实例" << endl;static Singleton instance;cout << "地址为:" << &instance << endl;return instance;}
private:// 私有构造函数Singleton() {cout << "构造函数启动。" << endl;};// 私有析构函数~Singleton() {cout << "析构函数启动。" << endl;};
};
//main.h
/****************************************************/
#include <iostream>
#include "Singleton.h"using namespace std;int main()
{cout << "main开始" << endl;thread t1([] {Singleton &s1 = Singleton::getInstance();});thread t2([] {Singleton &s2 = Singleton::getInstance();});t1.join();t2.join();cout << "main结束" << endl;return 0;
}
从结果中可以看出,构造函数启动了一次,另一个线程直接获取了地址。并且当程序结束时,进行了自动释放。
三、饿汉式实现
3.1 饿汉基础实现
饿汉和懒汉的差别就在于,饿汉提前进行了创建,所以它的基础实现也不是很复杂,如下所示。
//Singleton.h
/****************************************************/
#include <iostream>
#include <mutex>using namespace std;// 单例模式演示类
class Singleton
{
public:// 公有接口获取唯一实例static Singleton* getInstance() {cout << "获取实例" << endl;cout << "地址为:" << instance << endl;return instance;}
private:// 私有构造函数Singleton() {cout << "构造函数启动。" << endl;};// 私有析构函数~Singleton() {cout << "析构函数启动。" << endl;};
private:// 静态私有对象static Singleton* instance;
};// 初始化
Singleton* Singleton::instance = new Singleton();
//main.h
/****************************************************/
#include <iostream>
#include "Singleton.h"using namespace std;int main()
{cout << "main开始" << endl;thread t1([] {Singleton* s1 = Singleton::getInstance();});thread t2([] {Singleton* s2 = Singleton::getInstance();});t1.join();t2.join();cout << "main结束" << endl;return 0;
}
输出结果中可知,main还没开始,实例就已经构建完毕,获取实例的函数也不需要进行判空操作,因此也就不用双重检测锁来保证线程安全了,它本身已经是线程安全状态了。
但是内存泄漏的问题还是要解决的,这点同懒汉是一样的。
3.2 基于资源管理的饿汉实现
内存泄漏解决方法有两个:智能指针&静态嵌套类。
3.2.1 智能指针方案
将实例指针更换为智能指针,另外智能指针在初始化时,还需要人为添加公有的毁灭函数,因为析构函数私有化了。
//Singleton.h
/****************************************************/
#include <iostream>
#include <mutex>using namespace std;// 单例模式演示类
class Singleton
{
public:// 公有接口获取唯一实例static shared_ptr<Singleton> getInstance() {cout << "获取实例" << endl;cout << "地址为:" << instance << endl;return instance;}// 毁灭实例static void destoryInstance(Singleton* x) {cout << "自定义释放实例" << endl;delete x;}
private:// 私有构造函数Singleton() {cout << "构造函数启动。" << endl;};// 私有析构函数~Singleton() {cout << "析构函数启动。" << endl;};
private:// 静态私有对象static shared_ptr<Singleton> instance;
};// 初始化
shared_ptr<Singleton> Singleton::instance(new Singleton(), destoryInstance);
//main.h
/****************************************************/
#include <iostream>
#include "Singleton.h"using namespace std;int main()
{cout << "main开始" << endl;thread t1([] {shared_ptr<Singleton> s1 = Singleton::getInstance();});thread t2([] {shared_ptr<Singleton> s2 = Singleton::getInstance();});t1.join();t2.join();cout << "main结束" << endl;return 0;
}
加入了智能指针后,不出意外地进行了自动的资源释放。
3.2.2 静态嵌套类方案
类中定义一个嵌套类,初始化该类的静态对象,当程序结束时,该对象进行析构的同时,将单例实例也删除了。
//Singleton.h
/****************************************************/
#include <iostream>
#include <mutex>using namespace std;// 单例模式演示类
class Singleton
{
public:// 公有接口获取唯一实例static Singleton* getInstance() {cout << "获取实例" << endl;cout << "地址为:" << instance << endl;return instance;}
private:// 私有构造函数Singleton() {cout << "构造函数启动。" << endl;};// 私有析构函数~Singleton() {cout << "析构函数启动。" << endl;};// 定义一个删除器class Deleter {public:Deleter() {};~Deleter() {if (instance != nullptr) {cout << "删除器启动。" << endl;delete instance;instance = nullptr;}}};// 删除器是嵌套类,当该静态对象销毁的时候,也会将单例实例销毁static Deleter m_deleter;
private:// 静态私有对象static Singleton* instance;
};// 初始化
Singleton* Singleton::instance = new Singleton();
Singleton::Deleter Singleton::m_deleter;
//main.h
/****************************************************/
#include <iostream>
#include "Singleton.h"using namespace std;int main()
{cout << "main开始" << endl;thread t1([] {Singleton* s1 = Singleton::getInstance();});thread t2([] {Singleton* s2 = Singleton::getInstance();});t1.join();t2.join();cout << "main结束" << endl;return 0;
}
同懒汉的一样,不多做阐述。
四、总结
上述讲了这么多关于单例模式的内容,我尽可能地将测试的结果也同步展示了,目的就是帮助大家更好地理解。文中所有的代码都是完整的,可以直接复制到自己的项目中测试验证下。
最后,如果说让我选择用什么样的实现,那我选择用局部静态对象的方法,代码简洁,线程安全,内存无泄漏,有什么理由说不呢?除非你是C++11之前的版本。。。。
如果文章帮助到你了,可以点个赞让我知道,我会很快乐~加油!
相关文章:

设计模式之单例模式(C++)
作者:翟天保Steven 版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处 一、单例模式是什么? 单例模式是一种创建型的软件设计模式,在工程项目中非常常见。通过单例模式的设计&am…...
贪心算法(基础)
目录 一、什么是贪心? (一)以教室调度问题为例 1. 问题 2. 具体做法如下 3. 因此将在这间教室上如下三堂课 4. 结论 (二)贪心算法介绍 1. 贪心算法一般解题步骤 二、最优装载问题 (一…...

【九宫格坐标排列 Objective-C语言】
一、这个案例做好之后的效果如图: 1.这个下载是可以点击的,当你点击之后,弹出一个框,过一会儿,框框自动消失,这里变成“已安装” 2.那么,我现在先问大家一句话:大家认为在这一个应用里面,它包含几个控件, 3个,哪3个:一个是图片框,一个是Label,一个是按钮, 这…...

Tomcat简介
目录 一、Tomcat简介 二、下载安装Tomcat 三、利用Tomcat部署静态页面 一、Tomcat简介 Tomcat是一个HTTP服务器,可以按照HTTP的格式来解析请求来调用用户指定的相关代码然后按照HTTP的格式来构造返回数据。 二、下载安装Tomcat 进入Tomcat官网选择与自己电脑…...

Python基础及函数解读(深度学习)
一、语句1.加注释单行注释:(1)在代码上面加注释: # 后面跟一个空格(2)在代码后面加注释:和代码相距两个空格, # 后面再跟一个空格多行注释:按住shift 点击三次"&am…...

车道线检测-PolyLaneNet 论文学习笔记
论文:《PolyLaneNet: Lane Estimation via Deep Polynomial Regression》代码:https://github.com/lucastabelini/PolyLaneNet地址:https://arxiv.org/pdf/2004.10924.pdf参考:https://blog.csdn.net/sinat_17456165/article/deta…...
GO——接口(下)
接口接口值警告:一个包含空指针值的接口不是nil接口sort.Interface接口http.Handler接口类型断言类型分支接口值 接口值,由两个部分组成,一个具体的类型和那个类型的值。它们被称为接口的动态类型和动态值。对于像Go语言这种静态类型的语言&…...

计算机网络之http02| HTTPS HTTP1.1的优化
post与get请求的区别 get 是获取资源,Post是向指定URI提交资源,相关信息放在body里 2.http有哪些优点 (1)简单 报文只有报文首部和报文主体,易于理解 (2)灵活易拓展 URI相应码、首部字段都没有…...

基于matlab使用神经网络清除海杂波
一、前言此示例演示如何使用深度学习工具箱™训练和评估卷积神经网络,以消除海上雷达 PPI 图像中的杂波返回。深度学习工具箱提供了一个框架,用于设计和实现具有算法、预训练模型和应用程序的深度神经网络。二、数据集该数据集包含 84 对合成雷达图像。每…...

每天10个前端小知识 【Day 8】
前端面试基础知识题 1. Javascript中如何实现函数缓存?函数缓存有哪些应用场景? 函数缓存,就是将函数运算过的结果进行缓存。本质上就是用空间(缓存存储)换时间(计算过程), 常用于…...

【项目精选】基于Java的敬老院管理系统的设计和实现
本系统主要是针对敬老院工作人员即管理员和员工设计的。敬老院管理系统 将IT技术为养老院提供一个接口便于管理信息,存储老人个人信息和其他信息,查找 和更新信息的养老院档案,节省了员工的劳动时间,大大降低了成本。 其主要功能包括: 系统管理员用户功能介绍&#…...

Spark SQL 介绍
文章目录Spark SQL1、Hive on SparkSQL2、SparkSQL 优点3、SparkSQL 特点1) 容易整合2) 统一的数据访问3) 兼容 Hive4) 标准的数据连接4、DataFrame 是什么5、DataSet 是什么Spark SQL Spark SQL 是 Spark 用于结构化数据(structured data) 处理的Spark模块。 1、Hive on Spa…...
升级到 CDP 后Hive on Tez 性能调整和故障排除指南
优化Hive on Tez查询永远不能以一种万能的方法来完成。查询的性能取决于数据的大小、文件类型、查询设计和查询模式。在性能测试期间,要评估和验证配置参数和任何 SQL 修改。建议在工作负载的性能测试期间一次进行一项更改,并且最好在生产环境中使用它们…...

理解HDFS工作流程与机制,看这篇文章就够了
HDFS(The Hadoop Distributed File System) 是最初由Yahoo提出的分布式文件系统,它主要用来: 1)存储大数据 2)为应用提供大数据高速读取的能力 重点是掌握HDFS的文件读写流程,体会这种机制对整个分布式系统性能提升…...

Intel处理器分页机制
分页模式 Intel 64位处理器支持3种分页模式: 32-bit分页PAE分页IA-32e分页 32-bit分页 32-bit分页模式支持两种页面大小:4KB以及4MB。 4KB页面的线性地址转换 4MB页面的线性地址转换 PAE分页模式 PAE分页模式支持两种页面大小:4KB以及…...

Linux常用命令
linux常用命令创建一个目录mkdir 命令可以创建新目录。mkdir 是 make directory 的缩写。[rootiZ2ze66tzux2otcpbvie88Z ~]# ls [rootiZ2ze66tzux2otcpbvie88Z ~]# mkdir web [rootiZ2ze66tzux2otcpbvie88Z ~]# ls web [rootiZ2ze66tzux2otcpbvie88Z ~]# 创建一个文件2.1 在 Li…...

基于STM32设计的音乐播放器
一、项目背景与设计思路 1.1 项目背景 时代进步,科学技术的不断创新,促进电子产品的不断更迭换代,各种新功能和新技术的电子产品牵引着消费者的眼球。人们生活水平的逐渐提高,对娱乐消费市场需求日益扩大,而其消费电子产品在市场中的占有份额越来越举足轻重。目前消费电…...
微服务开发
目录 微服务配置管理 权限认证 批处理 定时任务 异步 微服务调用 (协议)...

【(C语言)数据结构奋斗100天】二叉树(上)
【(C语言)数据结构奋斗100天】二叉树(上) 🏠个人主页:泡泡牛奶 🌵系列专栏:数据结构奋斗100天 本期所介绍的是二叉树,那么什么是二叉树呢?在知道答案之前,请大家思考一下…...

Java 验证二叉搜索树
验证二叉搜索树中等给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。有效 二叉搜索树定义如下:节点的左子树只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。示例 1&…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...

LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...

自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
比较数据迁移后MySQL数据库和OceanBase数据仓库中的表
设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...