【项目设计】MySQL 连接池的设计

目录

    • 👉关键技术点👈
    • 👉项目背景👈
    • 👉连接池功能点介绍👈
    • 👉MySQL Server 参数介绍👈
    • 👉功能实现设计👈
    • 👉开发平台选型👈
    • 👉MySQL 数据库编程👈
    • 👉连接池的编写👈
    • 👉压力测试👈
    • 👉项目常见问题👈

👉关键技术点👈

MySQL 数据库编程、单例模式、queue 队列容器、C++11 多线程编程、线程互斥、线程同步通信和 unique_lock、基于 CAS 的原子整形、智能指针 shared_ptr、lambda 表达式、生产者-消费者线程模型。

👉项目背景👈

为了提高 MySQL 数据库(基于 C/S 设计)的访问瓶颈,除了在服务器端增加缓存服务器缓存常用的数据之外(例如 redis),还可以增加连接池,来提高 MySQL Server 的访问效率,在高并发情况下,大量的 TCP 三次握手、MySQL Server 连接认证、MySQL Server 关闭连接回收资源和 TCP 四次挥手所耗费的性能时间也是很明显的,增加连接池就是为了减少这一部分的性能损耗。

在这里插入图片描述

在市场上比较流行的连接池包括阿里的 druid、c3p0 以及 apache dbcp 连接池,它们对于短时间内大量的数据库增删改查操作性能的提升是很明显的,但是它们有一个共同点就是,全部由 Java 实现的。

那么本项目就是为了在 C/C++ 项目中,提供 MySQL Server 的访问效率,实现基于 C++代 码的数据库连接
池模块。

👉连接池功能点介绍👈

连接池是一个数据库连接的管理工具,旨在优化数据库连接的开启、关闭和复用,从而提高数据库访问性能和系统的并发处理能力。连接池在应用程序启动时预先创建一定数量的数据库连接,并将它们放入一个池中。当应用程序需要连接数据库时,从连接池中获取一个空闲的连接,使用完毕后再将连接归还给连接池,以便其他请求可以复用这个连接。这样一来,就避免了频繁地开启和关闭数据库连接的开销。

连接池一般包含了数据库连接所用的 ip 地址、port 端口号、用户名和密码以及其它的性能参数,例如初始连接量,最大连接量,最大空闲时间、连接超时时间等,该项目是基于 C++ 语言实现的连接池,主要也是实现以上几个所有连接池都支持的通用基础功能。

初始连接量(initSize):表示连接池事先会和 MySQL Server 创建 initSize 个数的 connection 连接,当应用发起 MySQL 访问时,不用再创建和 MySQL Server 新的连接,直接从连接池中获取一个可用的连接就可以,使用完成后,并不去释放 connection,而是把当前 connection 再归还到连接池当中。

最大连接量(maxSize):当并发访问 MySQL Server 的请求增多时,初始连接量已经不够使用了,此时会根据新的请求数量去创建更多的连接给应用去使用,但是新创建的连接数量上限是 maxSize,不能无限制的创建连接,因为每一个连接都会占用一个 socket 资源,一般连接池和服务器程序是部署在一台主机上的,如果连接池占用过多的 socket 资源,那么服务器就不能接收太多的客户端请求了。当这些连接使用完成后,再次归还到连接池当中来维护。

最大允许空闲时间(maxIdleTime):当访问 MySQL 的并发请求多了以后,连接池里面的连接数量会动态增加,上限是 maxSize 个,当这些连接用完再次归还到连接池当中。如果在指定的 maxIdleTime 最大允许空闲时间里面,这些新增加的连接都没有被再次使用过,那么新增加的这些连接资源就要被回收掉,只需要保持初始连接量 initSize 个连接就可以了。连接资源回收掉后,系统的 socket 资源就会增多,可以接收更多的客户端请求。

连接超时时间(connectionTimeout):当 MySQL 的并发请求量过大,连接池中的连接数量已经到达 maxSize 了,而此时没有空闲的连接可供使用,那么此时应用从连接池获取连接无法成功,它通过阻塞的方式获取连接的时间如果超过 connectionTimeout 时间,那么获取连接失败,无法访问数据库。

