面试题答案
一键面试设计遵循RAII原则的自定义类型
- 类的定义
- 使用C++语言为例,定义一个
NetworkConnection
类来管理网络连接资源。
#include <iostream> #include <memory> // 假设这里有套接字相关的头文件 #include <sys/socket.h> #include <unistd.h> class NetworkConnection { private: int socket_fd; // 套接字描述符 // 其他可能的网络资源,如缓冲区等 char* buffer; size_t buffer_size; public: NetworkConnection() : socket_fd(-1), buffer(nullptr), buffer_size(0) { // 复杂的初始化操作 socket_fd = socket(AF_INET, SOCK_STREAM, 0); if (socket_fd == -1) { throw std::runtime_error("Socket creation failed"); } // 这里模拟多次握手和配置参数 // 例如设置套接字选项 int optval = 1; if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) { close(socket_fd); throw std::runtime_error("Setsockopt failed"); } buffer_size = 1024; buffer = new char[buffer_size]; } ~NetworkConnection() { // 正确清理资源 if (socket_fd != -1) { close(socket_fd); } if (buffer != nullptr) { delete[] buffer; } } // 禁止拷贝构造和赋值操作,因为网络连接资源通常不可拷贝 NetworkConnection(const NetworkConnection&) = delete; NetworkConnection& operator=(const NetworkConnection&) = delete; // 移动构造和移动赋值操作 NetworkConnection(NetworkConnection&& other) noexcept : socket_fd(other.socket_fd), buffer(other.buffer), buffer_size(other.buffer_size) { other.socket_fd = -1; other.buffer = nullptr; other.buffer_size = 0; } NetworkConnection& operator=(NetworkConnection&& other) noexcept { if (this != &other) { if (socket_fd != -1) { close(socket_fd); } if (buffer != nullptr) { delete[] buffer; } socket_fd = other.socket_fd; buffer = other.buffer; buffer_size = other.buffer_size; other.socket_fd = -1; other.buffer = nullptr; other.buffer_size = 0; } return *this; } };
- 使用C++语言为例,定义一个
- RAII原则的体现
- 构造函数:在构造函数中进行网络连接资源的复杂初始化,包括套接字创建、配置参数等。如果初始化过程中任何一步失败,抛出异常,对象不会被部分构造,保证资源不会泄漏。
- 析构函数:在析构函数中确保所有相关的网络资源(套接字、缓冲区等)被正确清理。当对象生命周期结束时,无论是正常结束还是因为异常,析构函数都会被调用,从而保证资源的正确释放。
- 禁止拷贝:通过删除拷贝构造函数和拷贝赋值运算符,确保每个
NetworkConnection
对象管理自己独立的网络资源,避免资源的重复释放或非法访问。 - 移动语义:提供移动构造函数和移动赋值运算符,使得对象在转移所有权时能够高效地转移资源,而不是进行昂贵的拷贝操作。
高并发场景下的性能优化
- 实现思路
- 线程池:使用线程池来处理网络请求,避免频繁创建和销毁线程带来的开销。可以使用开源的线程池库,如
boost::thread_pool
或者自己实现一个简单的线程池。 - 异步I/O:采用异步I/O操作,如使用
epoll
(在Linux系统下)或者IOCP
(在Windows系统下)来处理多个网络连接,提高I/O效率。这允许程序在等待I/O操作完成时执行其他任务,而不是阻塞线程。 - 资源复用:对于一些临时的资源,如缓冲区,可以采用对象池的方式进行复用,减少内存分配和释放的开销。
- 线程池:使用线程池来处理网络请求,避免频繁创建和销毁线程带来的开销。可以使用开源的线程池库,如
- 关键代码片段
- 线程池示例
#include <vector> #include <queue> #include <thread> #include <mutex> #include <condition_variable> #include <functional> #include <atomic> class ThreadPool { public: ThreadPool(size_t num_threads) { for (size_t i = 0; i < num_threads; ++i) { threads.emplace_back([this] { while (true) { std::function<void()> task; { std::unique_lock<std::mutex> lock(this->queue_mutex); this->condition.wait(lock, [this] { return this->stop ||!this->tasks.empty(); }); if (this->stop && this->tasks.empty()) return; task = std::move(this->tasks.front()); this->tasks.pop(); } task(); } }); } } ~ThreadPool() { { std::unique_lock<std::mutex> lock(queue_mutex); stop = true; } condition.notify_all(); for (std::thread& thread : threads) { thread.join(); } } template<class F, class... Args> auto enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> { using return_type = typename std::result_of<F(Args...)>::type; auto task = std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...)); std::future<return_type> res = task->get_future(); { std::unique_lock<std::mutex> lock(queue_mutex); if (stop) throw std::runtime_error("enqueue on stopped ThreadPool"); tasks.emplace([task]() { (*task)(); }); } condition.notify_one(); return res; } private: std::vector<std::thread> threads; std::queue<std::function<void()>> tasks; std::mutex queue_mutex; std::condition_variable condition; std::atomic<bool> stop{ false }; };
- 异步I/O示例(使用epoll)
#include <sys/epoll.h> #include <unistd.h> #include <vector> int main() { int epoll_fd = epoll_create1(0); if (epoll_fd == -1) { throw std::runtime_error("epoll_create1 failed"); } // 假设已经有一些NetworkConnection对象,将其套接字添加到epoll中 std::vector<NetworkConnection> connections; // 初始化connections... for (auto& conn : connections) { epoll_event event; event.data.fd = conn.socket_fd; event.events = EPOLLIN | EPOLLET; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn.socket_fd, &event) == -1) { close(epoll_fd); throw std::runtime_error("epoll_ctl add failed"); } } std::vector<epoll_event> ready_events(10); while (true) { int num_events = epoll_wait(epoll_fd, ready_events.data(), ready_events.size(), -1); if (num_events == -1) { if (errno == EINTR) continue; close(epoll_fd); throw std::runtime_error("epoll_wait failed"); } for (int i = 0; i < num_events; ++i) { int fd = ready_events[i].data.fd; // 处理该套接字的I/O事件 // 例如读取数据 ssize_t read_bytes = read(fd, buffer, buffer_size); if (read_bytes == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) continue; // 处理错误 } else if (read_bytes == 0) { // 连接关闭 // 从epoll中移除并关闭套接字等操作 epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, nullptr); close(fd); } else { // 处理读取到的数据 } } } close(epoll_fd); return 0; }
处理资源竞争和死锁问题
- 资源竞争
- 互斥锁:在访问共享资源(如共享的网络缓冲区、全局的连接计数等)时,使用互斥锁(
std::mutex
)来保护这些资源。例如,如果有一个全局的连接计数,在增加和减少连接计数时需要加锁。
std::mutex connection_count_mutex; int connection_count = 0; void add_connection() { std::lock_guard<std::mutex> lock(connection_count_mutex); ++connection_count; } void remove_connection() { std::lock_guard<std::mutex> lock(connection_count_mutex); --connection_count; }
- 读写锁:如果共享资源有大量的读操作和少量的写操作,可以使用读写锁(
std::shared_mutex
)。读操作可以并发执行,而写操作需要独占访问。
std::shared_mutex shared_data_mutex; std::vector<int> shared_data; void read_shared_data() { std::shared_lock<std::shared_mutex> lock(shared_data_mutex); // 读取shared_data } void write_shared_data() { std::unique_lock<std::shared_mutex> lock(shared_data_mutex); // 写入shared_data }
- 互斥锁:在访问共享资源(如共享的网络缓冲区、全局的连接计数等)时,使用互斥锁(
- 死锁
- 避免循环依赖:仔细设计资源的获取顺序,确保不会形成循环依赖。例如,如果有两个资源
A
和B
,所有线程获取资源的顺序都应该是先A
后B
,而不是有些线程先A
后B
,有些线程先B
后A
。 - 锁层次:定义一个锁层次结构,要求线程按照层次顺序获取锁。例如,可以给每个锁分配一个唯一的编号,线程在获取多个锁时,必须按照编号从小到大的顺序获取。
- 超时机制:在获取锁时设置一个超时时间,如果在超时时间内没有获取到锁,则放弃并释放已经获取的锁,然后重试或者采取其他策略。例如,使用
std::unique_lock
的try_lock_for
方法。
std::mutex mutex1, mutex2; void potentially_deadlocked_function() { std::unique_lock<std::mutex> lock1(mutex1, std::try_to_lock); if (lock1.owns_lock()) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::unique_lock<std::mutex> lock2(mutex2, std::try_to_lock_for, std::chrono::milliseconds(200)); if (lock2.owns_lock()) { // 执行需要两个锁的操作 } else { // 未获取到第二个锁,释放第一个锁 lock1.unlock(); } } }
- 避免循环依赖:仔细设计资源的获取顺序,确保不会形成循环依赖。例如,如果有两个资源