共享内存是指在内存空间中开辟出一段空间供多个进程使用,它是一种跨进程通信的重要手段。共享内存在多进程开发中应用非常多,特别是在跨进程间大数据量通信时,几乎是必备的选择。工程实践中,安卓的framework中就用到了它,当然其它很多知名的软件都有应用,有兴趣的可以自己查找看看。
Linux和Windows中共享内存的处理原理基本是相同的,但方式却有不同。本文只分析Linux下的共享内存,对Windows上的共享内存感兴趣的可以自己查找相关资料。
在Linux下,处理共享内存有两种方式:即传统的system V和Posix库两种形式。
在进程通信中,最常遇到的就是两个问题,一个数据的读写操作,另外一个就是读写的同步问题。前者是基础的功能,这里重点分析一下同步问题。在实际的工程开发,在进程的锁选择中下意识的就选择了std::mutex。这和早年学习VC中的有名Mutex的影响非常大,在后期随着技术的演进,同时,多进程编程的实际工程少之又少,这也导致了很多问题。基本上的框架如下:
struct SmData final {
bool flag = false;
std::mutex mt;
std::array buffer = {0};
};
struct MemData final {
// WorkID id;
SmData buf[2] = {0};
};
整体的测试流程是好的,但在测试时发现锁无效,两个进程中发送进程从共享内存中拿到mt锁住后,另外的接收进程也能从共享内存拿到锁。测试是好多次都是如此,查询网上得到的结果是std::mutex不可以用在进程间。查看一下其实现的源码:
/// The standard mutex type.
class mutex : private __mutex_base
{
public:
typedef __native_type* native_handle_type;
#ifdef __GTHREAD_MUTEX_INIT
constexpr
#endif
mutex() noexcept = default;
~mutex() = default;
mutex(const mutex&) = delete;
mutex& operator=(const mutex&) = delete;
......
native_handle_type
native_handle() noexcept
{ return &_M_mutex; }
};
也没有发现特别的定义。于是便换用了pthread_mutex_t,并将属性设置为PTHREAD_PROCESS_SHARED(注意,只在一个进程中设置即可,此处是在写入进程中设置,另外一个读进程只管操作即可 ),测试成功。然后重新封装了pthread_mutex_t的相关RAII类。
可仍然没有放弃对std::mutex的测试,采用了更准确的测试方式,发现标准库的std::mutex也可以,真的让人没办法,应该是前面的测试方法有问题,产生了误解。改了就改了吧,不改回去了。
POSIX的共享内存示例可以 参看下面的网址:
https://www.bo-yang.net/2016/07/27/shared-memory-ring-buffer#shm_ring_buffer
很详细,说明也非常到位,就不再赘述。这里只说明,这种方式和创建一个内存映射类似,会在应用程序的指定位置生成一个文件,而且这个文件会随着 开辟空间的增大而增大,这也是一个需要重视的问题。不过,也没有太大影响,毕竟硬盘的大小相对于内存来说不是一个量级的存在。
下面是两个反复测试的例程(含STL和Posix两种Mutex),稍微有点乱:
下面是写共享内存的:
//send
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace mem
{
template class lock_guard final {
public:
//lock_guard()=default;
lock_guard(Mutex_ *mt) : mt_(mt) { pthread_mutex_lock(mt_); }
~lock_guard() { pthread_mutex_unlock(mt_); }
public:
lock_guard(const lock_guard &) = delete;
lock_guard(lock_guard &&) = delete;
lock_guard &operator=(const lock_guard &) = delete;
lock_guard &operator=(lock_guard &&) = delete;
private:
Mutex_ *mt_;
};
}
struct LockTest
{
int d;
std::mutex mt;
//pthread_mutex_t mt;
char buf[1024];
};
struct LockArr
{
LockTest lt[6];
};
LockArr *tmp = NULL;
std::mutex* init_shm(const key_t key)
{
int shmid = shmget(key, sizeof(LockArr), 0666 | IPC_CREAT);
if (shmid == -1)
{
return NULL;
}
void* shm_addr = shmat(shmid, NULL, 0);
if (shm_addr == (void *) -1)
{
return NULL;
}
//pthread_mutexattr_t mutex_attr;
//pthread_mutexattr_init( &mutex_attr);
//pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
//pthread_mutexattr_setrobust( &mutex_attr, PTHREAD_MUTEX_ROBUST);
tmp = (LockArr *)shm_addr;
std::mutex* _mutex = &tmp->lt[0].mt;
//pthread_mutex_t* _mutex = &tmp->lt[0].mt;
//pthread_mutex_init(_mutex,&mutex_attr);
return _mutex;
}
#define PKEY 20181220
//process
std::mutex* pMutex = NULL;
void writemem(const unsigned char* data)
{
if (pMutex == NULL)
{
pMutex = init_shm(PKEY);
}
std::lock_guard l(*pMutex);
//mem::lock_guard pmt{pMutex};
//pthread_mutex_lock(pMutex);
//do something
tmp->lt[0].buf[0] = 1;
tmp->lt[0].buf[1] = 3;
tmp->lt[0].buf[2] = 6;
printf("lock successed--------------ooooooo--------------\n");
usleep(10 * 1000 * 1000);
//pthread_mutex_unlock(pMutex);
}
int main()
{
unsigned char buf[1024] = {0};
writemem(buf);
return 0;
}
下面是读共享内存:
//recv
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace mem{
template class lock_guard final {
public:
//lock_guard()=default;
lock_guard(Mutex_ *mt) : mt_(mt) { pthread_mutex_lock(mt_); }
~lock_guard() { pthread_mutex_unlock(mt_); }
public:
lock_guard(const lock_guard &) = delete;
lock_guard(lock_guard &&) = delete;
lock_guard &operator=(const lock_guard &) = delete;
lock_guard &operator=(lock_guard &&) = delete;
private:
Mutex_ *mt_;
};
}
struct LockTest
{
int d;
std::mutex mt;
//pthread_mutex_t mt;
char buf[1024];
};
struct LockArr
{
LockTest lt[6];
};
LockArr *tmp = NULL;
/*pthread_mutex_t* */ std::mutex *init_shm(const key_t key)
{
int shmid = shmget( key, sizeof(LockArr), 0666 | IPC_CREAT);
if (shmid == -1)
{
return NULL;
}
void* shm_addr = shmat(shmid, NULL, 0);
if (shm_addr == (void *) -1)
{
return NULL;
}
//pthread_mutexattr_t mutex_attr;
//pthread_mutexattr_init( &mutex_attr);
//pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
//pthread_mutexattr_setrobust( &mutex_attr, PTHREAD_MUTEX_ROBUST);
tmp = (LockArr *)shm_addr;
//pthread_mutex_t* _mutex = &tmp->lt[0].mt;
std::mutex* _mutex = &tmp->lt[0].mt;
//pthread_mutex_init(_mutex,&mutex_attr);
return _mutex;
}
#define PKEY 20181220
//process
std::mutex* pMutex = NULL;
void readmem(const unsigned char* data)
{
if (pMutex == NULL)
{
pMutex = init_shm(PKEY);
}
//lock_guard(&pBuf->mt)
//mem::lock_guard pmt(pMutex);
std::lock_guard l(*pMutex);
//pthread_mutex_lock(pMutex);
//do something
std::cout<lt[0].buf[0]<lt[0].buf[1]<lt[0].buf[2]<
目前这是用的标准库的std::mutex,如果将pthread_mutex_t放开并注释掉标准库的,也是没有问题的,但是得自己封装一个锁控制互斥体。
这里顺带说一个问题,看下面的代码:
//lock_guard(&pBuf->mt);//这行代码OK
//lock_guard(pMutex);//编译有问题
//lock_guard()=default;
上面两行代码都被注释了,第二行代码引入了下面第三行代码的出现(虽然增加第三行代码后可以 编译通过),但它们是有问题的,之所以说有问题而不说错误,是因为从形式上讲编译器认为是正确的,但实际达不到目的。编译器报得错误和警告是:
error:candidate expects 1 argument, 0 provided
warning:parentheses were disambiguated as redundant parentheses around declaration of variable named
这引出一个基础的问题,声明一个匿名类对象时,如果使用一个具体的值来定义一个类,则没问题,如果是用一个同样值的变量来定义,编译器就无法确定此处是什么 情况(是类对象定义还是函数声明),从而编译错误提出警告(如果后面级联调用对象内的变量或者函数同样没问题,编译器可以确定其为类对象定义)。解决的方法是要么直接采用新标准下的大括号即lock_guard
共享内存中还有两个收尾的函数:
//共享内存从当前进程中分离,相当于shmat函数的反操作
int shmdt(const void *shmaddr);
//删除共享内存,这个其实有很多种用法,看command的标志
int shmctl(int shm_id, int command, struct shmid_ds *buf);
更细节的可以查看相关资料。
在新的C++标准下,提供了很多新的锁,比如scoped_lock、shared_lock(对应的互斥体: timed_mutex、recursive_mutex、recursive_timed_mutex、shared_mutex、shared_timed_mutex)。还是要好好的看一看相关的锁的应用和跨进程中的使用的问题,没有实践就没有发言权。