I see an example in boost ipc (inter process communication)
#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <iostream>
#include <cstdio>
#include "doc_anonymous_condition_shared_data.hpp"
using namespace boost::interprocess;
int main ()
{
//Erase previous shared memory and schedule erasure on exit
struct shm_remove
{
shm_remove() { shared_memory_object::remove("MySharedMemory"); }
~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
} remover;
//Create a shared memory object.
shared_memory_object shm
(create_only //only create
,"MySharedMemory" //name
,read_write //read-write mode
);
try{
//Set size
shm.truncate(sizeof(trace_queue));
//Map the whole shared memory in this process
mapped_region region
(shm //What to map
,read_write //Map it as read-write
);
//Get the address of the mapped region
void * addr = region.get_address();
//Construct the shared structure in memory
trace_queue * data = new (addr) trace_queue;
const int NumMsg = 100;
for(int i = 0; i < NumMsg; ++i){
scoped_lock<interprocess_mutex> lock(data->mutex);
if(data->message_in){
data->cond_full.wait(lock);
}
if(i == (NumMsg-1))
std::sprintf(data->items, "%s", "last message");
else
std::sprintf(data->items, "%s_%d", "my_trace", i);
//Notify to the other process that there is a message
data->cond_empty.notify_one();
//Mark message buffer as full
data->message_in = true;
}
}
catch(interprocess_exception &ex){
std::cout << ex.what() << std::endl;
return 1;
}
return 0;
}
There is no delete operator in the example. Probably new operator used in memory region place and it can not use with delete operator. If I need to call destructor, I should simply call directly:
data->~trace_queue();
Am I right?
Yes, you are right as Joachim commented.
However, I'd suggest using managed_shared_memory which has the find<T>, find_or_construct<T> or construct<T> to make your life easier.
While you're at it, if you need to store many object of the same type, consider using a std::vector (or boost::container::vector) of that type, with boost::interprocess::allocator.
Related
In a previous questionTrying to write asynchronous I/O in C++ using locks and condition variables. This code calls terminate on the first lock() why?
,
we tried to use two mutexes to have asynchronous code that reads one block of a file into memory, then asynchronously tries to read the next block while processing the current one. Someone made a comment that using read was not the best way to do that. This is an attempt to use POSIX aio_read, but we are trying to wait on a condition_variable and do a notify on the condition variable in the callback after the I/O completes, and it's not working -- in the debugger we can see it blows right past the wait.
#include <aio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <condition_variable>
#include <cstring>
#include <iostream>
#include <thread>
using namespace std;
using namespace std::chrono_literals;
constexpr uint32_t blockSize = 512;
mutex readMutex;
mutex procMutex;
condition_variable cv;
int fh;
int bytesRead;
void process(char* buf, uint32_t bytesRead) {
cout << "processing..." << endl;
usleep(100000);
}
void aio_completion_handler(sigval_t sigval) {
struct aiocb* req = (struct aiocb*)sigval.sival_ptr;
// check whether asynch operation is complete
if (aio_error(req) == 0) {
int ret = aio_return(req);
cout << "ret == " << ret << endl;
cout << (char*)req->aio_buf << endl;
}
cv.notify_one();
}
void thready() {
char* buf1 = new char[blockSize];
char* buf2 = new char[blockSize];
aiocb cb;
char* processbuf = buf1;
char* readbuf = buf2;
fh = open("smallfile.dat", O_RDONLY);
if (fh < 0) {
throw std::runtime_error("cannot open file!");
}
memset(&cb, 0, sizeof(aiocb));
cb.aio_fildes = fh;
cb.aio_nbytes = blockSize;
cb.aio_offset = 0;
// Fill in callback information
/*
Using SIGEV_THREAD to request a thread callback function as a notification
method
*/
cb.aio_sigevent.sigev_notify_attributes = nullptr;
cb.aio_sigevent.sigev_notify = SIGEV_THREAD;
cb.aio_sigevent.sigev_notify_function = aio_completion_handler;
/*
The context to be transmitted is loaded into the handler (in this case, a
reference to the aiocb request itself). In this handler, we simply refer to
the arrived sigval pointer and use the AIO function to verify that the request
has been completed.
*/
cb.aio_sigevent.sigev_value.sival_ptr = &cb;
int currentBytesRead = read(fh, buf1, blockSize); // read the 1st block
unique_lock<mutex> readLock(readMutex);
while (true) {
cb.aio_buf = readbuf;
aio_read(&cb); // each next block is read asynchronously
process(processbuf, currentBytesRead); // process while waiting
cv.wait(readLock);
if (currentBytesRead < blockSize) {
break; // last time, get out
}
cout << "back from wait" << endl;
swap(processbuf, readbuf); // switch to other buffer for next time
currentBytesRead = bytesRead; // create local copy
}
delete[] buf1;
delete[] buf2;
}
int main() {
try {
thready();
} catch (std::exception& e) {
cerr << e.what() << '\n';
}
return 0;
}
I know that construction of a string in a shared memory needs an allocator.
That's fine, but I can't find out how can I do that, because all examples are using a Managed Shared Memory which has a method of get_segment_manager() which has to be used as allocator (if I'm not wrong).
Let's see this example copied from here: https://www.boost.org/doc/libs/1_77_0/doc/html/interprocess/synchronization_mechanisms.html#interprocess.synchronization_mechanisms.conditions.conditions_anonymous_example
doc_anonymous_condition_shared_data.hpp
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <boost/interprocess/sync/interprocess_condition.hpp>
struct trace_queue
{
enum { LineSize = 100 };
trace_queue()
: message_in(false)
{}
//Mutex to protect access to the queue
boost::interprocess::interprocess_mutex mutex;
//Condition to wait when the queue is empty
boost::interprocess::interprocess_condition cond_empty;
//Condition to wait when the queue is full
boost::interprocess::interprocess_condition cond_full;
//Items to fill
char items[LineSize];
//Is there any message
bool message_in;
};
Main process
#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <iostream>
#include <cstdio>
#include "doc_anonymous_condition_shared_data.hpp"
using namespace boost::interprocess;
int main ()
{
//Erase previous shared memory and schedule erasure on exit
struct shm_remove
{
shm_remove() { shared_memory_object::remove("MySharedMemory"); }
~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
} remover;
//Create a shared memory object.
shared_memory_object shm
(create_only //only create
,"MySharedMemory" //name
,read_write //read-write mode
);
try{
//Set size
shm.truncate(sizeof(trace_queue));
//Map the whole shared memory in this process
mapped_region region
(shm //What to map
,read_write //Map it as read-write
);
//Get the address of the mapped region
void * addr = region.get_address();
//Construct the shared structure in memory
trace_queue * data = new (addr) trace_queue;
const int NumMsg = 100;
for(int i = 0; i < NumMsg; ++i){
scoped_lock<interprocess_mutex> lock(data->mutex);
if(data->message_in){
data->cond_full.wait(lock);
}
if(i == (NumMsg-1))
std::sprintf(data->items, "%s", "last message");
else
std::sprintf(data->items, "%s_%d", "my_trace", i);
//Notify to the other process that there is a message
data->cond_empty.notify_one();
//Mark message buffer as full
data->message_in = true;
}
}
catch(interprocess_exception &ex){
std::cout << ex.what() << std::endl;
return 1;
}
return 0;
}
Second process:
#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <iostream>
#include <cstring>
#include "doc_anonymous_condition_shared_data.hpp"
using namespace boost::interprocess;
int main ()
{
//Create a shared memory object.
shared_memory_object shm
(open_only //only create
,"MySharedMemory" //name
,read_write //read-write mode
);
try{
//Map the whole shared memory in this process
mapped_region region
(shm //What to map
,read_write //Map it as read-write
);
//Get the address of the mapped region
void * addr = region.get_address();
//Obtain a pointer to the shared structure
trace_queue * data = static_cast<trace_queue*>(addr);
//Print messages until the other process marks the end
bool end_loop = false;
do{
scoped_lock<interprocess_mutex> lock(data->mutex);
if(!data->message_in){
data->cond_empty.wait(lock);
}
if(std::strcmp(data->items, "last message") == 0){
end_loop = true;
}
else{
//Print the message
std::cout << data->items << std::endl;
//Notify the other process that the buffer is empty
data->message_in = false;
data->cond_full.notify_one();
}
}
while(!end_loop);
}
catch(interprocess_exception &ex){
std::cout << ex.what() << std::endl;
return 1;
}
return 0;
}
I'd like to replace char items[LineSize]; to a more convenient string in the trace_queue struct.
How can I do that without the Managed Shared Memory?
Or this is somewhat completely not recommended to do without the managed Boost libraries?
Or this is somewhat completely not recommended to do without the managed Boost libraries?
I cannot recommend it. It's fine to do it unmanaged, but I'd 100% suggest the exact approach they gave with the fixed char array. What's wrong with that?
You cannot have your cake and eat it. You can't wish for "highlevel dynamic strings" and "no heap management overhead" magically at the same time.
That said, you may be able to find some trade-offs. Specifically, you might want to emulate something like a polymorphic memory resource in such a shared byte array. Then you could use std::pmr::string on top of that. Tragedy has it that memory_resource isn't shared-memory safe.
SIMPLIFY
However, I suppose all you need is some nice abstraction, where the interface is using C++ vocabulary types. Why not simplfy the entire deal to that point?
Here's a quick draft:
struct trace_queue {
private:
bip::interprocess_mutex mutex;
bip::interprocess_condition cond;
std::array<char, 300> buffer{};
bool message_in{false}; // Is there any message
auto wait(bool state) {
bip::scoped_lock lock(mutex);
cond.wait(lock, [=,this] { return message_in == state; });
return lock;
}
public:
void send(std::string_view msg) {
auto lock = wait(false); // !message_in
auto n = std::min(buffer.size(), msg.size());
std::fill(buffer.begin(), buffer.end(), '\0');
std::copy_n(msg.data(), n, buffer.begin());
message_in = true;
cond.notify_one();
}
std::string receive() {
auto lock = wait(true); // message_in
std::string msg(buffer.data(), strnlen(buffer.data(), buffer.size()));
message_in = false;
cond.notify_one();
return msg;
}
};
In my opinion the code is already easier to read. And it's certainly easier to use! The entire server side:
// Create a shared memory object.
bip::shared_memory_object shm(bip::create_only, "MySharedMemory",
bip::read_write);
shm.truncate(sizeof(trace_queue));
// Map the whole shared memory in this process
bip::mapped_region region(shm, bip::read_write);
trace_queue& data = *new (region.get_address()) trace_queue;
for (int i = 0; i < 99; ++i)
data.send("my_trace_" + std::to_string(i));
data.send("TEARDOWN");
And the client side:
bip::shared_memory_object shm(bip::open_only, "MySharedMemory",
bip::read_write);
bip::mapped_region region(shm, bip::read_write);
trace_queue& data = *static_cast<trace_queue*>(region.get_address());
while (true) {
auto msg = data.receive();
if (msg == "TEARDOWN")
break;
std::cout << msg << "\n";
};
See it Live On Coliru
#include <array>
#include <boost/interprocess/mapped_region.hpp>
#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/sync/interprocess_condition.hpp>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <iostream>
namespace bip = boost::interprocess;
struct trace_queue {
private:
bip::interprocess_mutex mutex;
bip::interprocess_condition cond;
std::array<char, 300> buffer{};
bool message_in{false}; // Is there any message
auto wait(bool state) {
bip::scoped_lock lock(mutex);
cond.wait(lock, [=,this] { return message_in == state; });
return lock;
}
public:
void send(std::string_view msg) {
auto lock = wait(false); // !message_in
auto n = std::min(buffer.size(), msg.size());
std::fill(buffer.begin(), buffer.end(), '\0');
std::copy_n(msg.data(), n, buffer.begin());
message_in = true;
cond.notify_one();
}
std::string receive() {
auto lock = wait(true); // message_in
std::string msg(buffer.data(), strnlen(buffer.data(), buffer.size()));
message_in = false;
cond.notify_one();
return msg;
}
};
int main(int argc, char**) {
try {
if (argc < 2) {
// Erase previous shared memory and schedule erasure on exit
struct shm_remove {
shm_remove() { bip::shared_memory_object::remove("MySharedMemory"); }
~shm_remove() { bip::shared_memory_object::remove("MySharedMemory"); }
} remover;
// Create a shared memory object.
bip::shared_memory_object shm(bip::create_only, "MySharedMemory",
bip::read_write);
shm.truncate(sizeof(trace_queue));
// Map the whole shared memory in this process
bip::mapped_region region(shm, bip::read_write);
trace_queue& data = *new (region.get_address()) trace_queue;
for (int i = 0; i < 99; ++i)
data.send("my_trace_" + std::to_string(i));
data.send("TEARDOWN");
} else {
bip::shared_memory_object shm(bip::open_only, "MySharedMemory",
bip::read_write);
bip::mapped_region region(shm, bip::read_write);
trace_queue& data = *static_cast<trace_queue*>(region.get_address());
while (true) {
auto msg = data.receive();
if (msg == "TEARDOWN")
break;
std::cout << msg << "\n";
};
}
} catch (std::exception const& ex) {
std::cout << ex.what() << std::endl;
return 1;
}
}
Output, as expected:
Is it possible to grow and shrink managed_windows_shared_memory?
I'm try example for managed_shared_memory from boost doc and it's work.
//Now that the segment is not mapped grow it adding extra 500 bytes
managed_shared_memory::grow("MyManagedShm", 500);
But when my "MyManagedShm" is windows-native serment, program just freeze. If I write
managed_windows_shared_memory::grow("MyManagedShm", 500);
compiler says
no matching function to call
with note
couldn't deduce template parameter 'ManagedMemory'
and show me candidate that fits perfectly:
template<class ManagedMemory>
static bool grow(const char *filename, std::size_t extra_bytes)
{
typedef typename ManagedMemory::device_type device_type;
//Increase file size
try{
offset_t old_size;
{
device_type f(open_or_create, filename, read_write);
if(!f.get_size(old_size))
return false;
f.truncate(old_size + extra_bytes);
}
ManagedMemory managed_memory(open_only, filename);
//Grow always works
managed_memory.self_t::grow(extra_bytes);
}
catch(...){
return false;
}
return true;
}
I can't find any info in boost docs that this in not possible in windows. Maybe in windows I need to force unmap segment before grow or shrink? I can't find how to do this as well.
If I write
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/managed_windows_shared_memory.hpp>
using namespace boost::interprocess;
int main() {
{ managed_windows_shared_memory msm(open_or_create, "mmap", 400); }
//{ managed_shared_memory::grow("mmap", 65535); }
{ managed_windows_shared_memory msm(open_only, "mmap"); }
}
It throw "file not found" on second open.
What does "when it is a windows native segment" even mean? It should not be used at the time of the grow, obviously.
Also, the "perfect match" overload will never deduce the template argument, because it's not used in the signature.
So you'll always have to explicitly instantiate.
Proof of concept:
Live On Coliru
#include <boost/interprocess/managed_mapped_file.hpp>
using namespace boost::interprocess;
int main() {
{ managed_mapped_file mmf(open_or_create, "mmap", 400); }
{ managed_mapped_file::grow("mmap", 65535); }
{ managed_mapped_file mmf(open_only, "mmap"); }
}
Live On Coliru
#include <boost/interprocess/managed_shared_memory.hpp>
using namespace boost::interprocess;
int main() {
{ managed_shared_memory msm(open_or_create, "mmap", 400); }
{ managed_shared_memory::grow("mmap", 65535); }
{ managed_shared_memory msm(open_only, "mmap"); }
}
I compiled it on Linux with: g++ test.c -o test
I rewritten the original example.
Now made the first process to wait 2 seconds, (so that process2 could write on the shared memory), then I made process1 to read from that memory. Is this test correct?
Secondo question: where should I put:
shmdt(tests[0]); // or 1
shmctl(statesid, IPC_RMID, 0);
//Global scope
char *state[2];
//...
//...
struct teststruct {
int stateid;
teststruct *next;
//other things
};
void write(teststruct &t, char* what)
{
strcpy(state[t.next->stateid], what);
printf("\n\nI am (%d), I wrote on: %d", t.stateid, t.next->stateid);
}
void read(teststruct &t)
{
printf("\n\nI am (%d), I read: **%s**", t.stateid, state[t.stateid]);
}
int main() {
key_t key;
if ((key = ftok(".", 'a')) == -1) {
perror("ftok");
exit(1);
}
int statesid;
if ((statesid = shmget(key, sizeof(char*)*50, 0600 | IPC_CREAT )) == -1) {
perror("shmget error");
exit(1);
}
state[0] = (char*)shmat(statesid, NULL, 0);
state[1] = (char*)shmat(statesid, NULL, 0);
teststruct tests[2];
tests[0].stateid = 0;
tests[0].next = &tests[1];
tests[1].stateid = 1;
tests[1].next = &tests[0];
int t0, t1;
switch (t0 = fork()) {
case (0):
sleep(2);
read(tests[0]);
exit(0);
case (-1):
printf("\nError!");
exit(-1);
default:
wait();
}
switch (t1 = fork()) {
case (0):
write(tests[1], "1 write on 0 in theory.");
exit(0);
case (-1):
printf("\nError!");
exit(-1);
default:
wait();
}
return 0;
}
In particular I am asking if "state" is really shared between the two process, and If what I've done is a good way to do that.
My goal is to make char *state[2] shared (reading/modifying) between the two processes after fork.
You don't need to call shmat() twice. You've only allocated enough space for two pointers, so you can't communicate much between the two processes. And you can't rely on being able to copy a pointer to memory in the first process into shared memory and then have the second process read and use it. The address may be valid in the first process and not in the second; it may well point at completely different data in the second process (dynamic memory allocation in particular could screw this up). You can only rely on the contents of the shared memory being the same in both processes. You should allocate enough shared memory to hold the shared data.
However, with that said, the two processes should be sharing that small piece of shared memory, and in both processes, state[0] and state[1] will point at the shared memory and you should be able to communicate between the two by writing in the shared memory. Note that after forking, if either process changes the value stored in its state[0] or state[1], the other process will not see that change — the other process can only see what changes in the shared memory those pointers point to.
Of course, you've not set up any synchronization mechanism, so the access will likely be chaotic.
How can I modify my code just to make it works as intended (without considering synchronization issues)?
It isn't entirely clear how it is intended to work, which complicates answering the question. However, if you want (for sake of example) the child process to write a word to the shared memory and the parent process to read the word from shared memory, then you allocate enough shared memory for the biggest word you're willing to process, then arrange for the child to copy a word from its per-process memory into the shared memory (and notify the parent that it has done so), and then the parent can copy or read the word from shared memory and compare it with data from its per-process memory.
Because you have a parent-child process which are forks of the same process, you will find that the two processes share a lot of the same memory addresses containing the same information. This is, however, coincidental. You can have unrelated processes connect to shared memory, and they need not have any addresses in common. Thus, it would be trivial to get spurious results from your current setup.
Working Code
For some definitions of 'working', the following C++ code does. The code is subtly C++; the code assumes struct teststruct declares type teststruct, and uses references as parameters.
Note that the (revised) code in the question has its wait() calls infelicitously placed.
shm2.cpp
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
static char *state = 0;
struct teststruct
{
int stateid;
teststruct *next;
};
void sm_write(teststruct &t, char* /*what*/)
{
//strcpy(state[t.next->stateid], what);
printf("[%5d] I am (%d), I wrote on: %d\n", (int)getpid(), t.stateid, t.next->stateid);
}
void sm_read(teststruct &t)
{
printf("[%5d] I am (%d), I read: **%s**\n", (int)getpid(), t.stateid, state);
}
int main(void)
{
key_t key;
if ((key = ftok(".", 'a')) == -1) {
perror("ftok");
exit(1);
}
int statesid;
if ((statesid = shmget(key, sizeof(char)*512, 0600 | IPC_CREAT )) == -1) {
perror("shmget error");
exit(1);
}
if ((state = (char*)shmat(statesid, NULL, 0)) == 0)
{
perror("shmat");
exit(1);
}
sprintf(state, "This is a string in shared memory %d", 919);
teststruct tests[2];
tests[0].stateid = 0;
tests[0].next = &tests[1];
tests[1].stateid = 0;
tests[1].next = &tests[0];
int t0, t1;
if ((t0 = fork()) < 0)
{
perror("fork-1");
exit(1);
}
else if (t0 == 0)
{
sm_read(tests[0]);
printf("[%5d] sleeping\n", (int)getpid());
sleep(2);
printf("[%5d] waking\n", (int)getpid());
sm_read(tests[0]);
exit(0);
}
else if ((t1 = fork()) < 0)
{
perror("fork-2");
exit(-1);
}
else if (t1 == 0)
{
printf("[%5d] sleeping\n", (int)getpid());
sleep(1);
printf("[%5d] waking\n", (int)getpid());
strcpy(state, "1 write on 0 in theory.");
sm_write(tests[1], state);
exit(0);
}
int corpse;
int status;
while ((corpse = wait(&status)) > 0)
printf("PID %5d died with status 0x%.4X\n", corpse, status);
return 0;
}
Example run
[20440] sleeping
[20440] waking
[20440] I am (0), I wrote on: 0
[20439] I am (0), I read: **This is a string in shared memory 919**
[20439] sleeping
[20439] waking
[20439] I am (0), I read: **1 write on 0 in theory.**
PID 20440 died with status 0x0000
PID 20439 died with status 0x0000
You have a problem with the size of the shared memory. In:
(statesid = shmget(key, sizeof(char*)*2, 0600 | IPC_CREAT )
you are just reserving space for 2 pointers to char. You need to allocate enough space for all your data, that based on the struct is kind of linked structure. The code could be something like the following, though the purpose of the fork() and shared memory is not very clear to me:
struct teststruct {
int stateid;
teststruct *next;
//other things
};
void dosomething(teststruct &t){
//forget about global space, you don't need it
}
int main() {
key_t key;
if ((key = ftok(".", 'a')) == -1) {
perror("ftok");
exit(1);
}
int statesid;
int size_struct = sizeof(teststruct)*2; //assuming you will have only 1 level of linking
if ((statesid = shmget(key, size_struct, 0600 | IPC_CREAT )) == -1) {
perror("shmget error");
exit(1);
}
//if you need to hold just one teststruct object data, you can do
teststruct* p_test_struct = (teststruct*)shmat(statesid, NULL, 0);
for (int i=0; i<2; i++){
*p_test_struct = tests[i]; //this actually writes tests[i] into shared mem
int t0, t1;
switch (t0 = fork()) {
case (0):
dosomething(*p_test_struct);
exit(0);
case (-1):
printf("\nError!");
exit(-1);
default:
wait();
}
}
return 0;
}
No, it does not. Because you are using fork (multiprocess) instead of threads (multithread). Memory zones are not shared into parent and child process. You will have the same value into it on the child but after that it will be independent to the another one.
This thread is gold when it comes to explaining how to implement reader/writer locks with Boost. It seems relatively simple and I really love it but it also seems to be using a non-named lock and I need an interprocess solution (doesn't need to be portable, can be Windows-only).
Is there a way to have an interprocess shared_mutex? I see there is a named_mutex but I can't get it to work with shared_lock ot other locks.
Any pointers are appreciated.
[EDIT]
In the meantime, I have come across this thread which almost hits the nail on the head. I have two issues:
it doesn't show complete code (I am guessing I need to use named_upgradable_mutex but I am not quite sure) and
I don't like the answer for the modified "writer" which uses no off the shelf class that does unlocking in destructor but a sequence of 3 raw calls on the mutex.
Comments or good solutions are still welcome.
The Boost.Interprocess documentation describes the so-called upgradable mutexes it supports and the upgradable mutex operations for the two supported upgradable mutex types:
boost::interprocess::interprocess_upgradable_mutex, a non-recursive, anonymous upgradable mutex that can be placed in shared memory or memory mapped files.
boost::interprocess::named_upgradable_mutex, a non-recursive, named upgradable mutex.
EDIT: I believe this works:
#include <iostream>
#include <string>
#include <unistd.h>
#include <boost/scope_exit.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/sync/interprocess_upgradable_mutex.hpp>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <boost/interprocess/sync/sharable_lock.hpp>
#include <boost/interprocess/sync/upgradable_lock.hpp>
// http://stackoverflow.com/questions/12439099/interprocess-reader-writer-lock-with-boost/
#define SHARED_MEMORY_NAME "SO12439099-MySharedMemory"
struct shared_data {
private:
typedef boost::interprocess::interprocess_upgradable_mutex upgradable_mutex_type;
mutable upgradable_mutex_type mutex;
volatile int counter;
public:
shared_data()
: counter(0)
{
}
int count() const {
boost::interprocess::sharable_lock<upgradable_mutex_type> lock(mutex);
return counter;
}
void set_counter(int counter) {
boost::interprocess::scoped_lock<upgradable_mutex_type> lock(mutex);
this->counter = counter;
}
};
int main(int argc, char *argv[])
{
using namespace boost::interprocess;
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " WHICH" << std::endl;
return 1;
}
const std::string which = argv[1];
if (which == "parent") {
shared_memory_object::remove(SHARED_MEMORY_NAME);
shared_memory_object shm(create_only, SHARED_MEMORY_NAME, read_write);
BOOST_SCOPE_EXIT(argc) {
shared_memory_object::remove(SHARED_MEMORY_NAME);
} BOOST_SCOPE_EXIT_END;
shm.truncate(sizeof (shared_data));
// Map the whole shared memory into this process.
mapped_region region(shm, read_write);
// Construct the shared_data.
new (region.get_address()) shared_data;
// Go to sleep for a minute.
sleep(60);
return 0;
} else if (which == "reader_child") {
shared_memory_object shm(open_only, SHARED_MEMORY_NAME, read_write);
mapped_region region(shm, read_write);
shared_data& d = *static_cast<shared_data *>(region.get_address());
for (int i = 0; i < 100000; ++i) {
std::cout << "reader_child: " << d.count() << std::endl;
}
} else if (which == "writer_child") {
shared_memory_object shm(open_only, SHARED_MEMORY_NAME, read_write);
mapped_region region(shm, read_write);
shared_data& d = *static_cast<shared_data *>(region.get_address());
for (int i = 0; i < 100000; ++i) {
d.set_counter(i);
std::cout << "writer_child: " << i << std::endl;
}
}
}
I tried this on a Mac with the following script:
#!/usr/bin/env sh
./a.out reader_child &
./a.out reader_child &
./a.out writer_child &
./a.out reader_child &
./a.out reader_child &
(You have to start the parent first: ./a.out parent)
The output showed interleaving of "reader_child" and "writer_child" lines (with all of the "reader_child" lines showing a non-zero value after the first "writer_child" line), so it appears to be working.