共享内存跨进程通信
通过共享内存通信是最快的,不过既然是共享资源,那么就必须要有同步机制。
创建共享内存有两种方式shm和mmap的方式。
- mmap是在磁盘上建立一个文件,每个进程地址空间中开辟出一块空间进行映射。
- 而对于shm而言,shm每个进程最终会映射到同一块物理内存。shm保存在物理内存,这样读写的速度要比磁盘要快,但是存储量不是特别大。
- 相对于shm来说,mmap更加简单,调用更加方便,所以这也是大家都喜欢用的原因。
- 另外mmap有一个好处是当机器重启,因为mmap把文件保存在磁盘上,这个文件还保存了操作系统同步的映像,所以mmap不会丢失,但是shmget就会丢失。
shm的创建要确保原子性的话,可以通过重命名来做。
https://segmentfault.com/a/1190000000630435
1 char* SharedMemory::CreateMapping(const std::string file_name, unsigned mapping_size, bool &is_new) { 2 char* mapping = (char*)MAP_FAILED; 3 int fd = -1; 4 fd = open(file_name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0666); // 同步O_EXCL 5 if (fd == -1) { 6 fd = open(file_name.c_str(), O_RDWR, 0666); 7 if (fd < 0) { 8 return mapping; 9 } 10 } 11 12 struct stat file_stat; 13 if(fstat(fd, &file_stat)== -1) { 14 close(fd); 15 return mapping; 16 } 17 int file_size = file_stat.st_size; 18 is_new = false; 19 if (file_size == 0) { 20 file_size = mapping_size; 21 ftruncate(fd, file_size); 22 is_new = true; 23 } 24 mapping = (char*)mmap(NULL, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 25 if (is_new) { 26 memset(mapping, 0, sizeof(char) * file_size); 27 } 28 close(fd); 29 return mapping; 30 }
这里用O_CREAT | O_EXCL来确保只创建一次文件,如果创建失败就以rw的方式来打开。
互斥量同步
跨进程的同步机制,根据APUE 15.9节提到的,可以有三种方式,带undo的信号量、记录锁、互斥量。pthread带的跨进程互斥量需要高版本支持。
1 bool SharedMemory::Init() { 2 bool is_new = false; 3 mutex_ = (pthread_mutex_t *)CreateMapping(file_name_ + ".lock", sizeof(pthread_mutex_t), is_new); 4 if (mutex_ == MAP_FAILED) { 5 return false; 6 } 7 if (is_new) { 8 InitLock(); 9 } 10 is_init_ = true; 11 return true; 12 } 13 14 void SharedMemory::InitLock() { 15 pthread_mutexattr_t attr; 16 pthread_mutexattr_init(&attr); //~necessary, or weird EINVAL error occurs when operating on the mutex 17 pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); 18 pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST); 19 pthread_mutex_init(mutex_, &attr); 20 } 21 22 void SharedMemory::Lock() { 23 if (!is_init_) { 24 return; 25 } 26 while (EOWNERDEAD == pthread_mutex_lock(mutex_)) { 27 pthread_mutex_consistent(mutex_); 28 pthread_mutex_unlock(mutex_); 29 } 30 } 31 32 void SharedMemory::Unlock() { 33 if (!is_init_) { 34 return; 35 } 36 pthread_mutex_unlock(mutex_); 37 }
pthread_mutex_consistent这个函数有版本限制。
如果持有 mutex 的线程退出,另外一个线程在 pthread_mutex_lock 的时候会返回 EOWNERDEAD。这时候你需要调用 pthread_mutex_consistent 函数来清除这种状态,否则后果自负。
https://segmentfault.com/a/1190000000630435
pthread_mutexattr_setpshared配合PTHREAD_PROCESS_SHARED可以创建跨进程的mutex,但是必需保证mutex所在的内存区域可以被每个进程访问,也就是说必需被创建在进程间共享的内存区域中,比如mmap创建的共享内存。
https://segmentfault.com/q/1010000000628904
记录锁
记录锁的功能:当一个进程正在读或修改文件的某个部分是,它可以阻止其他进程修改同一文件区。
记录锁是更常用的方式。因为它没有版本限制,进程退出时会自动释放锁。
1 void SharedMemory::InitLock(short type) { 2 if (lock_fd_ < 0) { 3 return; 4 } 5 struct flock lock; 6 lock.l_type = type; 7 lock.l_whence = SEEK_SET; 8 lock.l_start = 0; 9 lock.l_len = 0; 10 int ret = fcntl(lock_fd_, F_SETLKW, &lock); 11 //printf("InitLock %d \n", ret); 12 } 13 14 void SharedMemory::LockWrite() { 15 if (!is_init_) { 16 return; 17 } 18 19 InitLock(F_WRLCK); 20 } 21 22 void SharedMemory::LockRead() { 23 if (!is_init_) { 24 return; 25 } 26 27 InitLock(F_RDLCK); 28 } 29 30 void SharedMemory::Unlock() { 31 if (!is_init_) { 32 return; 33 } 34 InitLock(F_UNLCK); 35 }
- F_SETLK:获取(l_type为F_RDLCK或F_WRLCK)或释放由lock指向flock结构所描述的锁,如果无法获取锁时,该函数会立即返回一个EACCESS或EAGAIN错误,而不会阻塞。
- F_SETLKW:F_SETLKW和F_SETLK的区别是,无法设置锁的时候,调用线程会阻塞到该锁能够授权位置。
- F_GETLK:F_GETLK主要用来检测是否有某个已存在锁会妨碍将新锁授予调用进程,如果没有这样的锁,lock所指向的flock结构的l_type成员就会被置成F_UNLCK,否则已存在的锁的信息将会写入lock所指向的flock结构中