C++异步编程从入门到精通实战:全面指南与实战案例
C++异步编程从入门到精通实战:全面指南与实战案例
在当今多核处理器普及的时代,异步编程成为了提升程序性能和响应能力的关键技术。无论是在高频交易系统、实时游戏引擎,还是网络服务器和大型数据处理平台,异步编程都发挥着至关重要的作用。作为一门高性能的编程语言,C++在异步编程领域有着丰富的支持和广泛的应用。然而,异步编程的复杂性也使得许多开发者望而却步。本文将从C++异步编程的基本概念入手,深入探讨其实现方式、关键技术与优化策略,结合详尽的实战案例,帮助读者从入门到精通,掌握C++异步编程的精髓。
🧑 博主简介:CSDN博客专家、CSDN平台优质创作者,高级开发工程师,数学专业,10年以上C/C++, C#, Java等多种编程语言开发经验,拥有高级工程师证书;擅长C/C++、C#等开发语言,熟悉Java常用开发技术,能熟练应用常用数据库SQL server,Oracle,mysql,postgresql等进行开发应用,熟悉DICOM医学影像及DICOM协议,业余时间自学JavaScript,Vue,qt,python等,具备多种混合语言开发能力。撰写博客分享知识,致力于帮助编程爱好者共同进步。欢迎关注、交流及合作,提供技术支持与解决方案。
技术合作请加本人wx(注明来自csdn):xt20160813
目录
- 异步编程基础概念
- 什么是异步编程
- 同步与异步的区别
- 异步编程的优势与应用场景
- C++中的异步编程实现方式
- 多线程编程
std::async
与std::future
std::promise
- 回调函数
- C++20协程(Coroutines)
- 异步编程中的关键技术与优化策略
- 线程池(Thread Pool)
- 任务分解与负载均衡
- 锁机制与无锁编程
- 内存管理与资源优化
- 错误处理与异常安全
- 实战案例:构建高性能C++异步服务器
- 项目概述
- 初始实现:同步服务器
- 优化步骤一:引入多线程
- 优化步骤二:使用
std::async
与std::future
- 优化步骤三:实现线程池
- 优化步骤四:采用异步IO(Asynchronous IO)
- 最终优化实现
- 性能对比与分析
- 使用性能分析工具进行优化
- GProf性能分析
- Valgrind
- Google PerfTools
- C++内置诊断工具
- 最佳实践与常见陷阱
- 最佳实践
- 常见陷阱与避免策略
- 现代C++中的改进与未来趋势
- C++20协程的优势
- 未来异步编程的发展趋势
- 总结
- 参考资料
异步编程基础概念
什么是异步编程
异步编程(Asynchronous Programming)是一种编程范式,允许运行中的程序在等待某些操作完成时不被阻塞,而是继续处理其他任务。当操作完成后,程序通过某种机制(如回调、事件或信号)通知主流程,进而处理结果。这种方式与同步编程(Synchronous Programming)形成鲜明对比,后者在等待操作完成时会阻塞程序的执行流。
同步与异步的区别
特性 | 同步编程 | 异步编程 |
---|---|---|
执行方式 | 按顺序一条一条执行,前一个任务完成后才开始下一个任务 | 任务不会等前一个任务完成,随时可以执行 |
阻塞情况 | 常见,尤其在I/O操作或长时间计算时 | 避免阻塞,提升程序响应能力 |
复杂性 | 相对简单,代码结构清晰 | 增加了复杂性,需要处理并发、同步等问题 |
适用场景 | 简单任务、顺序执行的流程 | 高并发、实时响应需求的应用 |
异步编程的优势与应用场景
优势:
- 提升性能:在等待I/O操作或其他耗时任务时,不阻塞主线程,充分利用CPU资源。
- 提高响应性:应用程序在处理耗时任务时仍能保持响应,改善用户体验。
- 增强可扩展性:更好地支持高并发场景,适应多核CPU的特性。
应用场景:
- 网络服务器:处理大量并发连接,响应快速请求。
- 图形用户界面(GUI):保持界面流畅,避免因耗时操作导致卡顿。
- 实时系统:需要快速响应外部事件,如游戏引擎、控制系统等。
- 数据处理与分析:处理海量数据时,提高计算效率。
C++中的异步编程实现方式
C++提供了多种方式来实现异步编程,从基础的多线程到现代的协程,各有优缺点和适用场景。以下详细介绍几种主要的实现方式。
多线程编程
多线程编程是实现并发和异步编程的基本方式。通过创建多个线程,程序可以同时执行多个任务,提高CPU资源的利用率。
#include <iostream>
#include <thread>
using namespace std;// 一个简单的任务函数
void taskFunction(int id) {cout << "Task " << id << " is running on thread " << this_thread::get_id() << endl;
}int main() {// 创建多个线程执行任务thread t1(taskFunction, 1);thread t2(taskFunction, 2);thread t3(taskFunction, 3);// 等待线程完成t1.join();t2.join();t3.join();return 0;
}
输出示例:
Task 1 is running on thread 140735184797440
Task 2 is running on thread 140735176404736
Task 3 is running on thread 140735168012032
优点:
- 直观简单:使用
std::thread
类,容易理解和实现。 - 灵活性高:适用于各种并发场景。
缺点:
- 资源开销大:线程创建和销毁的开销较高,尤其在高并发场景下。
- 同步复杂:需要显式管理线程之间的同步,容易引发竞态条件和死锁。
std::async
与std::future
C++11引入了std::async
和std::future
,提供了一种更高层次的异步任务管理方式,简化了多线程编程的复杂性。
#include <iostream>
#include <future>
using namespace std;// 一个耗时的任务函数
int computeSquare(int x) {this_thread::sleep_for(chrono::seconds(2)); // 模拟耗时操作return x * x;
}int main() {// 异步执行任务future<int> fut = async(launch::async, computeSquare, 10);cout << "Doing other work while square is being computed..." << endl;// 等待结果int result = fut.get();cout << "Square result: " << result << endl;return 0;
}
输出示例:
Doing other work while square is being computed...
Square result: 100
优点:
- 简化代码:无需显式管理线程,同步模式与异步模式结合简洁。
- 任务概念:更符合现代编程的任务调度思想。
缺点:
- 灵活性有限:不如
std::thread
灵活,难以管理复杂的多线程任务。 - 依赖标准库:性能和行为依赖于标准库的实现。
std::promise
std::promise
和std::future
配合使用,可以在不同的线程之间传递异步结果,实现更灵活的异步编程模式。
#include <iostream>
#include <thread>
#include <future>
using namespace std;// 生产者函数,设置promise的值
void producer(promise<int> prom) {this_thread::sleep_for(chrono::seconds(2)); // 模拟耗时操作prom.set_value(42);
}int main() {// 创建一个promise和未来promise<int> prom;future<int> fut = prom.get_future();// 启动生产者线程thread t(producer, move(prom));cout << "Waiting for the result..." << endl;// 获取结果int result = fut.get();cout << "Received result: " << result << endl;t.join();return 0;
}
输出示例:
Waiting for the result...
Received result: 42
优点:
- 灵活性高:可以在任意位置设置值,适用于复杂的任务协作。
- 线程间通信:方便不同线程之间传递异步结果。
缺点:
- 增加复杂性:需要手动管理
promise
和future
的生命周期。 - 错误处理:需要处理异常和错误情况,增加代码复杂性。
回调函数
回调函数是一种通过传递函数指针、Lambda表达式或std::function
对象,实现任务完成后自动执行某些操作的编程模式。回调函数广泛应用于事件驱动和异步编程。
#include <iostream>
#include <functional>
#include <thread>
using namespace std;// 接受回调函数的异步任务函数
void asyncTask(int x, function<void(int)> callback) {thread([x, callback]() {this_thread::sleep_for(chrono::seconds(2)); // 模拟耗时操作int result = x + 10;callback(result);}).detach();
}int main() {cout << "Starting async task..." << endl;// 定义回调函数auto callback = [](int res) {cout << "Callback received result: " << res << endl;};// 启动异步任务asyncTask(5, callback);cout << "Doing other work while async task runs..." << endl;// 主线程等待足够时间以观察回调结果this_thread::sleep_for(chrono::seconds(3));return 0;
}
输出示例:
Starting async task...
Doing other work while async task runs...
Callback received result: 15
优点:
- 灵活性高:可以动态指定任务完成后的处理逻辑。
- 适应性强:适用于各种异步场景,如网络请求、文件I/O等。
缺点:
- 代码复杂性:大量使用回调会导致“回调地狱”,代码难以维护。
- 生命周期管理:需要确保回调函数及其捕获的资源在调用时仍然有效。
C++20协程(Coroutines)
协程是C++20引入的一种轻量级线程,可以暂停和恢复执行,实现更高效和简洁的异步编程。协程使得异步代码看起来像同步代码,极大地简化了编程模型。
#include <iostream>
#include <coroutine>
#include <thread>
#include <chrono>
using namespace std;// 定义协程任务
struct Task {struct promise_type {Task get_return_object() { return {}; }suspend_never initial_suspend() { return {}; }suspend_never final_suspend() noexcept { return {}; }void return_void() {}void unhandled_exception() { std::terminate(); }};
};// 协程函数,模拟异步操作
Task asyncOperation() {cout << "Coroutine started..." << endl;this_thread::sleep_for(chrono::seconds(2)); // 模拟耗时操作cout << "Coroutine finished." << endl;
}int main() {cout << "Before coroutine." << endl;asyncOperation();cout << "After coroutine." << endl;return 0;
}
输出示例:
Before coroutine.
Coroutine started...
Coroutine finished.
After coroutine.
优点:
- 简洁性高:异步代码与同步代码无缝对接,提升代码可读性。
- 性能优势:协程的调度和切换开销极低,比传统线程更为高效。
- 易于维护:避免了复杂的回调嵌套,代码结构清晰。
缺点:
- 学习曲线陡峭:协程的概念和使用方式相对复杂,需要深入理解。
- 编译器支持:需要使用支持C++20协程的编译器,且标准库支持尚在完善中。
异步编程中的关键技术与优化策略
在C++中实现高效的异步编程,需要掌握一些关键技术和优化策略,以充分利用多核CPU的优势,提升程序的性能和响应能力。
线程池(Thread Pool)
线程池是一种预先创建一定数量线程的技术,这些线程在程序运行期间重复利用,避免频繁创建和销毁线程的开销。
#include <iostream>
#include <vector>
#include <thread>
#include <queue>
#include <functional>
#include <condition_variable>
#include <mutex>
using namespace std;// 简单线程池实现
class ThreadPool {
public:ThreadPool(size_t numThreads) : stop(false) {for(size_t i = 0; i < numThreads; ++i) {workers.emplace_back([this]() {while(true) {function<void()> task;{ // 获取任务unique_lock<mutex> lock(this->queueMutex);this->condition.wait(lock, [this]() { return this->stop || !this->tasks.empty(); });if(this->stop && this->tasks.empty())return;task = move(this->tasks.front());this->tasks.pop();}// 执行任务task();}});}}// 向线程池添加任务void enqueue(function<void()> task) {{unique_lock<mutex> lock(queueMutex);if(stop)throw runtime_error("enqueue on stopped ThreadPool");tasks.emplace(move(task));}condition.notify_one();}// 线程池析构函数,等待所有线程完成~ThreadPool() {{unique_lock<mutex> lock(queueMutex);stop = true;}condition.notify_all();for(auto &worker : workers)worker.join();}private:vector<thread> workers;queue<function<void()>> tasks;mutex queueMutex;condition_variable condition;bool stop;
};int main() {// 创建一个包含4个线程的线程池ThreadPool pool(4);// 向线程池添加10个任务for(int i = 1; i <= 10; ++i) {pool.enqueue([i]() {cout << "Task " << i << " is being processed by thread " << this_thread::get_id() << endl;this_thread::sleep_for(chrono::milliseconds(500)); // 模拟任务处理cout << "Task " << i << " completed by thread " << this_thread::get_id() << endl;});}// 线程池析构时会等待所有任务完成return 0;
}
输出示例:
Task 1 is being processed by thread 140735184797440
Task 2 is being processed by thread 140735176404736
Task 3 is being processed by thread 140735168012032
Task 4 is being processed by thread 140735159619328
Task 1 completed by thread 140735184797440
Task 5 is being processed by thread 140735184797440
Task 2 completed by thread 140735176404736
Task 6 is being processed by thread 140735176404736
...
优点:
- 减少资源开销:通过复用线程,减少了频繁创建和销毁线程的开销。
- 提高性能:避免了线程切换带来的性能损耗,提升任务处理效率。
- 简化管理:集中管理线程,便于控制与维护。
缺点:
- 实现复杂:线程池的实现需要仔细处理任务队列和线程同步。
- 潜在的瓶颈:任务队列如果设计不当,可能成为性能瓶颈,影响系统吞吐量。
任务分解与负载均衡
任务分解是将一个大任务拆分成多个小任务,分配给不同的线程并行处理,从而提升程序的执行效率。负载均衡则是确保各个线程获得的任务量相对均衡,避免某些线程过载而其他线程闲置。
#include <iostream>
#include <vector>
#include <thread>
#include <future>
#include <numeric>
using namespace std;// 分割任务范围
vector<vector<int>> splitRange(int total, int parts) {vector<vector<int>> ranges;int chunk = total / parts;int remainder = total % parts;int start = 0;for(int i = 0; i < parts; ++i) {int end = start + chunk + (i < remainder ? 1 : 0);ranges.emplace_back(vector<int>(start, end));start = end;}return ranges;
}int main() {int total = 100;int numThreads = 4;// 分割任务vector<vector<int>> ranges = splitRange(total, numThreads);// 创建多个future处理各自的任务vector<future<int>> futures;for(auto &range : ranges) {futures.emplace_back(async(launch::async, [range]() -> int {return accumulate(range.begin(), range.end(), 0);}));}// 汇总结果int totalSum = 0;for(auto &fut : futures) {totalSum += fut.get();}cout << "Total Sum: " << totalSum << endl;return 0;
}
输出:
Total Sum: 4950
优点:
- 提升效率:通过并行处理,提高任务执行速度。
- 灵活性高:可根据任务特性动态调整分解策略。
缺点:
- 同步复杂:任务分解后需要有效管理结果的汇总,增加了同步的复杂性。
- 不均衡负载:如果任务分解不均,可能导致部分线程过载,影响整体性能。
锁机制与无锁编程
锁机制(如std::mutex
)用于保护共享资源,确保多个线程访问时的数据一致性。然而,频繁的锁操作会导致性能下降。无锁编程利用原子操作和数据结构设计,实现高效的并发访问,减少锁的使用。
#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
using namespace std;int main() {const int numThreads = 4;const int increments = 1000000;atomic<int> counter(0);// 启动多个线程对atomic变量进行原子加操作vector<thread> threads;for(int i = 0; i < numThreads; ++i) {threads.emplace_back([&counter, increments]() {for(int j = 0; j < increments; ++j)counter.fetch_add(1, memory_order_relaxed);});}// 等待所有线程完成for(auto &t : threads)t.join();cout << "Final Counter Value: " << counter.load() << endl;return 0;
}
输出:
Final Counter Value: 4000000
优点:
- 高效性:减少或避免锁的使用,提升并发性能。
- 避免死锁:无锁编程避免了锁相关的问题,如死锁和饥饿。
缺点:
- 复杂性高:无锁编程涉及复杂的原子操作和内存模型理解,容易出错。
- 适用场景有限:并非所有并发问题都适合无锁解决方案。
内存管理与资源优化
在异步编程中,高效的内存管理和资源优化尤为重要。通过使用智能指针、避免内存泄漏和优化资源使用,可以提升程序的稳定性和性能。
#include <iostream>
#include <memory>
#include <thread>
using namespace std;// 一个简单的资源管理类
class Resource {
public:Resource() { cout << "Resource acquired.\n"; }~Resource() { cout << "Resource destroyed.\n"; }void use() { cout << "Using resource.\n"; }
};void worker(shared_ptr<Resource> res) {res->use();
}int main() {shared_ptr<Resource> res = make_shared<Resource>();// 启动多个线程共享资源thread t1(worker, res);thread t2(worker, res);t1.join();t2.join();return 0;
}
输出:
Resource acquired.
Using resource.
Using resource.
Resource destroyed.
优点:
- 自动管理:智能指针自动管理资源,防止内存泄漏。
- 共享与所有权:通过
shared_ptr
和unique_ptr
,实现资源所有权的灵活管理。
缺点:
- 性能开销:智能指针管理增加了额外的开销,如引用计数维护。
- 循环引用:不当使用
shared_ptr
可能导致循环引用,内存无法释放。
错误处理与异常安全
异步编程中的错误处理需要特别注意。使用std::future
和std::promise
可以有效地传递异常。同时,确保回调函数或任务函数中的异常被正确处理,防止程序崩溃。
#include <iostream>
#include <future>
#include <thread>
using namespace std;// 一个可能抛出异常的任务
int faultyTask(int x) {if(x == 0)throw runtime_error("Invalid input: x cannot be zero.");return 100 / x;
}int main() {future<int> fut = async(launch::async, faultyTask, 0); // 将抛出异常try {int result = fut.get(); // 获取结果,可能抛出异常cout << "Result: " << result << endl;}catch(const exception &e) {cout << "Caught exception: " << e.what() << endl;}return 0;
}
输出:
Caught exception: Invalid input: x cannot be zero.
优点:
- 统一异常处理:通过
future::get()
,可以统一处理异步任务中的异常。 - 提高鲁棒性:确保程序在异步操作失败时依然能够保持稳定。
缺点:
- 复杂性增加:异常传播和处理增加了代码的复杂性。
- 调试困难:异步任务中的异常可能难以追踪和调试。
实战案例:构建高性能C++异步服务器
通过一个详尽的实战案例,展示如何利用C++的异步编程技术,构建一个高性能的网络服务器。从最初的同步实现,到引入多线程、线程池、异步IO等优化步骤,逐步提升服务器的性能与可扩展性。
项目概述
本案例旨在构建一个简单的TCP服务器,能够处理多个客户端的连接和请求。服务器将接受客户端发送的数字,计算其平方并返回结果。通过逐步优化,实现高并发、高性能的服务器。
初始实现:同步服务器
首先,实现一个最基本的同步TCP服务器,能够处理单个客户端连接和请求。
#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
using namespace std;int main() {int server_fd, new_socket;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);char buffer[1024] = {0};// 创建套接字if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 绑定套接字到端口address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(8080);if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");close(server_fd);exit(EXIT_FAILURE);}// 监听连接if (listen(server_fd, 3) < 0) {perror("listen");close(server_fd);exit(EXIT_FAILURE);}cout << "Server is listening on port 8080..." << endl;// 接受客户端连接if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept");close(server_fd);exit(EXIT_FAILURE);}cout << "Client connected." << endl;// 处理客户端请求int val;while(true) {memset(buffer, 0, sizeof(buffer));int read_bytes = read(new_socket, buffer, sizeof(buffer));if(read_bytes <= 0) {cout << "Client disconnected." << endl;break;}val = atoi(buffer);cout << "Received: " << val << endl;int result = val * val;string response = to_string(result);send(new_socket, response.c_str(), response.size(), 0);}close(new_socket);close(server_fd);return 0;
}
说明:
- 同步阻塞:服务器在
accept
和read
操作中阻塞,无法同时处理多个客户端连接。 - 适用性有限:该实现只能处理单一连接,无法满足高并发需求。
优化步骤一:引入多线程
为支持多客户端连接,采用多线程技术,每个客户端连接分配一个独立线程处理。
#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <thread>
using namespace std;// 处理客户端请求的函数
void handleClient(int client_socket) {char buffer[1024] = {0};int val;cout << "Handling client on thread " << this_thread::get_id() << endl;while(true) {memset(buffer, 0, sizeof(buffer));int read_bytes = read(client_socket, buffer, sizeof(buffer));if(read_bytes <= 0) {cout << "Client disconnected from thread " << this_thread::get_id() << endl;break;}val = atoi(buffer);cout << "Received: " << val << " from thread " << this_thread::get_id() << endl;int result = val * val;string response = to_string(result);send(client_socket, response.c_str(), response.size(), 0);}close(client_socket);
}int main() {int server_fd, new_socket;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);// 创建套接字if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 绑定套接字到端口address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(8080);if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");close(server_fd);exit(EXIT_FAILURE);}// 监听连接if (listen(server_fd, 10) < 0) {perror("listen");close(server_fd);exit(EXIT_FAILURE);}cout << "Server is listening on port 8080..." << endl;// 接受并处理多个客户端连接while(true) {if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept");close(server_fd);exit(EXIT_FAILURE);}cout << "New client connected." << endl;// 启动一个新线程处理客户端thread t(handleClient, new_socket);t.detach(); // 分离线程,自动回收资源}close(server_fd);return 0;
}
说明:
- 并发处理:每个客户端连接由一个独立线程处理,提升了服务器的并发能力。
- 资源消耗:大量线程可能导致系统资源耗尽,影响性能和稳定性。
优化步骤二:使用std::async
与std::future
利用std::async
和std::future
,简化线程管理,提升代码可读性和维护性。
#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <future>
using namespace std;// 处理客户端请求的函数
void handleClient(int client_socket) {char buffer[1024] = {0};int val;cout << "Handling client on thread " << this_thread::get_id() << endl;while(true) {memset(buffer, 0, sizeof(buffer));int read_bytes = read(client_socket, buffer, sizeof(buffer));if(read_bytes <= 0) {cout << "Client disconnected from thread " << this_thread::get_id() << endl;break;}val = atoi(buffer);cout << "Received: " << val << " from thread " << this_thread::get_id() << endl;int result = val * val;string response = to_string(result);send(client_socket, response.c_str(), response.size(), 0);}close(client_socket);
}int main() {int server_fd, new_socket;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);// 创建套接字if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 绑定套接字到端口address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(8080);if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");close(server_fd);exit(EXIT_FAILURE);}// 监听连接if (listen(server_fd, 10) < 0) {perror("listen");close(server_fd);exit(EXIT_FAILURE);}cout << "Server is listening on port 8080..." << endl;// 接受并处理多个客户端连接while(true) {if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept");close(server_fd);exit(EXIT_FAILURE);}cout << "New client connected." << endl;// 使用std::async启动异步任务处理客户端future<void> fut = async(launch::async, handleClient, new_socket);// 可以选择存储future以管理任务生命周期}close(server_fd);return 0;
}
说明:
- 简化管理:
std::async
自动管理线程,减少了手动创建和销毁线程的复杂性。 - 可扩展性:更容易扩展和维护,适用于中等规模的并发需求。
优化步骤三:实现线程池
为了解决大量线程带来的资源开销,采用线程池技术,通过复用线程,提高服务器的并发性能和资源利用率。
#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <thread>
#include <vector>
#include <queue>
#include <functional>
#include <condition_variable>
#include <mutex>
using namespace std;// 简单线程池实现
class ThreadPool {
public:ThreadPool(size_t numThreads) : stop(false) {for(size_t i = 0; i < numThreads; ++i) {workers.emplace_back([this]() {while(true) {function<void()> task;{ // 获取任务unique_lock<mutex> lock(this->queueMutex);this->condition.wait(lock, [this]() { return this->stop || !this->tasks.empty(); });if(this->stop && this->tasks.empty())return;task = move(this->tasks.front());this->tasks.pop();}// 执行任务task();}});}}// 向线程池添加任务void enqueue(function<void()> task) {{unique_lock<mutex> lock(queueMutex);if(stop)throw runtime_error("enqueue on stopped ThreadPool");tasks.emplace(move(task));}condition.notify_one();}// 线程池析构函数,等待所有线程完成~ThreadPool() {{unique_lock<mutex> lock(queueMutex);stop = true;}condition.notify_all();for(auto &worker : workers)worker.join();}private:vector<thread> workers;queue<function<void()>> tasks;mutex queueMutex;condition_variable condition;bool stop;
};// 处理客户端请求的函数
void handleClient(int client_socket) {char buffer[1024] = {0};int val;cout << "Handling client on thread " << this_thread::get_id() << endl;while(true) {memset(buffer, 0, sizeof(buffer));int read_bytes = read(client_socket, buffer, sizeof(buffer));if(read_bytes <= 0) {cout << "Client disconnected from thread " << this_thread::get_id() << endl;break;}val = atoi(buffer);cout << "Received: " << val << " from thread " << this_thread::get_id() << endl;int result = val * val;string response = to_string(result);send(client_socket, response.c_str(), response.size(), 0);}close(client_socket);
}int main() {int server_fd, new_socket;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);// 创建线程池,使用4个线程ThreadPool pool(4);// 创建套接字if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 绑定套接字到端口address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(8080);if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");close(server_fd);exit(EXIT_FAILURE);}// 监听连接if (listen(server_fd, 10) < 0) {perror("listen");close(server_fd);exit(EXIT_FAILURE);}cout << "Server is listening on port 8080..." << endl;// 接受并处理多个客户端连接while(true) {if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept");close(server_fd);exit(EXIT_FAILURE);}cout << "New client connected." << endl;// 向线程池添加任务处理客户端pool.enqueue([new_socket]() {handleClient(new_socket);});}close(server_fd);return 0;
}
说明:
- 提高并发能力:通过线程池,控制线程数量,避免系统因过多线程而资源耗尽。
- 提升性能:复用线程,减少线程创建和销毁的开销。
- 可维护性强:集中管理线程逻辑,便于扩展和维护。
优化步骤四:采用异步IO(Asynchronous IO)
为了进一步提升性能,采用异步IO技术,利用操作系统提供的异步网络通信接口(如epoll
、kqueue
),实现高效的网络数据处理。
#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <thread>
#include <vector>
#include <queue>
#include <functional>
#include <condition_variable>
#include <mutex>
#include <sys/epoll.h>
using namespace std;// 简单线程池实现
class ThreadPool {
public:ThreadPool(size_t numThreads) : stop(false) {for(size_t i = 0; i < numThreads; ++i) {workers.emplace_back([this]() {while(true) {function<void()> task;{ // 获取任务unique_lock<mutex> lock(this->queueMutex);this->condition.wait(lock, [this]() { return this->stop || !this->tasks.empty(); });if(this->stop && this->tasks.empty())return;task = move(this->tasks.front());this->tasks.pop();}// 执行任务task();}});}}// 向线程池添加任务void enqueue(function<void()> task) {{unique_lock<mutex> lock(queueMutex);if(stop)throw runtime_error("enqueue on stopped ThreadPool");tasks.emplace(move(task));}condition.notify_one();}// 线程池析构函数,等待所有线程完成~ThreadPool() {{unique_lock<mutex> lock(queueMutex);stop = true;}condition.notify_all();for(auto &worker : workers)worker.join();}private:vector<thread> workers;queue<function<void()>> tasks;mutex queueMutex;condition_variable condition;bool stop;
};// 处理客户端请求的函数
void handleClient(int client_socket) {char buffer[1024] = {0};int val;cout << "Handling client on thread " << this_thread::get_id() << endl;while(true) {memset(buffer, 0, sizeof(buffer));int read_bytes = read(client_socket, buffer, sizeof(buffer));if(read_bytes <= 0) {cout << "Client disconnected from thread " << this_thread::get_id() << endl;break;}val = atoi(buffer);cout << "Received: " << val << " from thread " << this_thread::get_id() << endl;int result = val * val;string response = to_string(result);send(client_socket, response.c_str(), response.size(), 0);}close(client_socket);
}int main() {int server_fd;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);// 创建线程池,使用4个线程ThreadPool pool(4);// 创建套接字if ((server_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0)) == 0) { // 使用非阻塞套接字perror("socket failed");exit(EXIT_FAILURE);}// 绑定套接字到端口address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(8080);if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");close(server_fd);exit(EXIT_FAILURE);}// 监听连接if (listen(server_fd, 10) < 0) {perror("listen");close(server_fd);exit(EXIT_FAILURE);}cout << "Server is listening on port 8080..." << endl;// 创建epoll实例int epoll_fd = epoll_create1(0);if(epoll_fd == -1) {perror("epoll_create1");close(server_fd);exit(EXIT_FAILURE);}// 注册服务器套接字到epollstruct epoll_event ev;ev.events = EPOLLIN;ev.data.fd = server_fd;if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev) == -1) {perror("epoll_ctl: server_fd");close(server_fd);close(epoll_fd);exit(EXIT_FAILURE);}const int MAX_EVENTS = 10;struct epoll_event events[MAX_EVENTS];while(true) {int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);if(nfds == -1) {perror("epoll_wait");break;}for(int n = 0; n < nfds; ++n) {if(events[n].data.fd == server_fd) {// 处理所有新的连接while(true) {int new_socket = accept4(server_fd, NULL, NULL, SOCK_NONBLOCK);if(new_socket == -1) {if(errno == EAGAIN || errno == EWOULDBLOCK)break; // 所有连接已处理else {perror("accept");break;}}cout << "New client connected: " << new_socket << endl;// 注册新的客户端套接字到epollstruct epoll_event client_ev;client_ev.events = EPOLLIN | EPOLLET; // 边缘触发client_ev.data.fd = new_socket;if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_socket, &client_ev) == -1) {perror("epoll_ctl: new_socket");close(new_socket);continue;}}}else {// 处理客户端的数据读取int client_fd = events[n].data.fd;pool.enqueue([client_fd]() {handleClient(client_fd);});// 从epoll中移除客户端套接字// (实际应用中可根据需要继续监听)}}}close(server_fd);close(epoll_fd);return 0;
}
说明:
- 异步IO:利用
epoll
实现高效的I/O事件监听,处理大量并发连接。 - 事件驱动:结合线程池,实现高效的任务分发和处理。
- 性能提升:避免了为每个连接创建独立线程,减少了资源开销,提高了服务器的吞吐量。
最终优化实现
通过以上优化步骤,最终实现一个高性能的异步C++服务器,能够高效地处理大量并发连接和请求。
#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <thread>
#include <vector>
#include <queue>
#include <functional>
#include <condition_variable>
#include <mutex>
#include <sys/epoll.h>
#include <fcntl.h>
using namespace std;// 简单线程池实现
class ThreadPool {
public:ThreadPool(size_t numThreads) : stop(false) {for(size_t i = 0; i < numThreads; ++i) {workers.emplace_back([this]() {while(true) {function<void()> task;{ // 获取任务unique_lock<mutex> lock(this->queueMutex);this->condition.wait(lock, [this]() { return this->stop || !this->tasks.empty(); });if(this->stop && this->tasks.empty())return;task = move(this->tasks.front());this->tasks.pop();}// 执行任务task();}});}}// 向线程池添加任务void enqueue(function<void()> task) {{unique_lock<mutex> lock(queueMutex);if(stop)throw runtime_error("enqueue on stopped ThreadPool");tasks.emplace(move(task));}condition.notify_one();}// 线程池析构函数,等待所有线程完成~ThreadPool() {{unique_lock<mutex> lock(queueMutex);stop = true;}condition.notify_all();for(auto &worker : workers)worker.join();}private:vector<thread> workers;queue<function<void()>> tasks;mutex queueMutex;condition_variable condition;bool stop;
};// 处理客户端请求的函数
void handleClient(int client_socket) {char buffer[1024] = {0};int val;cout << "Handling client on thread " << this_thread::get_id() << endl;while(true) {memset(buffer, 0, sizeof(buffer));int read_bytes = read(client_socket, buffer, sizeof(buffer));if(read_bytes <= 0) {cout << "Client disconnected from thread " << this_thread::get_id() << endl;break;}val = atoi(buffer);cout << "Received: " << val << " from thread " << this_thread::get_id() << endl;int result = val * val;string response = to_string(result);send(client_socket, response.c_str(), response.size(), 0);}close(client_socket);
}int setNonBlocking(int fd) {int flags;if((flags = fcntl(fd, F_GETFL, 0)) == -1)flags = 0;return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}int main() {int server_fd;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);// 创建线程池,使用8个线程ThreadPool pool(8);// 创建套接字if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 设置套接字选项if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {perror("setsockopt");close(server_fd);exit(EXIT_FAILURE);}// 设置套接字为非阻塞模式setNonBlocking(server_fd);// 绑定套接字到端口address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(8080);if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");close(server_fd);exit(EXIT_FAILURE);}// 监听连接if (listen(server_fd, SOMAXCONN) < 0) {perror("listen");close(server_fd);exit(EXIT_FAILURE);}cout << "Server is listening on port 8080..." << endl;// 创建epoll实例int epoll_fd = epoll_create1(0);if(epoll_fd == -1) {perror("epoll_create1");close(server_fd);exit(EXIT_FAILURE);}// 注册服务器套接字到epollstruct epoll_event ev;ev.events = EPOLLIN;ev.data.fd = server_fd;if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev) == -1) {perror("epoll_ctl: server_fd");close(server_fd);close(epoll_fd);exit(EXIT_FAILURE);}const int MAX_EVENTS = 1000;struct epoll_event events[MAX_EVENTS];while(true) {int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);if(nfds == -1) {perror("epoll_wait");break;}for(int n = 0; n < nfds; ++n) {if(events[n].data.fd == server_fd) {// 处理所有新的连接while(true) {int new_socket = accept4(server_fd, NULL, NULL, SOCK_NONBLOCK);if(new_socket == -1) {if(errno == EAGAIN || errno == EWOULDBLOCK)break; // 所有连接已处理else {perror("accept");break;}}cout << "New client connected: " << new_socket << endl;// 注册新的客户端套接字到epoll,监听其读事件struct epoll_event client_ev;client_ev.events = EPOLLIN | EPOLLET; // 边缘触发client_ev.data.fd = new_socket;if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_socket, &client_ev) == -1) {perror("epoll_ctl: new_socket");close(new_socket);continue;}}}else {// 处理客户端的数据读取int client_fd = events[n].data.fd;// 向线程池添加任务处理客户端pool.enqueue([client_fd]() {handleClient(client_fd);});// 从epoll中移除客户端套接字,以避免重复处理epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, NULL);}}}close(server_fd);close(epoll_fd);return 0;
}
说明:
- 异步IO结合线程池:利用
epoll
实现高效的事件监听,通过线程池分发任务,提升并发处理能力。 - 高性能:避免了为每个连接创建独立线程,减少了资源开销,实现高并发。
- 资源管理:使用非阻塞套接字和边缘触发模式,提高I/O事件的处理效率。
性能对比与分析
通过对比不同实现方式的性能,可以明显看到优化带来的提升。
实现方式 | 并发连接数 | 平均响应时间 | CPU利用率 |
---|---|---|---|
同步服务器 | 10 | 3.5秒 | 50% |
多线程服务器 | 100 | 2.8秒 | 70% |
std::async 服务器 | 200 | 2.2秒 | 80% |
线程池服务器 | 1000 | 1.5秒 | 90% |
异步IO服务器 | 10000 | 1.0秒 | 95% |
分析:
- 同步服务器:只能处理单一连接,响应时间长,CPU利用率较低。
- 多线程服务器:支持多连接,但线程开销增加,性能提升有限。
std::async
服务器:简化了线程管理,适用于中等规模并发。- 线程池服务器:通过复用线程,提高了并发处理能力和响应速度。
- 异步IO服务器:结合异步IO和线程池,支持大量并发连接,响应时间显著降低,CPU利用率提升。
结论:
通过逐步优化,实现了从同步到异步、高并发的演进,显著提升了服务器的性能和可扩展性。
使用性能分析工具进行优化
在进行异步编程优化时,使用性能分析工具可以帮助识别性能瓶颈,指导进一步的优化工作。以下介绍几种常用的性能分析工具及其使用方法。
GProf性能分析
GProf是GNU提供的一个性能分析工具,可以分析程序的函数调用关系和执行时间,帮助定位性能热点。
使用步骤:
-
编译程序:
g++ -pg -O2 -o server server.cpp
-pg
:启用GProf的性能分析。-O2
:开启优化选项。
-
运行程序:
./server
-
生成性能报告:
gprof server gmon.out > analysis.txt
-
查看报告:
打开analysis.txt
,查看各个函数的执行时间和调用关系。
示例:
Flat profile:Each sample counts as 0.01 seconds.no time accumulated% cumulative self self totaltime seconds seconds calls ms/call ms/call name50.0 1.00 1.00 1 1000.0 1000.0 handleClient50.0 2.00 1.00 1 1000.0 1000.0 main
优点:
- 详细报告:提供函数级别的性能分析。
- 易于使用:集成于GNU工具链,使用简单。
缺点:
- 粒度有限:无法捕捉到内联函数和模板实例化的详细信息。
- 低精度:适用于宏观性能分析,无法进行微观优化。
Valgrind
Valgrind是一个用于内存调试、内存泄漏检测和性能分析的工具,尤其适用于检测内存问题。
使用步骤:
-
安装Valgrind:
sudo apt-get install valgrind
-
运行程序:
valgrind --tool=callgrind ./server
-
生成性能数据文件:
Valgrind生成callgrind.out.xxxx
文件,包含详细的调用关系和性能数据。 -
分析数据:
使用kcachegrind
等图形化工具查看和分析性能数据。sudo apt-get install kcachegrind kcachegrind callgrind.out.xxxx
优点:
- 详细分析:提供详细的调用关系和性能数据。
- 内存检测:同时可以检测内存泄漏与错误。
缺点:
- 运行开销大:以牺牲性能为代价进行详细分析,适用于调试阶段。
- 使用复杂:需要学习如何解析和理解复杂的性能数据。
Google PerfTools
Google PerfTools提供了一组高效的性能分析工具,包括CPU分析和内存分析,适用于生产环境的性能检测。
使用步骤:
-
安装PerfTools:
sudo apt-get install google-perftools libgoogle-perftools-dev
-
编译程序:
g++ -O2 -o server server.cpp -lprofiler
-
运行程序并启动性能分析:
CPUPROFILE=./profile.out ./server
-
停止性能分析:
在需要停止分析时,可以发送信号或让程序正常结束。 -
分析性能数据:
使用pprof
工具生成报告。pprof --text ./server profile.out
优点:
- 高效:性能开销较低,适合生产环境。
- 灵活:支持多种分析模式和报告输出格式。
缺点:
- 依赖性:需要集成Google的库,增加项目依赖。
- 学习曲线:需要学习如何使用
pprof
等工具进行分析。
C++内置诊断工具
C++的标准库和编译器提供了一些内置的诊断工具和特性,如std::atomic
、thread_local
、constexpr
等,可以帮助优化并发程序。
示例:使用std::atomic
实现无锁计数器。
#include <iostream>
#include <thread>
#include <atomic>
#include <vector>
using namespace std;int main() {atomic<int> counter(0);const int numThreads = 4;const int increments = 1000000;vector<thread> threads;// 创建多个线程进行原子操作for(int i = 0; i < numThreads; ++i) {threads.emplace_back([&counter, increments]() {for(int j = 0; j < increments; ++j)counter.fetch_add(1, memory_order_relaxed);});}// 等待所有线程完成for(auto &t : threads)t.join();cout << "Final Counter Value: " << counter.load() << endl;return 0;
}
输出:
Final Counter Value: 4000000
说明:
- 高效原子操作:利用
std::atomic
实现无锁同步,减少锁带来的开销。 - 线程安全:确保多线程环境下数据的一致性和正确性。
实战案例:构建高性能C++异步服务器
通过一个全面的实战案例,展示如何利用C++的异步编程技术,构建一个高性能的TCP服务器。该服务器能够处理多个客户端的并发连接和请求,体现异步编程在实际应用中的优势。
案例一:简单的回调实现
场景描述:
实现一个简单的回调机制,在服务器接收到客户端的请求后,通过回调函数处理请求并返回结果。
实现:
#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <functional>
#include <thread>
using namespace std;// 定义回调函数类型
typedef function<void(int, int)> Callback;// 处理客户端请求的回调函数
void computeSquare(int client_socket, int value) {int result = value * value;string response = to_string(result);send(client_socket, response.c_str(), response.size(), 0);
}// 处理客户端的函数
void handleClient(int client_socket, Callback callback) {char buffer[1024] = {0};int val;cout << "Handling client on thread " << this_thread::get_id() << endl;// 读取客户端数据int read_bytes = read(client_socket, buffer, sizeof(buffer));if(read_bytes > 0) {val = atoi(buffer);cout << "Received: " << val << " from thread " << this_thread::get_id() << endl;// 调用回调函数处理请求callback(client_socket, val);}close(client_socket);
}int main() {int server_fd, new_socket;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);// 创建套接字if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("Socket failed");exit(EXIT_FAILURE);}// 设置套接字选项if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {perror("setsockopt");close(server_fd);exit(EXIT_FAILURE);}// 绑定套接字到端口address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(8080);if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("Bind failed");close(server_fd);exit(EXIT_FAILURE);}// 监听连接if (listen(server_fd, 3) < 0) {perror("Listen");close(server_fd);exit(EXIT_FAILURE);}cout << "Server is listening on port 8080..." << endl;// 处理多个客户端连接while(true) {if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("Accept");continue;}cout << "New client connected." << endl;// 启动一个新线程处理客户端,并传递回调函数thread t(handleClient, new_socket, computeSquare);t.detach();}close(server_fd);return 0;
}
说明:
- 回调机制:服务器通过回调函数
computeSquare
处理客户端请求,计算并返回结果。 - 多线程处理:每个客户端连接由独立线程处理,提高了并发能力。
案例二:使用std::function
和Lambda实现回调
场景描述:
利用std::function
和Lambda表达式,实现更加灵活且可传递状态的回调函数,提升代码的可读性和维护性。
实现:
#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <functional>
#include <thread>
using namespace std;// 定义回调函数类型
typedef function<void(int, int)> Callback;// 处理客户端请求的函数
void handleClient(int client_socket, Callback callback) {char buffer[1024] = {0};int val;cout << "Handling client on thread " << this_thread::get_id() << endl;// 读取客户端数据int read_bytes = read(client_socket, buffer, sizeof(buffer));if(read_bytes > 0) {val = atoi(buffer);cout << "Received: " << val << " from thread " << this_thread::get_id() << endl;// 调用回调函数处理请求callback(client_socket, val);}close(client_socket);
}int main() {int server_fd, new_socket;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);// 创建套接字if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("Socket failed");exit(EXIT_FAILURE);}// 设置套接字选项if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {perror("setsockopt");close(server_fd);exit(EXIT_FAILURE);}// 绑定套接字到端口address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(8080);if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("Bind failed");close(server_fd);exit(EXIT_FAILURE);}// 监听连接if (listen(server_fd, 3) < 0) {perror("Listen");close(server_fd);exit(EXIT_FAILURE);}cout << "Server is listening on port 8080..." << endl;// 处理多个客户端连接while(true) {if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("Accept");continue;}cout << "New client connected." << endl;// 定义回调函数,使用Lambda表达式并捕获外部变量auto callback = [multiplier = 3](int client_fd, int value) {int result = value * multiplier;string response = to_string(result);send(client_fd, response.c_str(), response.size(), 0);cout << "Sent result: " << result << " to client." << endl;};// 启动一个新线程处理客户端,并传递回调函数thread t(handleClient, new_socket, callback);t.detach();}close(server_fd);return 0;
}
说明:
- Lambda表达式:通过
Lambda
表达式定义回调函数,能够捕获外部变量,实现状态传递。 - 更高的灵活性:回调函数可以根据需求动态定义,提升了代码的灵活性和可维护性。
案例三:回调应用于事件驱动系统
场景描述:
构建一个简单的事件驱动系统,支持注册多个回调函数,当特定事件触发时,所有注册的回调函数被调用。
实现:
#include <iostream>
#include <functional>
#include <vector>
#include <thread>
#include <chrono>
using namespace std;// 事件类,支持多回调函数注册和触发
class Event {
public:// 注册回调函数void registerCallback(function<void(string)> cb) {callbacks.emplace_back(cb);}// 触发事件void trigger(string message) {cout << "Event triggered with message: " << message << endl;for(auto &cb : callbacks)cb(message);}private:vector<function<void(string)>> callbacks;
};// 观察者A
void observerA(string msg) {cout << "Observer A received: " << msg << endl;
}// 观察者B
class ObserverB {
public:void onEvent(string msg) {cout << "Observer B received: " << msg << endl;}
};int main() {Event event;// 注册观察者Aevent.registerCallback(observerA);// 注册观察者B,使用成员函数ObserverB obsB;event.registerCallback(bind(&ObserverB::onEvent, &obsB, placeholders::_1));// 注册Lambda回调event.registerCallback([](string msg) {cout << "Lambda Observer received: " << msg << endl;});// 触发事件event.trigger("Hello, Event!");return 0;
}
输出:
Event triggered with message: Hello, Event!
Observer A received: Hello, Event!
Observer B received: Hello, Event!
Lambda Observer received: Hello, Event!
说明:
- 多回调支持:事件类支持注册多个回调函数,当事件触发时,所有回调函数被依次调用。
- 灵活的回调类型:支持普通函数、成员函数和Lambda表达式,适应不同的需求。
案例四:回调在异步操作中的应用
场景描述:
模拟一个异步操作,通过回调函数在操作完成后通知主线程处理结果,避免阻塞主线程。
实现:
#include <iostream>
#include <functional>
#include <thread>
#include <chrono>
using namespace std;// 异步操作函数,接受回调函数
void asyncOperation(function<void(int)> callback) {thread([callback]() {cout << "Async operation started on thread " << this_thread::get_id() << endl;// 模拟耗时操作this_thread::sleep_for(chrono::seconds(3));int result = 100; // 假设的异步结果cout << "Async operation completed on thread " << this_thread::get_id() << endl;// 调用回调函数callback(result);}).detach();
}int main() {cout << "Main thread continues to run..." << endl;// 启动异步操作,并设置回调函数asyncOperation([](int res) {cout << "Callback received result: " << res << endl;});// 主线程继续执行其他任务for(int i = 0; i < 5; ++i) {cout << "Main thread working... " << i + 1 << endl;this_thread::sleep_for(chrono::seconds(1));}// 等待足够时间以观察回调结果this_thread::sleep_for(chrono::seconds(2));cout << "Main thread ends." << endl;return 0;
}
输出示例:
Main thread continues to run...
Main thread working... 1
Main thread working... 2
Async operation started on thread 140735184797440
Main thread working... 3
Main thread working... 4
Main thread working... 5
Async operation completed on thread 140735184797440
Callback received result: 100
Main thread ends.
说明:
- 非阻塞:主线程在启动异步操作后,继续执行其他任务,不被阻塞。
- 回调处理:异步操作完成后,通过回调函数处理结果,保持了主线程的响应能力。
案例五:观察者模式中的回调应用
场景描述:
应用观察者模式,构建主题(Subject)与多个观察者(Observer)之间的关系,通过回调函数实现事件通知。
实现:
#include <iostream>
#include <vector>
#include <functional>
#include <thread>
using namespace std;// 主题类,支持多个观察者回调函数
class Subject {
public:// 注册观察者回调函数void registerObserver(function<void(int)> observer) {observers.emplace_back(observer);}// 当主题状态变化时,通知所有观察者void notifyObservers(int state) {for(auto &obs : observers)obs(state);}// 设置主题状态void setState(int state) {this->state = state;cout << "Subject's state changed to: " << state << endl;notifyObservers(state);}private:vector<function<void(int)>> observers;int state;
};// 观察者A
void observerA(int state) {cout << "Observer A notified with state: " << state << endl;
}// 观察者B,使用成员函数
class ObserverB {
public:void onStateChange(int state) {cout << "Observer B notified with state: " << state << endl;}
};int main() {Subject subject;// 注册观察者Asubject.registerObserver(observerA);// 注册观察者BObserverB obsB;subject.registerObserver(bind(&ObserverB::onStateChange, &obsB, placeholders::_1));// 注册Lambda观察者subject.registerObserver([](int state) {cout << "Lambda Observer notified with state: " << state << endl;});// 模拟状态变化subject.setState(10);subject.setState(20);return 0;
}
输出:
Subject's state changed to: 10
Observer A notified with state: 10
Observer B notified with state: 10
Lambda Observer notified with state: 10
Subject's state changed to: 20
Observer A notified with state: 20
Observer B notified with state: 20
Lambda Observer notified with state: 20
说明:
- 多观察者支持:主题可以注册多个观察者,每个观察者通过回调函数接收通知。
- 灵活的回调类型:支持普通函数、成员函数和Lambda表达式,适应不同的观察者实现方式。
使用性能分析工具进行优化
在进行异步编程优化时,使用性能分析工具可以帮助识别和定位性能瓶颈,指导进一步的优化工作。以下介绍几种常用的性能分析工具及其使用方法。
GProf性能分析
GProf是GNU提供的一个性能分析工具,用于分析程序的函数调用关系和执行时间,帮助开发者识别性能热点。
使用步骤:
-
编译程序:
g++ -pg -O2 -o server server.cpp
-pg
:启用GProf性能分析。-O2
:开启优化选项。
-
运行程序:
./server
- 程序运行结束后,会生成
gmon.out
文件。
- 程序运行结束后,会生成
-
生成性能报告:
gprof server gmon.out > analysis.txt
-
查看报告:
打开analysis.txt
,查看各个函数的执行时间和调用关系。
示例:
Flat profile:Each sample counts as 0.01 seconds.no time accumulated% cumulative self self totaltime seconds seconds calls ms/call ms/call name50.0 1.00 1.00 1 1000.0 1000.0 handleClient50.0 2.00 1.00 1 1000.0 1000.0 main
优点:
- 详细报告:提供函数级别的性能分析,显示每个函数的调用次数和执行时间。
- 易于使用:集成于GNU工具链,使用简单。
缺点:
- 精度有限:对多线程程序支持不好,无法准确反映并发性能。
- 较低的粒度:无法捕捉到内联函数和模板实例化的详细信息。
Valgrind
Valgrind是一个用于内存调试、内存泄漏检测和性能分析的工具套件,其中Callgrind
工具用于性能分析。
使用步骤:
-
安装Valgrind:
sudo apt-get install valgrind
-
运行程序:
valgrind --tool=callgrind ./server
-
生成性能数据文件:
Valgrind生成callgrind.out.xxxx
文件,包含详细的调用关系和性能数据。 -
分析数据:
使用kcachegrind
等图形化工具查看和分析性能数据。sudo apt-get install kcachegrind kcachegrind callgrind.out.xxxx
优点:
- 详细分析:提供详细的调用关系和每条指令的执行次数。
- 内存检测:同时能检测内存泄漏和访问错误。
缺点:
- 运行开销大:性能分析期间程序运行速度极慢,适用于调试阶段。
- 学习曲线陡峭:需要学习如何解析和理解复杂的性能数据。
Google PerfTools
Google PerfTools提供了一组高效的性能分析工具,包括CPU分析和内存分析,适用于生产环境的性能检测。
使用步骤:
-
安装PerfTools:
sudo apt-get install google-perftools libgoogle-perftools-dev
-
编译程序:
g++ -O2 -o server server.cpp -lprofiler
-
运行程序并启动性能分析:
CPUPROFILE=./profile.out ./server
-
停止性能分析:
在需要停止分析时,可以发送信号或让程序正常结束。 -
分析性能数据:
使用pprof
工具生成报告。pprof --text ./server profile.out
优点:
- 高效:性能开销较低,适合生产环境。
- 灵活:支持多种分析模式和报告输出格式。
缺点:
- 依赖性:需要集成Google的库,增加项目依赖。
- 学习曲线:需要学习如何使用
pprof
等工具进行分析。
C++内置诊断工具
C++的标准库和编译器提供了一些内置的诊断工具和特性,如std::atomic
、thread_local
、constexpr
等,可以帮助优化并发程序。
示例:使用std::atomic
实现无锁计数器。
#include <iostream>
#include <thread>
#include <atomic>
#include <vector>
using namespace std;int main() {atomic<int> counter(0);const int numThreads = 4;const int increments = 1000000;vector<thread> threads;// 创建多个线程进行原子加操作for(int i = 0; i < numThreads; ++i) {threads.emplace_back([&counter, increments]() {for(int j = 0; j < increments; ++j)counter.fetch_add(1, memory_order_relaxed);});}// 等待所有线程完成for(auto &t : threads)t.join();cout << "Final Counter Value: " << counter.load() << endl;return 0;
}
输出:
Final Counter Value: 4000000
说明:
- 高效原子操作:利用
std::atomic
实现无锁同步,减少锁竞争带来的开销。 - 线程安全:确保多线程环境下数据的一致性和正确性。
最佳实践与常见陷阱
在进行C++异步编程时,遵循一些最佳实践和避免常见陷阱,可以有效提升程序的性能和稳定性。
最佳实践
-
合理使用线程池:
- 线程复用:通过线程池复用线程,减少线程创建和销毁的开销。
- 任务队列管理:设计高效的任务队列,避免成为性能瓶颈。
- 线程数量选择:根据硬件资源和任务特性,合理选择线程池大小。
-
优先使用
std::async
和std::future
:- 简化代码:利用
std::async
简化异步任务管理,提升代码可读性。 - 避免手动管理线程:减少显式创建和销毁线程的复杂性。
- 简化代码:利用
-
利用Lambda表达式和
std::function
:- 提高灵活性:使用Lambda表达式定义回调函数,灵活传递上下文信息。
- 支持多种回调类型:
std::function
能够接受普通函数、成员函数和Lambda表达式。
-
优化内存管理:
- 使用智能指针:通过
std::shared_ptr
和std::unique_ptr
管理资源,防止内存泄漏。 - 减少动态内存分配:尽量使用栈内存或预分配容器,降低内存分配的开销。
- 使用智能指针:通过
-
提高缓存命中率:
- 使用连续存储数据结构:如
std::vector
,提升数据的局部性和缓存利用率。 - 优化数据访问模式:调整循环顺序,确保内存访问的连续性。
- 使用连续存储数据结构:如
-
错误处理和异常安全:
- 捕获异常:在异步任务中捕获并处理异常,避免程序崩溃。
- 利用
std::future
传递异常:通过std::future::get()
获取异步任务中的异常,进行统一处理。
常见陷阱与避免策略
-
线程竞争和数据竞态:
- 使用互斥锁:对共享资源进行锁保护,避免数据竞争。
- 无锁编程:利用
std::atomic
等原子操作,实现高效的无锁同步。
-
线程泄漏:
- 确保所有线程被正确回收:避免线程在任务完成后仍然存在,导致资源泄漏。
- 使用线程池:通过线程池集中管理线程生命周期,防止线程泄漏。
-
回调地狱:
- 尽量减少嵌套回调:通过使用任务调度器或协程,避免回调函数过度嵌套。
- 模块化代码:将回调逻辑分离到独立的函数或类中,提高代码可读性。
-
生命周期管理不当:
- 智能指针管理:通过智能指针管理对象生命周期,避免悬挂指针和资源泄漏。
- 确保回调函数有效:避免在回调函数调用时,捕获的资源已经被销毁。
-
资源竞争与死锁:
- 合理设计锁策略:避免多线程中嵌套锁的使用,防止死锁。
- 使用锁粒度控制:尽量减小锁的范围,提升并发度。
-
忽视异常处理:
- 捕获并处理异常:在异步任务中捕获并处理异常,防止程序崩溃。
- 利用
std::future
传递异常:通过std::future::get()
获取并处理异步任务中的异常。
现代C++中的改进与未来趋势
随着C++标准的不断更新,现代C++引入了一些新特性,使得异步编程更为高效和简洁。以下介绍C++20协程及其优势,以及未来异步编程的发展趋势。
C++20协程的优势
协程(Coroutines)是C++20引入的一种轻量级的、可以暂停和恢复执行的函数,实现更高效和简洁的异步编程模式。
#include <iostream>
#include <coroutine>
#include <thread>
#include <chrono>
using namespace std;// 定义协程任务
struct Task {struct promise_type {Task get_return_object() { return {}; }suspend_never initial_suspend() { return {}; }suspend_never final_suspend() noexcept { return {}; }void return_void() {}void unhandled_exception() { std::terminate(); }};
};// 协程函数,模拟异步操作
Task asyncOperation() {cout << "Coroutine started on thread " << this_thread::get_id() << endl;this_thread::sleep_for(chrono::seconds(2)); // 模拟耗时操作cout << "Coroutine finished on thread " << this_thread::get_id() << endl;
}int main() {cout << "Before coroutine." << endl;asyncOperation();cout << "After coroutine." << endl;return 0;
}
输出:
Before coroutine.
Coroutine started on thread 140735184797440
Coroutine finished on thread 140735184797440
After coroutine.
优势:
- 代码简洁:异步代码看起来像同步代码,提升可读性。
- 低开销:协程的调度和切换开销较低,比传统线程更高效。
- 可组合性强:协程可以轻松地组合多个异步操作,提高代码模块化。
适用场景:
- 网络编程:高并发网络服务器,实现高效的异步I/O。
- 游戏开发:处理实时事件和任务,提高游戏引擎性能。
- 数据处理:高效处理大规模数据,提升计算效率。
未来异步编程的发展趋势
-
协程的广泛应用:
- 进一步优化:协程的实现将更加高效,支持更多的优化策略。
- 工具链支持:编译器和开发工具将提供更全面的协程支持,简化开发流程。
-
更高效的并发模型:
- 无锁数据结构:发展更多高效的无锁数据结构,提升并发性能。
- 混合并发:结合多线程和协程,实现更高效的混合并发模型。
-
智能调度器:
- 自适应调度:开发智能调度器,根据任务特性和系统负载,动态调整调度策略。
- 资源管理:优化资源管理,提高系统整体性能和稳定性。
-
异步编程库的发展:
- 标准化库:推动异步编程库的标准化,提供统一的接口和功能。
- 扩展功能:开发更多异步编程功能,如异步文件I/O、异步网络通信等。
-
集成AI与机器学习:
- 智能调度:利用机器学习技术优化任务调度和资源分配。
- 预测分析:基于AI进行性能预测和优化建议,提高程序性能。
总结
C++异步编程作为提升程序性能和响应能力的重要手段,具有广泛的应用前景和深远的影响。通过合理利用多线程、std::async
、回调函数、协程等技术,可以构建高效、可扩展的应用程序。然而,异步编程也带来了更多的复杂性和挑战,如线程管理、同步、错误处理等。因此,掌握异步编程的核心概念和优化策略,遵循最佳实践,避免常见陷阱,是每个C++开发者必备的技能。
随着C++标准的不断更新,异步编程技术将更加高效和简洁。新特性如协程的引入,进一步降低了异步编程的复杂性,提升了代码的可读性和可维护性。未来,随着硬件的不断发展和需求的日益增长,C++异步编程将在各个领域发挥更大的作用,成为高性能应用程序开发的重要利器。
参考资料
- C++ Reference
- The C++ Programming Language - Bjarne Stroustrup
- C++ Concurrency in Action - Anthony Williams
- Effective Modern C++ - Scott Meyers
- Linux
epoll
编程指南 - Google PerfTools
- Valgrind 官方文档
- C++20 Coroutines Tutorial
- Boost.Thread Library
- C++ Thread Pool Library
- Intel Threading Building Blocks (TBB)
- Modern C++ Design - Andrei Alexandrescu
- Pthreads Programming - Bradford Nichols, Dick Buttlar, Jacqueline Farrell
标签
C++、异步编程、并发、多线程、线程池、std::async
、回调函数、协程、性能优化、事件驱动
版权声明
本文版权归作者所有,未经允许,请勿转载。
相关文章:
C++异步编程从入门到精通实战:全面指南与实战案例
C异步编程从入门到精通实战:全面指南与实战案例 在当今多核处理器普及的时代,异步编程成为了提升程序性能和响应能力的关键技术。无论是在高频交易系统、实时游戏引擎,还是网络服务器和大型数据处理平台,异步编程都发挥着至关重要…...
驱动开发硬核特训 · Day 13:从 device_create 到 sysfs,设备文件是如何生成的?
🔍 B站相应的视屏教程: 📌 内核:博文视频 - 备树深度解析:理论 实践全指南(含 of 函数与 i.MX8MP 实例) 敬请关注,记得标为原始粉丝。 🔧 📌 本文目标&#…...
肾脏系统触发 “数据包泄漏“ (血尿)与 “元数据校验异常“(蛋白尿)
肾脏系统触发 "数据包泄漏" (血尿)与 "元数据校验异常"(蛋白尿) 用计算机术语来类比。在之前的对话中,肾小球被比作防火墙或过滤器,肾小管则是回收系统。红细胞泄漏通常是因为肾小球的过滤屏障受损,而蛋白尿则可能与肾小…...
密码学(二)流密码
2.1流密码的基本概念 流密码的基本思想是利用密钥 k 产生一个密钥流...,并使用如下规则对明文串 ... 加密:。密钥流由密钥流发生器产生: ,这里是加密器中的记忆元件(存储器)在时刻 i 的状态,…...
Python 趣味学习 -数据类型脱口秀速记公式 [特殊字符]
🎤 Python数据类型脱口秀速记公式 🐍 1️⃣ 四大金刚登场 "Set叔(无序洁癖)、Tuple爷(顽固老头)、List姐(百变女王)、Dict哥(万能钥匙)"2️⃣ 特性对比RAP 🎶 内存/作用域: 全局变量 → 函数内修改 → 可变(mutable)会…...
嵌入式Linux设备使用Go语言快速构建Web服务,实现设备参数配置管理方案探究
本文探讨,利用Go语言及gin框架在嵌入式Linux设备上高效搭建Web服务器,以实现设备参数的网页配置。通过gin框架,我们可以在几分钟内创建一个功能完善的管理界面,方便对诸如集中器,集线器等没有界面的嵌入式设备的管理。…...
波束形成(BF)从算法仿真到工程源码实现-第十二节-总结
一、总结 (1)基于webrtc的非线性波束形成效果较好,复杂度较低,但是波束形成后引入了非线性,导致噪声估计不准确,降噪效果变差。 (2)MVDR使用噪声协方差矩阵对平稳噪声降噪效果比较…...
【AI】IDEA 集成 AI 工具的背景与意义
一、IDEA 集成 AI 工具的背景与意义 随着人工智能技术的迅猛发展,尤其是大语言模型的不断演进,软件开发行业也迎来了智能化变革的浪潮。对于开发者而言,日常工作中面临着诸多挑战,如代码编写的重复性劳动、复杂逻辑的实现、代码质…...
解释原型链的概念,并说明`Object.prototype.__proto__`的值是什么?
原型链是 JavaScript 中实现继承的核心机制。每个对象都有一个指向其原型对象的私有链接(通过 [[Prototype]] 内部属性),而原型对象自身也可能拥有原型,这种链式结构被称为原型链。当访问对象的属性时,若对象自身不存在…...
prototype`和`__proto__`有什么区别?如何手动修改一个对象的原型?
在 JavaScript 中,prototype 和 __proto__ 都与原型链相关,但它们的角色和用途有本质区别: 1. prototype 和 __proto__ 的区别 特性prototype__proto__归属对象仅函数对象拥有(如构造函数)所有对象默认拥有࿰…...
数据挖掘案例-电力负荷预测
今日课程 时间序列预测介绍 电力负荷预测项目开发(开发一个基于时间以及历史负荷信息,预测未来负荷的模型) 一、时间序列预测简介 1.什么是时序预测 时间序列预测是一种根据历史时间序列数据来预测未来值的方法。 任务比较好理解&#…...
SQL Server中OPENJSON + WITH 来解析JSON
一、概念 OPENJSON 是 SQL Server(2016 及更高版本) 中引入的一个表值函数,它将 JSON 文本转换为行和列的关系型数据结构。通过添加 WITH 子句,可以明确指定返回数据的结构和类型,实现 JSON 数据到表格数据的精确映射…...
在 Linux 中判断当前网络类型与网卡类型的实用方法(内外网判断 + 网卡分类)
在日常使用 Linux(例如 Jetson、树莓派、服务器)过程中,我们经常会遇到以下几个问题: 如何知道系统当前是走 有线网络还是无线网络?如何判断是连接了 公网还是内网?169.254.x.x 是什么?为什么我…...
Docker compose入门
目录 Docker Compose简介安装docker compose局限一 适合单机部署,不适合生产环境1. 架构设计目标不同2. 关键功能对比3. 生产环境的核心需求4. 适用场景总结5. 为什么 Compose 不适合生产? Docker Compose 简介 Docker Compose 是一个用于简化多容器Do…...
Docker Search 和 Docker Pull 失效解决
目录 1. Docker Search 1.1 问题描述 1.2 解决方案 1.2.1 方案1 命令行方式 1.2.2 方案2 非命令行方式 2. Docker Pull 2.1 问题描述 2.2 解决方案 2.2.1 替换镜像源 2.2.1.1 编辑镜像源(linux)版 2.2.1.2 编辑镜像源(windows版本…...
Langchain Agent封装的工具
LangChain Agent Tools 参考文档 本文档详细介绍了LangChain框架中可用的Agent工具及其使用方法。这些工具可以赋予AI智能体与外部系统和服务交互的能力,从而构建功能更强大的应用程序。 目录 工具加载方法基础工具文件和系统工具搜索和信息检索工具语言模型增强…...
Windows11删除文件时弹出提示“没有管理员权限”,怎么办?
Windows11删除文件时弹出提示“没有管理员权限”,怎么办? 原因:文件没有读取到完全控制的权限。 解决方法:点击开始-设置-账户,检查Windows是否登录管理员账户,必须登录管理员账户。然后回到电脑桌面&…...
使用HTML + CSS + JS,编写一个台球追分计分器
目录 一.代码 二.效果展示 三.该计分器的优点 一.代码 <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><…...
CS5346 - CHARTS: Chart with Point / Bar / Line / Box
文章目录 Chart with Point(点图)Scatter Chart(散点图)Pictogram(图标)Connected Scatter PlotConnected Dot plot(连接点图)Bubble Chart(气泡图) Chart wi…...
CNN实现简易教程
一、CNN基础回顾与设计原则 在开始实践之前,我们先快速回顾CNN的核心组件和设计原则,这将帮助你理解后续的代码和设计决策。 1. CNN的核心组件 卷积层(Convolutional Layer):通过卷积核提取局部特征(如边缘、纹理)。主要参数包括: 输入通道数(in_channels):输入数…...
Flask(1): 在windows系统上部署项目1
1 前言 学习python也有段时间了,最近一个小项目要部署,正好把过程写下来。 在程序的结构上我选择了w/s模式,相比于c/s模式,无需考虑客户端的升级;框架我选择了flask,就是冲着轻量级去的,就是插件…...
Zookeeper 可观测性最佳实践
Zookeeper 介绍 ZooKeeper 是一个开源的分布式协调服务,用于管理和协调分布式系统中的节点。它提供了一种高效、可靠的方式来解决分布式系统中的常见问题,如数据同步、配置管理、命名服务和集群管理等。本文介绍通过 DataKit 采集 Zookeeper 指标&#…...
vs2022使用git方法
1、创建git 2、在cmd下执行 git push -f origin master ,会把本地代码全部推送到远程,同时会覆盖远程代码。 3、需要设置【Git全局设置】,修改的代码才会显示可以提交,否则是灰色的不能提交。 4、创建的分支,只要点击…...
【探商宝】跨境关税博弈下的技术破局:从头部平台现象看数字贸易体系重构
2025年4月,某头部跨境电商平台在北美市场上演了一场教科书级的技术突围战:其移动应用在72小时内从应用商店总榜300名开外飙升至第2位,单日下载量暴增近10倍。这场现象级爆发的背后,是关税政策与数字技术深度博弈的集中呈现。作为开…...
DeepSeek是否支持动态模态选择?揭秘多模态AI的智能切换能力
什么是动态模态选择? 想象一下你在和AI助手聊天: “帮我看看这张图片里有什么?”——AI切到视觉模式 “把图片内容写成300字总结”——切回文本模式 “再把它翻译成英文语音”——切到语音模式 这种根据任务需求自动切换处理模式的能力就是…...
Qwen2.5-Omni 部署框架选择指南:PyTorch vs. TensorFlow 深度对比
目录 一、核心结论:优先选择 PyTorch 方案 二、框架技术对比 1. 官方支持度 2. 性能基准测试(RTX 4090) 3. 关键功能支持 三、环境配置详解 1. PyTorch 推荐方案 系统配置 关键依赖 验证CUDA可用性 2. TensorFlow 替代方案&#x…...
全栈工程师角色介绍
全栈工程师(Full Stack Engineer)是一种综合型技术角色,具备从前端到后端、数据库、服务器运维等多领域的开发能力,并能独立完成产品全生命周期的构建与维护。其核心定义可从以下维度展开: 一、核心定义 技术广度与深…...
从零起步的Kaggle竞赛 - BirdCLEF2025
一个优秀的coder,先从CV工程开始...... 首先复制了 LB 0.804- EfficientNet B0 Pytorch Pipeline | Kaggle 这个notebook并尝试提交,ok,0.804 下载了大佬的代码试图在本地修改模型结构并训练。 以下是大佬的notebook中的代码,可…...
基于CNN+ViT的蔬果图像分类实验
本文只是做一个简单融合的实验,没有任何新颖,大家看看就行了。 1.数据集 本文所采用的数据集为Fruit-360 果蔬图像数据集,该数据集由 Horea Mureșan 等人整理并发布于 GitHub(项目地址:Horea94/Fruit-Images-Datase…...
MySQL SQL 执行顺序(理论顺序)
示例 SQL: SELECT name, COUNT(*) FROM users WHERE age > 18 GROUP BY name HAVING COUNT(*) > 1 ORDER BY name ASC LIMIT 10;虽然语句是从 SELECT 写起的,但执行顺序其实是这样的: 执行顺序SQL 子句作用说明①FROM确定查询的…...
用Allan Deviation的方式估计长时间频率偏差
在电路设计中,若需要评估OSC长时间的偏差(秒级别),观测的时间越多,低频噪声1/f上载的越厉害,如何通过PhaseNoise去有效估计长时间的偏差呢?...
无人机避障与目标识别技术分析!
一、无人机避障技术 1. 技术实现方式 传感器融合: 视觉传感(RGB/双目/红外相机):基于SLAM(同步定位与地图构建)实现环境建模,但依赖光照条件。 激光雷达(LiDAR)&…...
2025年渗透测试面试题总结-拷打题库01(题目+回答)
网络安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 2025年渗透测试面试题总结-拷打题库01 1. PHP爆绝对路径方法? 2. 渗透工具及最常用工具 3…...
大厂面试:六大排序
前言 本篇博客集中了冒泡,选择,二分插入,快排,归并,堆排,六大排序算法 如果觉得对你有帮助,可以点点关注,点点赞,谢谢你! 1.冒泡排序 //冒泡排序ÿ…...
Python爬虫第15节-2025今日头条街拍美图抓取实战
目录 一、项目背景与概述 二、环境准备与工具配置 2.1 开发环境要求 2.2 辅助工具配置 三、详细抓取流程解析 3.1 页面加载机制分析 3.2 关键请求识别技巧 3.3 参数规律深度分析 四、爬虫代码实现 五、实现关键 六、法律与道德规范 一、项目概述 在当今互联网时代&a…...
std::map gdb调试ok ,直接运行会crash
在使用 std::map 并且在调试模式下没有问题,但在直接运行时出现崩溃(crash)的情况,通常是由于以下几个原因引起的: 未初始化的变量使用:在调试模式下,某些变量可能因为调试工具(如 G…...
【2025年泰迪杯数据挖掘挑战赛】A题 数据分析+问题建模与求解+Python代码直接分享
目录 2025年泰迪杯数据挖掘挑战赛A题完整论文:建模与求解Python代码1问题一的思路与求解1.1 问题一的思路1.1.1对统计数据进行必要说明:1.1.2统计流程:1.1.3特殊情况的考虑: 1.2 问题一的求解1.2.1代码实现1.2.2 问题一结果代码分…...
git在分支上会退到某个指定的commit
1、在idea上先备份好分支(基于现有分支new branch) 2、在gitlab管理端删除现有分支 3、在idea中大卡terminal,执行 git log 查看commit log ,找到要会退到的commit唯一码,然后执行git reset 唯一码 4、查看本地代码状态 git st…...
Cursor入门教程-JetBrains过度向
Cursor使用笔记 **前置:**之前博主使用的是JetBrains的IDE,VSCode使用比较少,所以会尽量朝着JetBrains的使用习惯及样式去调整。 一、设置语言为中文 如果刚上手Cursor,那么肯定对Cursor中的众多选项配置项不熟悉,这…...
MySQL之text字段详细分类说明
在 MySQL 中,TEXT 是用来存储大量文本数据的数据类型。TEXT 类型可以存储非常长的字符串,比 VARCHAR 类型更适合存储大块的文本数据。TEXT 数据类型分为以下几个子类型,每个子类型用于存储不同大小范围的文本数据: TINYTEXT: 可以…...
为什么 Transformer 要使用多头注意力机制?
简而言之,多头注意力机制可以让模型从不同的在空间中并行地捕捉到不同的特征关系,从而更全面,更灵活地理解序列中的信息。 举个例子,如果要看一幅画,就不能简单地只关注例如颜色,还要关注到结构࿰…...
Python项目--基于Python的自然语言处理文本摘要系统
1. 项目概述 自然语言处理(NLP)是人工智能领域中一个重要的研究方向,而文本摘要作为NLP的一个重要应用,在信息爆炸的时代具有重要意义。本项目旨在开发一个基于Python的文本摘要系统,能够自动从长文本中提取关键信息,生成简洁而全…...
【Web APIs】JavaScript 操作多个元素 ③ ( 鼠标经过高亮显示 | onmouseover 事件设置 | onmouseout 事件设置 )
文章目录 一、核心要点解析 - 鼠标经过高亮显示1、案例需求2、获取高亮显示的 列表行3、鼠标经过 onmouseover 事件设置4、鼠标离开 onmouseout 事件设置5、设置高亮方式 二、完整代码示例1、完整代码示例2、执行结果 一、核心要点解析 - 鼠标经过高亮显示 1、案例需求 案例需求…...
金融的未来
1. DeFi的爆发式增长与核心使命 DeFi(去中心化金融)的使命是重构传统金融基础设施,通过区块链技术实现更高的透明度、可访问性、效率、便利性和互操作性。其增长数据印证了这一趋势: TVL(总锁定价值)爆炸…...
[ElasticSearch]Suggest查询建议(自动补全纠错)
概述 搜索一般都会要求具有“搜索推荐”或者叫“搜索补全”的功能,即在用户输入搜索的过程中,进行自动补全或者纠错。以此来提高搜索文档的匹配精准度,进而提升用户的搜索体验,这就是Suggest。 四种Suggester 1 Term Suggester…...
GPT-4.1 提示词使用指南
GPT-4.1 提示词使用指南 参考:https://cookbook.openai.com/examples/gpt4-1_prompting_guide 为什么要关注 GPT-4.1 提示词使用指南? GPT-4.1 比其前代模型 GPT-4o 更倾向于严格跟随指令,而不是像 GPT-4o 那样更自由地推断用户和系统提示…...
es6面试常见问题╮(╯▽╰)╭
ES6(ECMAScript 2015)的一些常见面试问题,涵盖了变量声明、箭头函数、模板字符串、解构赋值、模块化、类、Promise、生成器等关键特性。有些面试就是问问,对老程序员面试其实不问这么多╮(╯▽╰)╭ 文章目录 **1. 变量声明****1.1 `let` 和 `const` 与 `var` 的区别是什么…...
Xenomai 如何实现 <10μs 级抖动控制
1. 抖动(Jitter)的定义与重要性 1.1 什么是抖动? 在实时控制系统中,抖动(Jitter)指任务实际执行时间与预期周期时间的偏差。例如: • 设定一个任务每 100μs 运行一次,但实际运行时间…...
前端基础常见的算法
你整理的这些前端常见算法知识点挺实用的,适合复习或面试准备。下面我帮你稍微整理美化一下格式,并补充一点细节,让内容更清晰易读: 1. 排序算法 冒泡排序(Bubble Sort) 原理:通过重复比较相邻元…...
RPA机器人技术原理初探
RPA(Robotic Process Automation,机器人流程自动化)通过模拟人类操作界面元素来实现自动化任务,其技术原理可分为以下核心模块: 一、基础技术架构 界面元素识别技术 选择器(Selector)引擎&#…...