面试题答案
一键面试可能存在的竞争条件
- 内存分配竞争:多个线程同时尝试进行内存映射操作,系统的可用内存资源有限。当接近系统内存上限时,不同线程可能同时检测到还有足够内存用于映射,但在实际执行映射时却出现
ENOMEM
错误。例如,线程 A 和线程 B 都检测到剩余内存足够映射 100MB,然后同时发起映射请求,最终导致ENOMEM
。 - 映射表竞争:Linux 内核维护内存映射表,多个线程同时进行映射操作可能导致对映射表的竞争。如果一个线程正在更新映射表,而另一个线程也尝试进行映射操作,可能会干扰映射表的一致性,进而导致内存映射失败并出现
ENOMEM
错误。
错误处理策略
- 全局锁机制:
- 使用互斥锁(
pthread_mutex_t
)对内存映射操作进行保护。在进行内存映射前,每个线程先获取互斥锁,操作完成后再释放。这样可以避免多个线程同时进行内存映射操作,减少竞争条件。 - 示例代码:
- 使用互斥锁(
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
pthread_mutex_t mmap_mutex;
#define MAP_SIZE 1024 * 1024
void* map_memory() {
pthread_mutex_lock(&mmap_mutex);
int fd = open("testfile", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (fd == -1) {
perror("open");
pthread_mutex_unlock(&mmap_mutex);
return NULL;
}
if (lseek(fd, MAP_SIZE - 1, SEEK_SET) == -1) {
perror("lseek");
close(fd);
pthread_mutex_unlock(&mmap_mutex);
return NULL;
}
if (write(fd, "", 1) != 1) {
perror("write");
close(fd);
pthread_mutex_unlock(&mmap_mutex);
return NULL;
}
void* map_start = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map_start == MAP_FAILED) {
perror("mmap");
close(fd);
pthread_mutex_unlock(&mmap_mutex);
return NULL;
}
close(fd);
pthread_mutex_unlock(&mmap_mutex);
return map_start;
}
void* thread_function(void* arg) {
void* mapped_mem = map_memory();
if (mapped_mem) {
// 使用映射内存
munmap(mapped_mem, MAP_SIZE);
}
return NULL;
}
int main() {
pthread_t thread1, thread2;
pthread_mutex_init(&mmap_mutex, NULL);
if (pthread_create(&thread1, NULL, thread_function, NULL) != 0) {
perror("pthread_create");
return 1;
}
if (pthread_create(&thread2, NULL, thread_function, NULL) != 0) {
perror("pthread_create");
return 1;
}
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_mutex_destroy(&mmap_mutex);
return 0;
}
- 错误重试:
- 当出现
ENOMEM
错误时,线程可以等待一段时间后重试内存映射操作。但要注意设置合理的重试次数和等待时间,避免无限循环重试占用过多资源。 - 示例代码:
- 当出现
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#define MAP_SIZE 1024 * 1024
#define MAX_RETRIES 3
#define WAIT_TIME 1000000 // 1 秒
void* map_memory() {
int retries = 0;
while (retries < MAX_RETRIES) {
int fd = open("testfile", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (fd == -1) {
perror("open");
return NULL;
}
if (lseek(fd, MAP_SIZE - 1, SEEK_SET) == -1) {
perror("lseek");
close(fd);
return NULL;
}
if (write(fd, "", 1) != 1) {
perror("write");
close(fd);
return NULL;
}
void* map_start = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map_start != MAP_FAILED) {
close(fd);
return map_start;
}
if (errno == ENOMEM) {
close(fd);
usleep(WAIT_TIME);
retries++;
} else {
perror("mmap");
close(fd);
return NULL;
}
}
perror("mmap after retries");
return NULL;
}
int main() {
void* mapped_mem = map_memory();
if (mapped_mem) {
// 使用映射内存
munmap(mapped_mem, MAP_SIZE);
}
return 0;
}
- 资源预分配与监控:
- 在程序启动时,预先分配一定量的内存资源供多线程使用。可以通过系统调用(如
brk
或mmap
一次分配较大内存块)实现。 - 同时,使用一个监控线程定期检查系统内存使用情况。如果发现内存不足,通知其他线程减少内存使用或暂停映射操作。
- 示例代码(仅展示内存预分配部分):
- 在程序启动时,预先分配一定量的内存资源供多线程使用。可以通过系统调用(如
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#define PREALLOC_SIZE 1024 * 1024 * 10 // 10MB
int main() {
void* prealloc_mem = mmap(0, PREALLOC_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (prealloc_mem == MAP_FAILED) {
perror("mmap for preallocation");
return 1;
}
// 后续多线程使用预分配内存,这里省略多线程代码
if (munmap(prealloc_mem, PREALLOC_SIZE) == -1) {
perror("munmap preallocation");
return 1;
}
return 0;
}
通过以上策略,可以有效减少竞争条件,并在出现 ENOMEM
错误时保证程序的健壮性和数据一致性。