jeudi 28 janvier 2021

Implementation of pattern "Multiplex" in c++11/14

Hey everybody and first of all forgive my poor english.

I have to implement the pattern "Multiplex" as described in "The Little Book of Semaphores" by Allen. B.Downey (it's a free resource).

I can't and I don't want use semaphores introduced in C++20 and so, only using mutex and condition variable, I came to the following code, maybe clumsy and twisted (spoiler):

/*
    PATTERN:    Multiplex

    TARGET:    allows multiple threads to run in the critical section at the
    same time, but it enforces an upper limit on the number of concurrent 
    threads. 
    In other words, no more than n threads can run in the critical section at 
    the same time
*/

//#include "stdafx.h"     // Only for MS Visual Studio
#include <mutex>
#include <condition_variable>
#include <iostream>
#include <thread>
#include <string>
#include <vector>

using namespace std;

//#define IF_EXPIRES_ON_ONLINE_COMPILER        //    comment/uncomment it if you need

mutex mtx_IO;    //    No interleaved output

vector<int> deb_evolution_of_threads_In_CR;    //    only for debug purposes

const int iterationForcpuConsumer = 1000;

void cpuConsumer(thread::id tid)    //    the first thing that came to my fingers
{
#ifndef IF_EXPIRES_ON_ONLINE_COMPILER
    {
        lock_guard<mutex> lg(mtx_IO);
        cout << "\n\tBEG cpuConsumer from #thread = " << tid;
    }

    string str = "str";
    for (int i = 0; i < iterationForcpuConsumer; ++i)
    {
        int j = i;
        try
        {
            str += str;
        }
        catch (...)
        {
            str = "";
        }
    }
    {
        lock_guard<mutex> lg(mtx_IO);
        cout << "\n\tEND cpuConsumer from #thread = " << tid;
    }
#else
    this_thread::sleep_for(chrono::milliseconds(1000));
#endif // !IF_EXPIRES_ON_ONLINE_COMPILER
}

const int totalNumThreadLaunched = 5;
const int upperLimitForThreadInCriticalRegion = 3;

const int nrOfIterations = 5;

mutex mtx_CR;
condition_variable cv;
int threads_In_CR = 0;

void threadLogic()
{
    for (int i = 0; i < nrOfIterations; ++i)
    {
        {
            lock_guard<mutex> lg(mtx_IO);
            cout << "\nElaboration that precedes the critical region for #thread = " << this_thread::get_id();
        }

        unique_lock<mutex> ul(mtx_CR);
        cv.wait(ul, []() {return (threads_In_CR < upperLimitForThreadInCriticalRegion); });
        ++threads_In_CR;
        deb_evolution_of_threads_In_CR.push_back(threads_In_CR);    //    only for debug purposes
        ul.unlock();

        cpuConsumer(this_thread::get_id());        //    Critical Region

        {
            lock_guard<mutex> lg(mtx_CR);
            --threads_In_CR;
            deb_evolution_of_threads_In_CR.push_back(threads_In_CR);    //    only for debug purposes
        }
        cv.notify_one();

        {
            lock_guard<mutex> lg(mtx_IO);
            cout << "\nElaboration that follows the critical region for #thread = " << this_thread::get_id();
        }
    }
}

int main()
{
    int DEBUG = 0;
    deb_evolution_of_threads_In_CR.push_back(0);

    vector<thread> vThreads;
    vThreads.reserve(totalNumThreadLaunched);
    for (int i = 0; i < totalNumThreadLaunched; ++i)
    {
        vThreads.push_back(thread(threadLogic));
    }

    for (int i = 0; i < totalNumThreadLaunched; ++i)
    {
        if (vThreads[i].joinable())
        {
            vThreads[i].join();
        }
    }

    for (auto i = deb_evolution_of_threads_In_CR.begin(); i != deb_evolution_of_threads_In_CR.end(); ++i)
    {
        cout << "\n" << *i;
    }

    return 0;
}

Here a link to Coliru.

I thought and thought again, I analyzed and reanalyzed, I watched the outputs (but you know that they are not a proof) but now I need a comparison.

Can you tell if this code is correct? And if not, where are the traps? Every other suggestion about a better design (inside the constraints of C++11/14) is well accepted.

PS: I suppose that something might be declared 'atomic' (correct me if I'm wrong) but anyway I didn't study deeply the C++ memory model and so suggestions in that direction are well accepted in any case but, at the moment, these notions are not at the top issue list; if I suppose the I don't really know something I don't want use it.

Finally: can you suggest some metodology, some tool, somewhat that can guide the approach to this kind of code/problems? I was thinking to Petri Nets

Probably this will not be my last question about the implementation of some similar patterns, I hope it will also join you in the next discussions.

Thank for your attention and for your time

Aucun commentaire:

Enregistrer un commentaire