这篇文章我们来认识一下线程间通信的一种手段—共享内存,共享内存由于其特殊性—共享内存是存在与每个进程的地址空间中的,通俗点就是这部分数据是对每个进程可见的,这样每个进程都可以在一定条件下直接操作共享内存中的数据,避免了数据的复制等耗时的操作,这也是共享内存比其他几种IPC机制(信号量、管道、消息队列等)的通信方式效率高的原因,但是共享内存也有缺点,那就是需要独立实现消息的同步机制。 本文将完成一个基础的共享内存类模板,包含在DeltaCV项目中,欢迎star,fork。
概述
在boost/Interprocess中,提供了很多关于操作系统底层的进程间通信的抽象层,它使用C++将操作系统的底层接口进行封装,并消除了不同操作系统之间各种各样的接口带来的开发困难等问题。
共享内存
先介绍一种最普通的共享内存创建方式,boost::interprocess::shared_memory_object类。
1 |
|
共享内存创建后大小为0,我们使用truncate()方法来为共享内存设定大小,单位为字节。
1 |
|
然而刚才我们创建的共享内存虽然对所有进程可见,但是现在还不能直接操作,因为每个进程都有自己独立的地址空间,我们还需要将上面的共享内存映射进当前进程的地址空间中,可以使用mapped_region()方法完成。
1 |
|
接下来就是开始向共享内存中存数据了,跟我们普通的指针操作一样.
1 |
|
那如何删除我们创建的共享内存呢?多数linux系统中,如果不主动删除共享内存,它会一直存在到系统重启.
1 |
|
托管共享内存
然而上述的方式几乎不会用到,因为它每次需要按单个字符的形式读写内存,所以绝大多数情况下我们会使用托管共享内存方式,它会以内存申请的方式对共享内存对象进行初始化.
1 |
|
删除托管共享内存
1 |
|
互斥对象
既然共享内存对所有进程都可见,那么怎么保证共享内存的读写不会相互干扰呢?那就是使用互斥对象,当共享内存被使用时,互斥对象被占用,其他进程想要操作同一共享内存时,就需要等待互斥对象释放,这样就保证了共享内存的正确读写,类似于多线程中的锁.
互斥对象的声明
1 |
|
DeltaCV中的共享内存类模板
至此,关于boost下的共享内存编程就简单的介绍一下,应该能满足日常的使用了,下面我将介绍我在DeltaCV中封装的共享内存类模板,整体框架类似与ROS中的topic形式,提供发布者和订阅者.完整代码见DeltaCV.
发布器
1 |
|
订阅器
1 |
|
小技巧
代码中,我额外添加了一个标志位update_flag,用来指示当前取到的数据是否是最新值,因为只要发布器那边存入一次数据,update_flag这个变量就置为1,而订阅器每取一次数据,就讲update_flag置0,所以当订阅器的获取频率超过发布器时,订阅器取到的数据一部分是未更新的数据,则订阅器能通过get()的返回值来选择是否使用本次获取的数据(若返回值为true,则为最新值,若为false,则说明自上次订阅器取过值后到这次取值之间,发布器没有存入过新的数据).
演示
分别新建两个main函数,代表两个进程,其中一个作为发布器,每5s发布一次数据,另外一个作为订阅器,每1s获取一次数据.
1 |
|
最终输出为:(可以看到5s内,订阅器有4次获取的是未更新的值)
1 |
|
参考:http://zh.highscore.de/cpp/boost/interprocesscommunication.html