Necktwi
Necktwi

Reputation: 2625

C++ how to safely multi thread read and write a memory block?

  1. reader threads can read the memory block simultaneously but not while a writer thread is writing.
  2. only one writer thread can write at a time.
  3. a writer thread can not write while a reader is reading.

How to do it in C++?

Upvotes: 1

Views: 2630

Answers (1)

rturrado
rturrado

Reputation: 8074

Here's a simple example to get you started:

  • It uses a global buffer of 5 ints, and a global std::mutex.
  • It creates 5 reader threads and 5 writer threads, each of which, respectively, go on and read/write the whole buffer and print what they read/wrote and from/to where.
  • The threads' life overlap: readers and writers creation is interleaved and, apart from that, each of them waits a random time before starting to work. Reader threads also wait some random time after the read.
  • All the threads share a std::shared_mutex.
  • Each reader thread uses a std::shared_lock on the std::shared_mutex, allowing simultaneous reads; while each writer thread uses a std::unique_lock on the std::shared_lock, granting them exclusive access to the buffer.

[Demo]

#include <chrono>
#include <fmt/core.h>
#include <iostream>  // cout
#include <random>  // default_random_engine, random_device, uniform_int_distribution
#include <thread>  // this_thread
#include <vector>
#include <mutex>  // unique_lock
#include <shared_mutex>  // shared_lock, shared_mutex

constinit const size_t buffer_size{5};
constinit int buffer[buffer_size]{};
constinit std::shared_mutex buffer_mtx{};

auto random_sleep_for = [](int low, int high) {
    std::default_random_engine eng{std::random_device{}()};
    std::uniform_int_distribution<> dist{low, high};
    std::this_thread::sleep_for(std::chrono::milliseconds{dist(eng)});
};

auto reader = [](int i){
    for (size_t n{0}; n < buffer_size; ++n) {
        random_sleep_for(50, 100);

        std::shared_lock lck{buffer_mtx};  // shared access with other readers

        std::cout << fmt::format("Thread {} reading {} from buffer[{}]\n",
            i, buffer[n], n);
        random_sleep_for(50, 100);
        std::cout << fmt::format("Thread {} done reading {} from buffer[{}]\n",
            i, buffer[n], n);
    }
};

auto writer = [](int i){
    for (size_t n{0}; n < buffer_size; ++n) {
        random_sleep_for(40, 80);
        
        std::unique_lock lck{buffer_mtx};  // exclusive access to buffer
        
        std::cout << fmt::format("\tThread {} writing {} to buffer[{}]\n",
            i, i*(n+1), n);
        buffer[n] = i*(n+1);
        std::cout << fmt::format("\tThread {} done writing {} to buffer[{}]\n",
            i, i*(n+1), n);
    }
};

int main()
{
    std::vector<std::thread> writers{};
    std::vector<std::thread> readers{};
    for (int i{0}; i < 5; ++i) {
        writers.emplace_back(writer, i + 1);
        readers.emplace_back(reader, i + 1);
    }
    for (int i{0}; i < 5; ++i) {
        writers[i].join();
        readers[i].join();
    }
}

// Outputs:
//
//       Thread 5 writing 5 to buffer[0]
//       Thread 5 done writing 5 to buffer[0]
//       Thread 1 writing 1 to buffer[0]
//       Thread 1 done writing 1 to buffer[0]
//   Thread 5 reading 1 from buffer[0]
//   Thread 4 reading 1 from buffer[0]
//   Thread 3 reading 1 from buffer[0]
//   Thread 2 reading 1 from buffer[0]
//   Thread 1 reading 1 from buffer[0]
//   Thread 5 done reading 1 from buffer[0]
//   Thread 3 done reading 1 from buffer[0]
//   Thread 2 done reading 1 from buffer[0]
//   Thread 4 done reading 1 from buffer[0]
//   Thread 1 done reading 1 from buffer[0]
//   ...

Upvotes: 2

Related Questions