Just making a simple example because I am having issues with a more complex usecase and want to udnerstand the base case before spending too much time in trial and error.
Scenario: I have two binaries that supposedly takes turns incrementing a number (stored in shared memory). What happens in practice is that the "consumer" app takes over 100% never letting the "creator" run.
If I add a small delay in the consumer in that case I obtain the intended behaviour.
Simple POD struct
#pragma once
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <boost/interprocess/sync/interprocess_condition.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
namespace bip = boost::interprocess;
namespace my_namespace {
static const char *name = "MySharedMemory";
struct MyStruct {
bip::interprocess_mutex mutex;
bip::interprocess_condition cond;
unsigned long counter;
MyStruct(): mutex(), cond(), counter(0) {
}
};
} // namespace my_namespace
"Creator/producer"
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <iostream>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <boost/thread/locks.hpp>
#include "my_struct.h"
bool exit_flag = false;
void my_handler(int) {
exit_flag = true;
}
namespace bip = boost::interprocess;
int main() {
struct sigaction sigIntHandler;
sigIntHandler.sa_handler = my_handler;
sigemptyset(&sigIntHandler.sa_mask);
sigIntHandler.sa_flags = 0;
sigaction(SIGINT, &sigIntHandler, NULL);
bip::shared_memory_object::remove(my_namespace::name);
auto memory = bip::managed_shared_memory(bip::create_only, my_namespace::name, 65536);
auto *data = memory.construct<my_namespace::MyStruct>(my_namespace::name)();
long unsigned iterations = 0;
while (!exit_flag) {
boost::interprocess::scoped_lock lock(data->mutex);
data->counter++;
std::cout << "iteration:" << iterations << "Counter: " << data->counter << std::endl;
++iterations;
auto start = boost::posix_time::microsec_clock::universal_time();
auto wait_time = start + boost::posix_time::milliseconds(1000);
auto ret = data->cond.timed_wait(lock, wait_time);
if (!ret) {
std::cout << "Timeout" << std::endl;
}
}
return 0;
}
Consumer
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sched.h>
#include <chrono>
#include <iostream>
#include <thread>
#include <mutex>
#include "my_struct.h"
bool exit_flag = false;
void my_handler(int) {
exit_flag = true;
}
namespace bip = boost::interprocess;
int fib(int x) {
if ((x == 1) || (x == 0)) {
return (x);
} else {
return (fib(x - 1) + fib(x - 2));
}
}
int main() {
struct sigaction sigIntHandler;
sigIntHandler.sa_handler = my_handler;
sigemptyset(&sigIntHandler.sa_mask);
sigIntHandler.sa_flags = 0;
sigaction(SIGINT, &sigIntHandler, nullptr);
auto memory = bip::managed_shared_memory(bip::open_only, my_namespace::name);
auto *data = memory.find<my_namespace::MyStruct>(my_namespace::name).first;
long unsigned iterations = 0;
while (!exit_flag) {
{
boost::interprocess::scoped_lock lock(data->mutex);
std::this_thread::sleep_for(std::chrono::milliseconds(200));
data->counter += 1;
std::cout << "iteration:" << iterations << "Counter: " << data->counter << std::endl;
++iterations;
std::cout << "notify_one" << std::endl;
data->cond.notify_one();
}
// usleep(1); // If I add this it works
}
return 0;
}
If someone can shed some light I would be grateful.
You're doing sleeps while holding the lock. This maximizes lock contention. E.g. in your consumer
Could be
Mutexes are supposed to synchronize access to shared resources. As long as you do not require exclusive access to the shared resource, don't hold the lock. In general, make access atomic and as short as possible in any locking scenario.
Side Notes
You don't need the complicated posix_time manipulation:
Just for sharing a single POD struct,
managed_shared_memory
is a lot of overkill. Considermapped_region
.Consider Asio for signal handling. In any case, make the
exit_flag
atomic so you don't suffer a data race:Since your application is symmetrical, I'd expect the signaling to be symmetrical. If not, I'd expect the producing side to do signaling (after all, presumably there is nothing to consume when nothing was produced. Why be "busy" when you know nothing was produced?).
Live Demo
Live On Coliru
Testing with
Prints e.g.