该项目主要实现上述的连接池四大功能,其余连接池更多的扩展功能,可以自行实现。同时本项目实现的只是一个组件,并不涉及具体的业务,所以你可以根据自己的业务需求将该组件引入到你自己的项目中。

👉MySQL Server 参数介绍👈

show variables like 'max_connections';

该命令可以查看 MySQL Server 所支持的最大连接个数,超过 max_connections 数量的连接,MySQL
Server 会直接拒绝连接请求,所以在使用连接池增加连接数量的时候,MySQL Server 的 max_connections 参数也要适当的进行调整,以适配连接池的连接上限。

👉功能实现设计👈

  • ConnectionPool.cpp 和 ConnectionPool.h:连接池代码实现。
  • Connection.cpp 和 Connection.h:数据库操作代码、增删改查代码实现。

在这里插入图片描述

连接池主要包含了以下功能点:

  1. 连接池只需要一个实例,所以 ConnectionPool 以单例模式进行设计。
  2. 从 ConnectionPool 中可以获取和 MySQL 的连接Connection。
  3. 空闲连接 Connection 全部维护在一个线程安全的Connection 队列中,使用线程互斥锁保证队列的线
    程安全。
  4. 如果 Connection 队列为空,还需要再获取连接,此时需要动态创建连接,上限数量是 maxSize。
  5. 队列中空闲连接时间超过 maxIdleTime 的就要被释放掉,只保留初始的 initSize 个连接就可以了,这个功能点肯定需要放在独立的线程中去做。
  6. 如果 Connection 队列为空,而此时连接的数量已达上限 maxSize,那么等待 connectionTimeout 时间如果还获取不到空闲的连接,那么获取连接失败,此处从 Connection 队列获取空闲连接,可以使用带超时时间的 mutex 互斥锁来实现连接超时时间。
  7. 用户获取的连接用 shared_ptr 智能指针来管理,用 lambda 表达式定制连接释放的功能(不真正释放
    连接,而是把连接归还到连接池中)。
  8. 连接的生产和连接的消费采用生产者-消费者线程模型来设计,使用了线程间的同步通信机制条件变量和互斥锁。

👉开发平台选型👈

有关 MySQL 数据库编程、多线程编程、线程互斥和同步通信操作、智能指针、设计模式、容器等等这些技术在 C++ 语言层面都可以直接实现,因此该项目选择直接在 Windows 平台上进行开发,当然放在 Linux 平台下用 g++ 也可以直接编译运行。

👉MySQL 数据库编程👈

MySQL 的 Windows 安装教程见下方链接:

https://blog.csdn.net/m0_51510236/article/details/129190003

在这里插入图片描述

在这里插入图片描述
安装好后,development 开发包: mysql 头文件和 libmysql 库文件就都下载好了。

创建测试表

create database chat;use chat;create table user(
id int primary key auto_increment,name varchar(50),age int,sex enum('male', 'female'));mysql> desc user;
+-------+-----------------------+------+-----+---------+----------------+
| Field | Type                  | Null | Key | Default | Extra          |
+-------+-----------------------+------+-----+---------+----------------+
| id    | int(11)               | NO   | PRI | NULL    | auto_increment |
| name  | varchar(50)           | YES  |     | NULL    |                |
| age   | int(11)               | YES  |     | NULL    |                |
| sex   | enum('male','female') | YES  |     | NULL    |                |
+-------+-----------------------+------+-----+---------+----------------+
4 rows in set (0.01 sec)

这里的MySQL数据库编程直接采用oracle公司提供的MySQL C/C++客户端开发包,在VS上需要进行相
应的头文件和库文件的配置,如下:

  1. 右键项目 - C/C++ - 常规 - 附加包含目录,填写 mysql.h 头文件的路径
  2. 右键项目 - 链接器 - 常规 - 附加库目录,填写 libmysql.lib 的路径
  3. 右键项目 - 链接器 - 输入 - 附加依赖项,填写 libmysql.lib 库的名字
  4. 把 libmysql.dll 动态链接库(Linux下后缀名是.so库)放在工程目录下

