共享内存在进程间应用

一、共享内存

共享内存是指在内存空间中开辟出一段空间供多个进程使用,它是一种跨进程通信的重要手段。共享内存在多进程开发中应用非常多,特别是在跨进程间大数据量通信时,几乎是必备的选择。工程实践中,安卓的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{pMutex},要么如正常开发增加一个变量定义如正文中的代码所示。
共享内存中还有两个收尾的函数:

//共享内存从当前进程中分离,相当于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)。还是要好好的看一看相关的锁的应用和跨进程中的使用的问题,没有实践就没有发言权。

你可能感兴趣的:(C++,Linux,c++)