Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Interprocess reader/writer lock with Boost

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:

  1. it doesn't show complete code (I am guessing I need to use named_upgradable_mutex but I am not quite sure) and
  2. 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.

like image 785
wpfwannabe Avatar asked Sep 15 '12 16:09

wpfwannabe


People also ask

What is boost interprocess?

Interprocess simplifies the use of common interprocess communication and synchronization mechanisms and offers a wide range of them: Shared memory. Memory-mapped files. Semaphores, mutexes, condition variables and upgradable mutex types to place them in shared memory and memory mapped files.

Is boost interprocess header only?

Boost Interprocess is a header-only library, so all you need to do is include the appropriate header in your sources and make the compiler aware of the include path.

What is a reader writer lock and when is it useful?

A readers/writer lock regulates access to a set of data. The readers/writer lock is so called because many threads can hold the lock simultaneously for reading, but only one thread can hold the lock for writing. Most device drivers do not use readers/writer locks. These locks are slower than mutexes.

How read write lock works?

An RW lock allows concurrent access for read-only operations, write operations require exclusive access. This means that multiple threads can read the data in parallel but an exclusive lock is needed for writing or modifying data.


1 Answers

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.

like image 158
Daniel Trebbien Avatar answered Sep 28 '22 16:09

Daniel Trebbien