日志功能:

// public.h
#pragma once#include <iostream>
using namespace std;/* 日志宏 */
#define LOG(str) \cout << __FILE__ << ":" << __LINE__ << " " << \__TIMESTAMP__ << " : " << str << endl;

MySQL 数据库 C++ 代码封装如下:

// Connection.h
#pragma once#include <string>
#include <mysql.h>using namespace std;/* 实现 MySQL 数据库的操作 */
class Connection
{
public:// 初始化数据库连接Connection();// 释放数据库连接资源~Connection();// 连接数据库bool connect(string ip,unsigned short port,string user,string password,string dbname);// 更新操作 insert、delete、updatebool update(string sql);// 查询操作 selectMYSQL_RES* query(string sql);private:MYSQL* _conn; // 表示和 MySQL Server 的一条连接
};
// Connection.cpp
#include "public.h"
#include "Connection.h"// 初始化数据库连接
Connection::Connection()
{_conn = mysql_init(nullptr);
}// 释放数据库连接资源
Connection::~Connection()
{if (_conn != nullptr) mysql_close(_conn);
}// 连接数据库: true 连接成功 false 连接失败
bool Connection::connect(string ip, unsigned short port,string user, string password, string dbname)
{MYSQL* p = mysql_real_connect(_conn, ip.c_str(), user.c_str(),password.c_str(), dbname.c_str(), port, nullptr, 0);if (p != nullptr){mysql_set_character_set(_conn, "utf8"); // 设置连接的编码return true;}elsereturn false;
}// 更新操作: insert、delete、update
bool Connection::update(string sql)
{// mysql_query: 1 表示失败 0 表示成功if (mysql_query(_conn, sql.c_str())){LOG("更新失败:" + sql);return false;}return true;
}// 查询操作: select
MYSQL_RES* Connection::query(string sql)
{if (mysql_query(_conn, sql.c_str())){LOG("查询失败:" + sql);return nullptr;}return mysql_use_result(_conn);
}
// main.cpp
#include <iostream>
#include "Connection.h"using namespace std;int main()
{Connection conn;char sql[1024] = { 0 };sprintf(sql, "insert into user (name, age, sex) values('%s', %d, '%s')","zhang san", 20, "male");bool ret = conn.connect("127.0.0.1", 3306, "root", "123456", "chat");conn.update(sql);return 0;
}

运行代码后,测看数据库。

mysql> select * from user;
+----+----------+------+------+
| id | name     | age  | sex  |
+----+----------+------+------+
|  1 | zhangsan |   20 | male |
+----+----------+------+------+
1 row in set (0.00 sec)

👉连接池的编写👈

Connection.h

#pragma once#include <ctime>
#include <string>
#include <mysql.h>using namespace std;/* 实现 MySQL 数据库的操作 */
class Connection
{
public:// 初始化数据库连接Connection();// 释放数据库连接资源~Connection();// 连接数据库bool connect(string ip,unsigned short port,string user,string password,string dbname);// 更新操作 insert、delete、updatebool update(string sql);// 查询操作 selectMYSQL_RES* query(string sql);// 刷新一下连接的起始的空闲时间点void refreshAliveTime(){_aliveTime = clock(); // clock 函数返回值的单位为毫秒}// 获取连接已经空闲了多长时间clock_t getAliveTime() const{return clock() - _aliveTime;}private:MYSQL* _conn; // 表示和 MySQL Server 的一条连接clock_t _aliveTime; // 记录进入空闲状态后的起始存活时间
};

Connection.cpp

#include "public.h"
#include "Connection.h"// 初始化数据库连接
Connection::Connection()
{_conn = mysql_init(nullptr);
}// 释放数据库连接资源
Connection::~Connection()
{if (_conn != nullptr) mysql_close(_conn);
}// 连接数据库: true 连接成功 false 连接失败
bool Connection::connect(string ip, unsigned short port,string user, string password, string dbname)
{MYSQL* p = mysql_real_connect(_conn, ip.c_str(), user.c_str(),password.c_str(), dbname.c_str(), port, nullptr, 0);if (p != nullptr){mysql_set_character_set(_conn, "utf8"); // 设置连接的编码return true;}elsereturn false;
}// 更新操作: insert、delete、update
bool Connection::update(string sql)
{// mysql_query: 1 表示失败 0 表示成功if (mysql_query(_conn, sql.c_str())){LOG("更新失败:" + sql);return false;}return true;
}// 查询操作: select
MYSQL_RES* Connection::query(string sql)
{if (mysql_query(_conn, sql.c_str())){LOG("查询失败:" + sql);return nullptr;}return mysql_use_result(_conn);
}

CommonConnectionPool.h

#pragma once#include <string>
#include <queue>
#include <mutex>
#include <atomic>
#include <thread>
#include <memory>
#include <functional>
#include <condition_variable>
#include "Connection.h"using namespace std;/* 实现连接池功能模块 */
class ConnectionPool
{
public:// 获取连接池对象实例static ConnectionPool* getConnectionPool();// 给消费者线程提供接口, 从连接池中获取一个空闲的连接// 返回值是智能指针, 需要定制智能指针的删除器// 该删除器的功能就是当消费者线程用完连接后, // 连接自动放回连接池中shared_ptr<Connection> getConnection();private:// 构造函数私有化ConnectionPool(); // 加载配置文件bool loadConfigFile();// 生产连接, 由生产者线程调用void produceConnection();// 扫描空闲连接, 看其空闲时间是否超过 maxIdleTimevoid scannConnection();string _ip;              // MySQL 的 IP 地址unsigned short _port;	 // MySQL 的端口号 3306string _username;        // MySQL 登录用户名string _password;        // MySQL 登录密码string _dbname;			 // 要连接的数据库int _initSize;           // 连接池的初始连接量int _maxSize;            // 连接池的最大连接量int _maxIdleTime;        // 连接池的最大允许空闲时间int _connectionTimeout;  // 连接池获取连接的超时时间queue<Connection*> _connectionQue; // 存储 MySQL 连接的队列mutex _queueMutex;	// 保证连接队列线程安全的互斥锁atomic_int _connectionCnt; // 记录所创建的 Connection 连接的总数量condition_variable _cv; // 设置条件变量, 用于生产者线程和消费者线程之间的同步
};

配置文件 mysql.ini

# 数据库连接池的配置文件
ip=127.0.0.1
port=3306
username=root
password=123456
dbname=chat
initSize=10
maxSize=1024
# 最大允许空闲时间默认单位是秒
maxIdleTime=60
# 连接超时时间默认单位是毫秒
connectionTimeout=100

CommonConnectionPool.cpp

#include "public.h"
#include "CommonConnectionPool.h"// 线程安全的懒汉单例函数接口
ConnectionPool* ConnectionPool::getConnectionPool()
{static ConnectionPool pool; // 静态变量初始化, 编译器自动 lock 和 unlockreturn &pool;
}// 连接池的构造
ConnectionPool::ConnectionPool()
{// 加载配置文件失败if (!loadConfigFile()) return;// 创建初始数量的连接for (int i = 0; i < _initSize; ++i){Connection* p = new Connection();p->connect(_ip, _port, _username, _password, _dbname);// 此处没有多线程, 因此不存在线程安全问题p->refreshAliveTime(); // 刷新一下连接开始空闲的起始时间_connectionQue.push(p);++_connectionCnt;}// 启动一个新的线程, 作为生产连接的线程// 使用绑定器给生产者线程绑定类内方法thread producer(bind(&ConnectionPool::produceConnection, this));producer.detach();// 启动一个新的线程, 作为定时线程// 负责扫描多余的空闲连接, 对空闲时间超过  // maxIdleTime 的空闲连接, 进行回收thread scanner(bind(&ConnectionPool::scannConnection, this));scanner.detach();
}// 加载配置文件
bool ConnectionPool::loadConfigFile()
{FILE* pf = fopen("mysql.ini", "r");if (pf == nullptr){LOG("mysql.ini file not exists!");return false;}// 没有到文件末尾, feof 返回 0while (!feof(pf)){char line[1024] = { 0 };fgets(line, 1024, pf);string str = line;size_t index = str.find('=', 0);// password=123456\nif (index == string::npos) // 无效配置项{continue;}size_t endIndex = str.find('\n', index);string key = str.substr(0, index);string value = str.substr(index + 1, endIndex - index - 1);if (key == "ip") _ip = value;else if (key == "port") _port = stoi(value.c_str());else if (key == "username") _username = value;else if (key == "password") _password = value;else if (key == "dbname") _dbname = value;else if (key == "initSize") _initSize = stoi(value.c_str());else if (key == "maxSize") _maxSize = stoi(value.c_str());else if (key == "maxIdleTime") _maxIdleTime = stoi(value.c_str());else if (key == "connectionTimeout") _connectionTimeout = stoi(value.c_str());}return true;
}// 运行在独立的线程中, 专门负责生产新链接
void ConnectionPool::produceConnection()
{while (true){unique_lock<mutex> lock(_queueMutex);while (!_connectionQue.empty()){_cv.wait(lock); // 队列不为空, 生产者线程进入等待状态}// 连接数量没有到达上限, 继续创建新的连接if (_connectionCnt < _maxSize){Connection* p = new Connection();p->connect(_ip, _port, _username, _password, _dbname);p->refreshAliveTime(); // 刷新一下连接开始空闲的起始时间_connectionQue.push(p);++_connectionCnt;}// 通知消费者线程, 可以获取新连接了_cv.notify_all();}
}// 由服务器的应用线程调用, 获取连接
shared_ptr<Connection> ConnectionPool::getConnection()
{unique_lock<mutex> lock(_queueMutex);while (_connectionQue.empty()){// 不能使用 sleep 函数, sleep 是直接休眠// 线程向下执行的可能情况: 被唤醒和超时// 被唤醒的可能情况: 生产者生产了新连接或消费者归还了连接// 如果是被唤醒的话, 队列肯定不为空, 那么就会跳出 while 循环// 如果是超时的话, 再判断队列是否为空, 如果为空, 那么获取连接// 就失败了; 如果不为空, 那么也会跳出 while 循环if (cv_status::timeout == _cv.wait_for(lock, chrono::microseconds(_connectionTimeout))){if (_connectionQue.empty()){LOG("获取空闲连接超时了...获取连接失败!");return nullptr;}}}// 队列不为空// shared_ptr 智能指针析构时, 会把 Connection 资源直接 delete掉// 相当于调用 Connection 的析构函数, 将 MySQL 连接给关闭掉, 因此// 需要定制 shared_ptr 释放资源的方法// 定制删除器: 将消费者用完的 Connection 连接放回到队列中shared_ptr<Connection> sp(_connectionQue.front(), [&](Connection* pcon) {// 这里是在服务器应用线程中调用的, 因此一定要考虑队列的线程安全问题unique_lock<mutex> lock(_queueMutex);pcon->refreshAliveTime(); // 刷新一下连接开始空闲的起始时间_connectionQue.push(pcon);});_connectionQue.pop();  // 消费者获取了该连接, 因此需要 pop 掉if (_connectionQue.empty()){// 谁消费了队列中最后一个 Connection, 谁负责通知生产者进行生产_cv.notify_all(); }return sp;
}// 扫描空闲连接, 看其空闲时间是否超过 maxIdleTime
void ConnectionPool::scannConnection()
{while (true){// 通过 sleep 来模拟定时效果this_thread::sleep_for(chrono::seconds(_maxIdleTime));// 扫描整个队列, 释放多余的空闲连接unique_lock<mutex> lock(_queueMutex);while (_connectionCnt > _initSize){Connection* p = _connectionQue.front();if (p->getAliveTime() >= (_maxIdleTime * 1000)){_connectionQue.pop();--_connectionCnt;delete p; // 调用 ~Connection() 释放连接}else{break; // 队头连接都没有超过 _maxIdleTime, 其他连接肯定也没有}}}
}

👉压力测试👈

验证数据的插入操作所花费的时间,第一次测试使用普通的数据库访问操作,第二次测试使用带连接池的数据库访问操作,对比两次操作同样数据量所花费的时间,性能压力测试结果如下:

在这里插入图片描述
可以看到,在单线程场景下,使用连接池性能会得到很大的提升。而在多线程场景下,使用连接池性能提升不明显。原因可能是:多线程涉及互斥锁所带来的一系列开销,这个开销可能会抵消避免频繁建立连接所省去的开销。

压力测试代码

#include <iostream>
#include <vector>
#include "CommonConnectionPool.h"using namespace std;int main()
{// 四线程测试auto fun = []() {// 使用连接池ConnectionPool* cp = ConnectionPool::getConnectionPool();int times = 2500;for (int i = 0; i < times; ++i){char sql[1024] = { 0 };sprintf(sql, "insert into user (name, age, sex) values('%s', %d, '%s')","zhang san", 20, "male");shared_ptr<Connection> sp = cp->getConnection();sp->update(sql);}// 未使用连接池//int times = 2500;//for (int i = 0; i < times; ++i)//{//	char sql[1024] = { 0 };//	sprintf(sql, "insert into user (name, age, sex) values('%s', %d, '%s')",//		"zhang san", 20, "male");//	Connection conn;//	conn.connect("127.0.0.1", 3306, "root", "123456", "chat");//	conn.update(sql);//}};// 未使用连接池的情况需要先登录一下, 因为不能一个// 用户被多次同时登录, 如果不这样将无法完成测试Connection conn;conn.connect("127.0.0.1", 3306, "root", "123456", "chat");int num = 4;clock_t begin = clock();vector<thread> v;for (int i = 0; i < num; ++i) v.push_back(thread(fun));for (int i = 0; i < num; ++i) v[i].join();clock_t end = clock();cout << (end - begin) << "ms" << endl;return 0;// 单线程测试
#if 0size_t times = 10000;clock_t begin = clock();for (int i = 0; i < times; ++i){// 未使用连接池char sql[1024] = { 0 };sprintf(sql, "insert into user (name, age, sex) values('%s', %d, '%s')","zhang san", 20, "male");Connection conn;conn.connect("127.0.0.1", 3306, "root", "123456", "chat");conn.update(sql);// 使用连接池//char sql[1024] = { 0 };//sprintf(sql, "insert into user (name, age, sex) values('%s', %d, '%s')",//	"zhang san", 20, "male");//shared_ptr<Connection> sp = cp->getConnection();//sp->update(sql);}clock_t end = clock();cout << (end - begin) << "ms" << endl;return 0;
#endif
}

压力测试注意事项:

  • 每次进行压力测试前,需要将表中的数据都删除掉,避免这个因素对测试结果的影响。
  • 电脑硬件资源的不同,对测试结果的影响是比较大的,只要控制变量来进行比较即可。
  • 当 MYSQL 服务端收到大量的 SQL 请求,MYSQL 可能会处理不过来,然后会给客户端返回 MySQL server has gone away 的错误信息。

👉项目常见问题👈

在写数据库连接池项目的时候,经常出现的问题就是 MySQL API 调用出错,提示 insert、delete、update 等操作执行失败,或者 connect 连接 MySQL Server 失败等等,很多人不知道遇到这个问题该怎么办?

其实开源库提供的对外调用 API 还是很全面的,MySQL API 专门提供了两个函数,能够打印出错时的信息提示,如下:

在这里插入图片描述

例如,代码执行报错:

在这里插入图片描述
无论上面截图右侧输出信息上提示 insert 错误还是其它错误,都可以在代码上通过添加 mysql_error 函数打印错误提示,一般通过查看提示就知道是什么错误了,例如权限问题,但大部分都是细节错误,字段不对、类型不对、表名不对等等。

在这里插入图片描述
重新执行代码,这里出错的话,就会打印错误信息。

mysql_errno 返回的是一个 int 整型错误码,可以在网上搜索 MySQL 错误码 xxx,就可以看到错误是什么原因了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/74418.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【力扣】 12. 整数转罗马数字 模拟

力扣 12. 整数转罗马数字 解题思路 当某个位数的某个数不为4或9时&#xff0c;高位对应的字符总是在低位对应的字符前面。只有当该数为4或9时&#xff0c;低位对应的字符在高位前面。 根据这一特性&#xff0c;我们进行分类讨论。 1.当数为4时&#xff0c;则对应的罗马数为 10 …

怎么把图片变成表格?几个步骤轻松转换

如果您需要将一张图片中的数据转换成表格&#xff0c;这里有几个简单的步骤&#xff0c;可以帮助您完成这个转换过程。以下是需要注意的事项。 首先&#xff0c;我们先了解OCR&#xff08;Optical Character Recognition&#xff0c;光学字符识别&#xff09;技术。然后合理运用…

【练】要求定义一个全局变量 char buf[] = “1234567“,创建两个线程,不考虑退出条件,打印buf

要求定义一个全局变量 char buf[] "1234567"&#xff0c;创建两个线程&#xff0c;不考虑退出条件&#xff0c;另&#xff1a; A线程循环打印buf字符串&#xff0c;B线程循环倒置buf字符串&#xff0c;即buf中本来存储1234567&#xff0c;倒置后buf中存储7654321. 不…

C++内存管理

C语言内存管理方式在C中可以继续使用&#xff0c;但是存在一定的缺陷&#xff0c;如使用malloc动态开辟自定义类型对象的空间&#xff0c;无法自动调用构造函数&#xff0c;那就必须我们去显示的调用构造函数&#xff08;一般情况下&#xff0c;构造函数不可以显示调用&#xf…

【多线程初阶】多线程案例之单例模式

文章目录 前言1. 什么是单例模式2. 饿汉模式3. 懒汉模式 --- 单线程版4. 懒汉模式 --- 多线程版5. 懒汉模式 --- 多线程改进版总结 前言 本文主要给大家讲解多线程的一个重要案例 — 单例模式. 关注收藏, 开始学习吧&#x1f9d0; 1. 什么是单例模式 单例模式是一种很经典的…

JVM的组件、自动垃圾回收的工作原理、分代垃圾回收过程、可用的垃圾回收器类型

详细画的jvm模型图 https://www.processon.com/diagraming/64c8aa11c07d99075d934311 官方网址 https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html 相关概念 年轻代是所有新对象被分配和老化的地方。当年轻代填满时&#xff0c;这会导致m…

活动目录密码更改

定期更改密码是一种健康的习惯&#xff0c;因为它有助于阻止使用被盗凭据的网络攻击&#xff0c;安全专家建议管理员应确保用户使用有效的密码过期策略更改其密码。 管理员可以通过电子邮件通知用户在密码即将过期时更改其密码&#xff0c;但在许多组织中&#xff0c;用户只能…

web流程自动化详解

今天给大家带来Selenium的相关解释操作 一、Selenium Selenium是一个用于自动化Web浏览器操作的开源工具和框架。它提供了一组API&#xff08;应用程序接口&#xff09;&#xff0c;可以让开发人员使用多种编程语言&#xff08;如Java、Python、C#等&#xff09;编写测试脚本&…

应用层协议——https

文章目录 1. HTTPS 是什么2. 什么是"加密"3. 常见的加密方式4. 数据摘要 && 数字签名5. HTTPS 的工作过程探究5.1 方案1 - 只使用对称加密5.2 方案2 - 只使用非对称加密5.3 方案3 - 双方都使用非对称加密5.4 方案4 - 非对称加密 对称加密5.5 中间人攻击5.6 …

Mac提示文件:已损坏,无法打开。你应该把它移到废纸篓

文章目录 一、电脑信息二、打开任何来源设置三、更改应用程序拓展属性 一、电脑信息 我的是新版的Venture 13的系统。UI改的比较多。与之前的配置还是有很大的区别的。 打开下载的软件&#xff0c;显示已经损坏&#xff0c;打不开。抛开软件本身的问题外&#xff0c;一般是Ma…

【数据结构与算法】TypeScript 实现图结构

class Grapg<T> {// 用于存储所有的顶点verteces: T[] [];// 用于存储所有的边 采用邻接表的形式adjList: Map<T, T[]> new Map();// 添加顶点addVertex(v: T) {this.verteces.push(v);// 初始化顶点的邻接表this.adjList.set(v, []);}// 添加边addEdge(v: T, w:…

文心一言 VS 讯飞星火 VS chatgpt (68)-- 算法导论6.5 7题

文心一言 VS 讯飞星火 VS chatgpt &#xff08;68&#xff09;-- 算法导论6.5 7题 七、试说明如何使用优先队列来实现一个先进先出队列&#xff0c;以及如何使用优先队列来实现栈(队列和栈的定义见 10.1 节。) 文心一言&#xff1a; 优先队列是一种数据结构&#xff0c;其中…

无人机机巢有哪些,无人机机场/机场的主要分类

随着无人机技术的飞速发展&#xff0c;无人机已经渗透到了物流、农业、救援、公共安全等多个领域。而为了使这些无人机能更加高效、灵活地运行&#xff0c;一个新的概念应运而生&#xff0c;那就是无人机机巢&#xff08;UAV Nest&#xff09;。复亚智能无人机机巢是一种供无人…

GitLab备份升级

数据备份(默认的备份目录在/var/opt/gitlab/backups/下&#xff0c;生成一个以时间节点命名的tar包。) gitlab-rake gitlab:backup:create新建repo源&#xff0c;升级新版本的gitlab vim /etc/yum.repos.d/gitlab-ce.repo [gitlab-ce] namegitlab-ce baseurlhttps://mirrors.…

【大数据】-- docker 启动 mysql 5.7,开启 binlog

1.说明 mysql binlog&#xff1a;二进制日志文件。它有两个作用&#xff0c;一是增量备份&#xff0c;即只备份新增的内容&#xff0c;可以用于恢复数据&#xff1b;二是用于主从复制等&#xff0c;即主节点维护了一个binlog日志文件&#xff0c;从节点从binlog中同步数据。 …

嵌入式pc技术的特点有哪些?

嵌入式PC技术是将计算机硬件和软件嵌入到各种设备中的一种技术&#xff0c;它具有低功耗、高效率、小型化、易于集成等优点&#xff0c;广泛应用于工业自动化、医疗设备、电力、通信、家用电器、物联网等领域&#xff0c;成为新时代工业生产和社会生活必不可少的技术之一。 嵌入…

使用idea实现git操作大全(在项目开发中遇到的实际情况

使用idea实现git操作大全&#xff08;在项目开发中遇到的实际情况&#xff09; 1.安装git插件2.在开发中切记拉一个自己的分支 1.安装git插件 2.在开发中切记拉一个自己的分支 选中需要拉的分支&#xff0c;右键该分支&#xff0c;选中new breach from “分支”&#xff0c;点…

接口测试如何在json中引用mock变量

在测试接口的时候&#xff0c;有的接口需要测试随机传入大量数据&#xff0c;查看数据库是否正常&#xff0c;但是大量的随机数据全靠自己手写会很慢&#xff0c;而且是通过json传递的数据。 这里我们就可以使用mock生成随机变量&#xff0c;然后在json中引用mock变量 首先看…

Reinforcement Learning with Code 【Code 1. Tabular Q-learning】

Reinforcement Learning with Code 【Code 1. Tabular Q-learning】 This note records how the author begin to learn RL. Both theoretical understanding and code practice are presented. Many material are referenced such as ZhaoShiyu’s Mathematical Foundation o…

leetcode 135. 分发糖果

2023.8.1 这道题只从前向后遍历会出各种问题&#xff0c;所以最后决定向前向后各遍历一次。 先定义一个饼干数组biscuits&#xff0c;记录每个孩子的饼干数量&#xff0c;初始化每个孩子饼干数量为1。 然后从前向后遍历、从后向前遍历&#xff0c;使其满足“相邻两孩子评分更高…