面试题答案
一键面试自定义内存池实现方法
- 固定大小内存块的内存池:
- 原理:预先分配一大块内存,将其划分成固定大小的内存块。当程序需要分配内存时,直接从内存池中获取一个空闲的内存块;释放内存时,将内存块标记为空闲,返回内存池。
- 示例代码:
#include <iostream>
#include <vector>
#include <mutex>
class MemoryPool {
public:
MemoryPool(size_t blockSize, size_t initialBlockCount)
: blockSize(blockSize), freeList(nullptr) {
// 分配一大块内存
char* pool = new char[blockSize * initialBlockCount];
// 初始化空闲链表
for (size_t i = 0; i < initialBlockCount - 1; ++i) {
char* block = pool + i * blockSize;
*(reinterpret_cast<void**>(block)) = pool + (i + 1) * blockSize;
}
char* lastBlock = pool + (initialBlockCount - 1) * blockSize;
*(reinterpret_cast<void**>(lastBlock)) = nullptr;
freeList = pool;
}
~MemoryPool() {
char* current = freeList;
while (current) {
char* next = *(reinterpret_cast<void**>(current));
delete[] current;
current = next;
}
}
void* allocate() {
if (!freeList) {
// 内存池耗尽,可以选择扩容等操作
return nullptr;
}
void* block = freeList;
freeList = *(reinterpret_cast<void**>(freeList));
return block;
}
void deallocate(void* block) {
*(reinterpret_cast<void**>(block)) = freeList;
freeList = block;
}
private:
size_t blockSize;
void* freeList;
};
- 可变大小内存块的内存池:
- 原理:使用不同大小的内存块集合来满足不同大小的内存请求。可以通过哈希表等数据结构来快速定位合适大小的内存块池。
- 示例代码(简化概念代码,实际实现更复杂):
#include <iostream>
#include <unordered_map>
#include <vector>
#include <mutex>
class VariableMemoryPool {
public:
void* allocate(size_t size) {
auto it = poolMap.find(size);
if (it != poolMap.end()) {
return it->second.allocate();
}
// 处理没有合适大小内存块池的情况,例如创建新的池
return nullptr;
}
void deallocate(void* block, size_t size) {
auto it = poolMap.find(size);
if (it != poolMap.end()) {
it->second.deallocate(block);
}
}
private:
struct FixedSizePool {
// 类似上面固定大小内存池的实现
};
std::unordered_map<size_t, FixedSizePool> poolMap;
};
多线程环境下需要考虑的问题及解决方案
- 线程安全问题:
- 问题描述:多个线程同时访问内存池进行分配和释放操作时,可能会导致数据竞争,如同时修改空闲链表等。
- 解决方案:
- 互斥锁(Mutex):在分配和释放内存的关键代码段加锁。例如,在上述固定大小内存池代码中,可以在
allocate
和deallocate
函数中添加互斥锁:
- 互斥锁(Mutex):在分配和释放内存的关键代码段加锁。例如,在上述固定大小内存池代码中,可以在
class MemoryPool {
public:
//...
void* allocate() {
std::lock_guard<std::mutex> lock(mutex_);
if (!freeList) {
return nullptr;
}
void* block = freeList;
freeList = *(reinterpret_cast<void**>(freeList));
return block;
}
void deallocate(void* block) {
std::lock_guard<std::mutex> lock(mutex_);
*(reinterpret_cast<void**>(block)) = freeList;
freeList = block;
}
private:
std::mutex mutex_;
//...
};
- **读写锁(Read - Write Lock)**:如果读操作(如获取空闲内存块信息但不修改)较多,可以使用读写锁,读操作可以并发执行,写操作(如分配和释放内存)加写锁,保证线程安全。
2. 缓存一致性问题:
- 问题描述:不同线程可能在各自的缓存中保留内存池数据的副本,导致缓存不一致。
- 解决方案:
- 内存屏障(Memory Barrier):在关键操作前后插入内存屏障指令,确保内存操作按顺序执行,避免缓存不一致问题。在C++中,可以使用
std::atomic
提供的内存序(memory order)来实现类似功能。例如,将空闲链表指针声明为std::atomic<void*>
类型,并在读写操作时指定合适的内存序:
- 内存屏障(Memory Barrier):在关键操作前后插入内存屏障指令,确保内存操作按顺序执行,避免缓存不一致问题。在C++中,可以使用
class MemoryPool {
public:
//...
void* allocate() {
std::lock_guard<std::mutex> lock(mutex_);
if (!freeList.load(std::memory_order_acquire)) {
return nullptr;
}
void* block = freeList.exchange(nullptr, std::memory_order_release);
return block;
}
void deallocate(void* block) {
std::lock_guard<std::mutex> lock(mutex_);
freeList.store(block, std::memory_order_release);
}
private:
std::mutex mutex_;
std::atomic<void*> freeList;
};
- 内存池的负载均衡:
- 问题描述:在多线程环境下,不同线程可能集中在某些内存块上进行分配和释放,导致部分内存块频繁使用,而其他内存块闲置。
- 解决方案:
- 按线程分配专属内存池:为每个线程分配一个专属的内存池,减少线程间竞争。定期合并闲置线程的内存池,以提高内存利用率。
- 随机分配策略:在分配内存时,采用随机策略从不同的内存块集合中获取内存块,避免线程集中在某些内存块上操作。