Synchronizacja sekwencyjnej wymiany danych między dwoma procesami z użyciem mutexów

0

Witam, chciałbym zrobić zsynchronizowaną sekwencyjną wymianę danych między dwoma procesami w pojedynczej iteracji pętli. Udało mi się zrobić bez sekwencyjną synchronizację komunikacji w pojedynczej iteracji pętli na zasadzie, że proces A zapisuje w pamięci (Shared File Mapping) pewną liczbę, proces B podbiją to liczbę o 1, proces A tą liczbę sobie zapisuje i znowu zapodaje ją do procesu B - no i tak w pętli.

[Proces A]            [Proces B]
------------[1-sza Iteracja Pętli]----------------
Wyślij value = 1      POCZEKAJ
POCZEKAJ              Odbierz i Podbij +1 i wyślij value = 1 + 1 = 2

------------[2-ga Iteracja Pętli]----------------
Wyślij value = 2      POCZEKAJ
POCZEKAJ              Odbierz i Podbij +1 i wyślij value = 2 + 1 = 3

------------[3-cia Iteracja Pętli]----------------
Wyślij value = 3      POCZEKAJ
POCZEKAJ              Odbierz i Podbij +1 i wyślij value = 3 + 1 = 4

i to działa dobrze gdy zastosuje CreateMutex + WaitForSingleObject + ReleaseMutex gdy muszę wymienić tylko jedną informację w jednej iteracji pętli

Problem pojawia się gdy chcę wymienić sekwencyjnie kilka informacji w pojedynczej iteracji pętli, tak by zachować logiczną spójność wymiany danych

[Proces A]                                            [Proces B]
----------------------------[1-sza Iteracja Pętli]---------------------------------------------------
Wyślij sendId = 12345 (Losowa liczba)                 POCZEKAJ
POCZEKAJ                                              Odbierz i Zapisz sendId = 12345. Wyślij prośbę o liczbie X
Odbierz prośbę o liczbę X i wyślij X = 7777           POCZEKAJ
POCZEKAJ                                              Odbierz i Zapisz X = 7777. Wyślij prośbę o zmianę wartości liczby X = 7777 + 2222 = 9999
Odbierz wartość liczby X = 9999 i zapisz              [Nastąpi przejście do następnej iteracji]

----------------------------[2-ga Iteracja Pętli]---------------------------------------------------
Wyślij sendId = 6789 (Losowa liczba)                  POCZEKAJ
POCZEKAJ                                              Odbierz i Zapisz sendId = 6789. Wyślij prośbę o liczbie X
Odbierz prośbę o liczbę X i wyślij X = 9999           POCZEKAJ
POCZEKAJ                                              Odbierz i Zapisz X = 9999. Wyślij prośbę o zmianę wartości liczby X = 9999 + 2222 = 12221
Odbierz wartość liczby X = 12221 i zapisz             [Nastąpi przejście do następnej iteracji]  

Przy próbie dokonania sekwencji i użycia CreateMutex + WaitForSingleObject + ReleaseMutex wszystko się dosłownie sypie, nie jest zachowana kolejność, proces B nie czeka na proces A.

Pseudo kod -> proces A:

mutex = CreateMutex(NULL, true, "myLock");

X = 7777; 

while(1)
{
      //----------------------------[1-sza Iteracja Pętli]---------------------------------------------------

      WaitForSingleObject(mutex, INFINITE);
      send_id(12345);                       //To powinno wykonać się jako 1-sze
      ReleaseMutex(mutex);                  //Koniec kroku

      WaitForSingleObject(mutex, INFINITE); 
      receive_request_about_X();            //To powinno wykonać się jako 3-cie
      send_X(X); //7777
      ReleaseMutex(mutex);                  //Koniec kroku

      WaitForSingleObject(mutex, INFINITE);
      tmp_X = receive_X(); //9999          //To powinno wykonać się jako 5-te
      X = tmp_X;
      ReleaseMutex(mutex);                 //Koniec kroku
}

Pseudo kod -> proces B:

mutex = OpenMutex(MUTEX_ALL_ACCESS, false, "myLock");

id = 0;
X = 0;

while(1)
{
      //----------------------------[1-sza Iteracja Pętli]---------------------------------------------------

      WaitForSingleObject(mutex, INFINITE);
      tmp_id = receive_id(); //12345       //To powinno wykonać się jako 2-gie
      id = tmp_id;
      send_request_about_X();
      ReleaseMutex(mutex);                 //Koniec kroku

      WaitForSingleObject(mutex, INFINITE); 
      tmp_X = receive_X(); //7777          //To powinno wykonać się jako 4-te
      X = tmp_X + 2222; //9999
      send_X(X); //9999
      ReleaseMutex(mutex);                //Koniec kroku
}

Jak zachować prawidłową synchronizację i kolejność wykonywanych kroków w danej sekwencji między dwoma procesami biorąc pod uwagę, że te dwa procesy działają w pętli (odstępy między iteracjami to ok. 20 ms) ?

1

Przy użyciu jednego mutexu nie masz gwarancji który proces go zajmie pierwszy. Z dużą dozą prawdopodobieństwa zrobi to ten sam wątek który go właśnie zwolnił. Potrzebujesz co najmniej dwóch muteksów, jeden na writera, drugi dla readera, lub jeden do eventów.
Poza tym lepiej jak każdy proces ma swój obszar pamięci do bazgrania, jak chcesz odczytać i zapisać w to samo miejsce to musisz sporo zwolnić komunikację i niepotrzebnie blokować na zbyt długo. Gdyby odpowiedź trafiała w inne miejsce pamięci to pierwszy proces bez przerw może sobie pisać a drugi z maksymalną prędkością czytać i vice versa.

Robisz to w ramach ćwiczeń czy na poważnie? Bo na poważnie to te niskopoziomowe tematy są opakowane w libki i raczej nie powinieneś się ich tykać bo to skomplikowane procesy. Tu jeszcze spoko, ale jak dochodzi więcej readerów, backpressure itp to robi się ciekawie. Albo nieciekawie...

1

Po nazwach funkcji wnioskuję, że programujesz coś w WinApi pod Windows. Nie wiem dokładnie, jak to wygląda w Windows, ale jakiś czas temu (w ramach ćwiczeń, testów, prób) robiłem komunikację między procesami w Linux i tam są trzy mechanizmy:

  1. Pamięć współdzielona
  2. Semafory
  3. Kolejka komunikatów.

O ile dobrze rozumiem, "Shared File Mapping" to nic innego, jak pamięć współdzielona, nie wiem, jak to funkcjonuje w Windows, w Linux obsługuje się to tak samo, jak tablicę
Semafor to jest coś podobnego do mutex, tylko trochę inaczej działa, ale jego rola może być zbliżona.

Nie wiem, czy to Ciebie urządza, ale podam gotowca, czyli zrobioną przez siebie klasę, która wykorzystuje pamięć współdzieloną i dwa semafory. To już musisz sobie przerobić samemu w celu dostosowania pod Windows. Wykorzystuje dwa semafory, bo w tym przypadku była potrzeba, że do odczytu dostęp może mieć dowolna liczba procesów, a do zapisu może mieć tylko jeden proces na wyłączność (problem czytelników i pisarzy). Gdyby ograniczyć się tylko do możliwości zapisu, albo przyjąć zasadę, że w danej chwili, tylko jeden proces może operować na współdzielonej pamięci, to wystarczyłby jeden semafor. Jak w mojej klasie będzie się używać tylko WriteBegin i WriteEnd, to taki efekt się uzyska i można wyrzucić implementację ReadBegin i ReadEnd.

Linki w kodzie to zgromadzone linki, z ktorych korzystałem swego czasu, w pliku nagłówkowym dopisałem komentarze, jak użyć tej klasy.

Oczywiście, jak do Windowsa, to musz przerobić, ale zasada działania może zostać ta sama i powinna być zrozumiała. Dopisałem komentarze w kodzie nagłówka.

#ifndef MEMO_H
#define MEMO_H

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>

#define MemoSemaphores 8

class Memo
{
public:
    // Otwarcie pamięci współdzielonej,
    //  Key - Unikalny identyfikator
    //  Size - Wielkość obszaru pamięci współdzielonej
    //  Create=1 - Tworzenie pamięci współdzielonej
    //  Create=0 - Uzyskanie dostępu do wcześniej utworzonej pamięci współdzielonej
    bool Open(int Key, int Size, bool Create);
    // Zamiknięcie pamięci współdzielonej
    //  Destroy=1 - niszczenie obszaru pamięci współdzielonej w systemie
    void Close(bool Destroy);

    // Funkcje odczytu i zapisu poszczególnych danych jednobajtowych (char) i czterobajtowych (int) we wskazanym miejscu pamięci współdzielonej
    signed char GetSChar(int Addr);
    void SetSChar(int Addr, signed char Value);
    unsigned char GetUChar(int Addr);
    void SetUChar(int Addr, unsigned char Value);
    signed int GetSInt(int Addr);
    void SetSInt(int Addr, signed int Value);
    unsigned int GetUInt(int Addr);
    void SetUInt(int Addr, unsigned int Value);

    // Wywołać przed odczytem czegokolwiek, oczekiwanie na dostęp tylko do odczytu
    void ReadBegin();

    // Wywołać po odczycie, gdy wywołano ReadBegin()
    void ReadEnd();

    // Wywołać przed odczytem lub zapisem czegokolwiek, oczekiwanie na dostęp z możliwoścą zapisu
    void WriteBegin();

    // Wywołać po czynnościach, gdy wywołano WriteEnd()
    void WriteEnd();

    // ### Funkcje przeznaczone do testów działania semaforów, normalnie nie powinny być używane poza klasą ###

    // Polecenie "czekaj" na semaforze
    int SemaWait(int N);

    // Polecenie "sygnał" na semaforze
    int SemaSignal(int N);

    // Pobranie bieżącego stanu semafora
    int SemaState(int N);
    Memo();
private:
    char GetRaw(int Addr);
    void SetRaw(int Addr, char Value);
    char * Buf;
    int SegId;
    int SemId;
    int BufSize;
    union BufValue
    {
        char Raw[4];
        signed int ValSInt;
        unsigned int ValUInt;
    };
    BufValue BufValue_;
};

#endif // MEMO_H
#include "memo.h"

// https://stackoverflow.com/questions/5656530/how-to-use-shared-memory-with-linux-in-c
// https://man.linux.pl/?section=&command=SHMAT
// https://achilles.tu.kielce.pl/portal/Members/332bd2d158a24964b2b98a7fc2879842/programowanie-wspolbiezne/laboratoria/laboratorium-6
// http://wazniak.mimuw.edu.pl/index.php?title=SOP_lab_nr_10
// http://www.mif.pg.gda.pl/homepages/marcin/PWIR2015-16/pwir-2.pdf
// https://mieszkomakuch.github.io/problemy-synchronizacyjne/doc/readers-writers.html

Memo::Memo()
{

}

bool Memo::Open(int Key, int Size, bool Create)
{
    BufSize = Size;
    if (Create)
    {
        SegId = shmget(Key, Size, IPC_CREAT);
        //if (SegId >= 0)
        //{
        //    shmctl(SegId, IPC_RMID, nullptr);
        //}
        //SegId = shmget(Key, Size, IPC_CREAT | IPC_EXCL);

        SemId = semget(Key, MemoSemaphores, IPC_CREAT);
        //if (SemId >= 0)
        //{
        //    semctl(SemId, 0, IPC_RMID);
        //}
        //SemId = semget(Key, MemoSemaphores, IPC_CREAT | IPC_EXCL);
        SemaSignal(0);
        SemaSignal(1);
    }
    else
    {
        SegId = shmget(Key, Size, 0);
        SemId = semget(Key, MemoSemaphores, 0);
    }
    Buf = (char*)shmat(SegId, nullptr, 0);
    if (Create)
    {
        for (int I = 0; I < BufSize; I++)
        {
            Buf[I] = 0;
        }
    }
    if ((SegId >= 0) && (SemId >= 0))
    {
        return true;
    }
    else
    {
        return false;
    }
}

void Memo::Close(bool Destroy)
{
    shmdt(&Buf);
    if (Destroy)
    {
        shmctl(SegId, IPC_RMID, nullptr);
        semctl(SemId, 0, IPC_RMID);
    }
    BufSize = 0;
}

void Memo::SetRaw(int Addr, char Value)
{
    if (Addr < BufSize)
    {
        Buf[Addr] = Value;
    }
}

char Memo::GetRaw(int Addr)
{
    if (Addr < BufSize)
    {
        return Buf[Addr];
    }
    return 0;
}

void Memo::SetSChar(int Addr, signed char Value)
{
    SetRaw(Addr, Value);
}

signed char Memo::GetSChar(int Addr)
{
    return GetRaw(Addr);
}

void Memo::SetUChar(int Addr, unsigned char Value)
{
    SetRaw(Addr, Value);
}

unsigned char Memo::GetUChar(int Addr)
{
    return GetRaw(Addr);
}

void Memo::SetSInt(int Addr, signed int Value)
{
    BufValue_.ValSInt = Value;
    SetRaw(Addr + 0, BufValue_.Raw[0]);
    SetRaw(Addr + 1, BufValue_.Raw[1]);
    SetRaw(Addr + 2, BufValue_.Raw[2]);
    SetRaw(Addr + 3, BufValue_.Raw[3]);
}

signed int Memo::GetSInt(int Addr)
{
    BufValue_.Raw[0] = GetRaw(Addr + 0);
    BufValue_.Raw[1] = GetRaw(Addr + 1);
    BufValue_.Raw[2] = GetRaw(Addr + 2);
    BufValue_.Raw[3] = GetRaw(Addr + 3);
    return BufValue_.ValSInt;
}

void Memo::SetUInt(int Addr, unsigned int Value)
{
    BufValue_.ValUInt = Value;
    SetRaw(Addr + 0, BufValue_.Raw[0]);
    SetRaw(Addr + 1, BufValue_.Raw[1]);
    SetRaw(Addr + 2, BufValue_.Raw[2]);
    SetRaw(Addr + 3, BufValue_.Raw[3]);
}

unsigned int Memo::GetUInt(int Addr)
{
    BufValue_.Raw[0] = GetRaw(Addr + 0);
    BufValue_.Raw[1] = GetRaw(Addr + 1);
    BufValue_.Raw[2] = GetRaw(Addr + 2);
    BufValue_.Raw[3] = GetRaw(Addr + 3);
    return BufValue_.ValUInt;
}

int Memo::SemaWait(int N)
{
    if (N >= MemoSemaphores)
    {
        return -1;
    }
    sembuf * Sema_ = new sembuf[1];
    Sema_[0].sem_num = N;
    Sema_[0].sem_op = -1;
    Sema_[0].sem_flg = 0;
    int T = semop(SemId, Sema_, 1);
    delete[] Sema_;
    return T;
}

int Memo::SemaSignal(int N)
{
    if (N >= MemoSemaphores)
    {
        return -1;
    }
    sembuf * Sema_ = new sembuf[1];
    Sema_[0].sem_num = N;
    Sema_[0].sem_op = 1;
    Sema_[0].sem_flg = 0;
    int T = semop(SemId, Sema_, 1);
    delete[] Sema_;
    return T;
}

int Memo::SemaState(int N)
{
    if (N >= MemoSemaphores)
    {
        return -1;
    }
    union semun
    {
        int val;
        struct semid_ds *buff;
        ushort *array;
    } arg;
    int T = semctl(SemId, N, GETVAL, arg);
    return T;
}

void Memo::ReadBegin()
{
    SemaWait(0);
    unsigned char Readers = GetUChar(0);
    Readers++;
    SetUChar(0, Readers);
    if (Readers == 1)
    {
        SemaWait(1);
    }
    SemaSignal(0);
}

void Memo::ReadEnd()
{
    SemaWait(0);
    unsigned char Readers = GetUChar(0);
    Readers--;
    SetUChar(0, Readers);
    if (Readers == 0)
    {
        SemaSignal(1);
    }
    SemaSignal(0);
}

void Memo::WriteBegin()
{
    SemaWait(1);
}

void Memo::WriteEnd()
{
    SemaSignal(1);
}

W mojej klasie pierwszy bajt (adres 0) to liczba aktualnie czytających procesów (tych, które wywołały ReadBegin, a nie wywołały jeszcze ReadEnd), więc można zapisywać do bajtu dowolnego innego niż 0.

Jeśli chodzi o przedmiotowy problem, to jeden bajt możesz użyć jako stan, albo dwa bajty, po jednym dla każdego procesu.

Na przykład tak może wyglądać Twoje rozwiązanie. Czynności dsą te same, co w wiadomosći podanej przez OP, ale rozpisana pierwsza iteracjea pętli, druga była by taka sama

[proces A]                                      

// Wprowadzanie stanu 1
WriteBegin();
SetUChar(1, 1);
WriteEnd();

----------------------------[1-sza Iteracja Pętli]---------------------------------------------------

// Wykonuje czynnosć i zmienia na stan 2
WriteBegin();
Wyślij sendId = 12345 (Losowa liczba)
SetUChar(1, 2);
WriteEnd();

// Czeka aż proces B zmieni stan na 3
State = 2;
while (State == 2)
{
  ReadBegin()
  State = GetUChar(1);  
  ReadEnd();
}

// Wykonuje czynnosć i zmienia na stan 4
WriteBegin();
Odbierz prośbę o liczbę X i wyślij X = 7777
SetUChar(1, 4);
WriteEnd();

// Czeka aż proces B zmieni stan na 5
State = 4;
while (State == 4)
{
  ReadBegin()
  State = GetUChar(1);  
  ReadEnd();
}

// Wykonuje czynnosć i zmienia na stan 6
WriteBegin();
Odbierz wartość liczby X = 9999 i zapisz 
SetUChar(1, 6);
WriteEnd();

[proces B]

// Czeka na stan 1 wprowadzony przez proces A, aby oba procesy równo zaczely
State = 0;
while (State != 1)
{
  ReadBegin()
  State = GetUChar(1);  
  ReadEnd();
}

----------------------------[1-sza Iteracja Pętli]---------------------------------------------------

// Czeka na stan 2 wprowadzony przez proces A
State = 0;
while (State != 2)
{
  ReadBegin()
  State = GetUChar(1);  
  ReadEnd();
}

// Wykonuje czynność i zmienia stan na 3
WriteBegin();
Odbierz i Zapisz sendId = 12345. Wyślij prośbę o liczbie X
SetUChar(1, 3);
WriteEnd();


// Czeka na stan 4 wprowadzony przez proces A
State = 0;
while (State != 4)
{
  ReadBegin()
  State = GetUChar(1);  
  ReadEnd();
}

// Wykonuje czynność i zmienia stan na 5
WriteBegin();
Odbierz i Zapisz X = 7777. Wyślij prośbę o zmianę wartości liczby X = 7777 + 2222 = 9999
SetUChar(1, 5);
WriteEnd();


// Czeka na stan 6 wprowadzony przez proces A
State = 0;
while (State != 6)
{
  ReadBegin()
  State = GetUChar(1);  
  ReadEnd();
}

// Wprowadzanie stanu 1 na potrzeby kolejnej iteracji pętli w obu procesach
WriteBegin();
SetUChar(1, 1);
WriteEnd();

Oczywiście, ze zapewne da się prościej, krócej, zapewne też z mniejsza liczbą możliwych stanów, ale chodzi mi o naświetlenie, jak zasygnalizować i jak wykryć zmianę stanu, żeby w ramach jednej iteracji pętli wykonać te czynnosći.

Mozna też korzystać z kolejki komunikatów, przy czym nie wiem, czy istnieje takie coś w Windows. Wtedy musiayby być dwie, gdzie proces A wstawia i proces B wyciąga, a druga kolejka przesyła komunikaty w drugą stronę. W przypadku użycia kolejki komunikatów, domyślnie kolejka byłaby pusta i jeżeli jest etap POCZEKAJ, to proces czekałby do pojawienia się komunikatu wstawionego przez drugi proces, potem go wyciągnie i pracuje dalej.

1
  1. Mam dwa źródła danych (pamięci współdzielone). Pierwsze źródło do którego proces A pisze i proces B czyta oraz drugie źródło gdzie proces B pisze i proces A czyta. Czy w takim wypadku potrzebuję zastosować dwa mutexy?

  2. Dlaczego miałbym zastosować mutexy/semafory gdy używam pętle while przy wzajemnym sprawdzaniu stanów w obu procesach. Mi się wydaje, że w takim przypadku mógłbym się oprzeć na pętlach while które piszą i czytają nawzajem w procesach i wtedy mutex/semafor mi nie jest potrzebny. Czy nie powinno być tak, że stosuję albo mutexy albo pętle while?

  3. Dlaczego na funkcji WaitForSingleObject nie mogę wymusić zatrzymania procesu, mimo, że aktualny proces nie jest w posiadaniu aktualnie mutexa?

mutex = CreateMutex(null, false, "myLock");

while(1)
{
      WaitForSingleObject(mutex, INFINITE);
      Print("TEST\n");
}

Ten przykład właśnie przedstawia, że proces nie jest właścicielem mutexa, więc powinien czekać, aż mutex zostanie zwolniony przez inny proces. Mimo wszystko printuje mi cały czas tekst "TEST"

0

A zastanawiałeś się nad użyciem named pipe?
Patrząc na twój scenariusz to wygląda bardziej rozsądnie, a dużo upraszcza.

Jakiś czas temu (+4 lata) pisałem napisałem taki kod:

  • serwer tworzył named pipe za pomocą CreateNamedPipeA ReadFile do czytania WriteFile do psiania.
  • Klient zaś użyłem po prostu std::fstream na named pipe utworzonym przez serwer

Działa bez problemów do tej pory (to był kod do testów, które chodzą codziennie).

0

Możesz też zerknąć w boost::interprocess, jako ściągawkę/inspirację. Tam do wymiany między informacji procesami dla Windowsa używają plików, jako pamięci współdzielonej.

0

Mam dwa źródła danych (pamięci współdzielone). Pierwsze źródło do którego proces A pisze i proces B czyta oraz drugie źródło gdzie proces B pisze i proces A czyta. Czy w takim wypadku potrzebuję zastosować dwa mutexy?

Mutex/semafor jest po to, żeby uczynić zapisanie/odczytanie danych czynnościa atomową. Jeżeli zapisuje się kilka bajtów, które potem ma odczytać inny program, to bez blokady istnieje możliwośc, że proces A zapisze połowę tego, co ma zapisać, a w tym czasie proces B odczyta śmietnik. Da się bez mutexa w ten sposób, że będzie wyznaczony jeden bajt, który normalnie ma wartość 0 i każdy proces, który chce coś zapisać lub odczytać, to czeka, aż będzie mieć 0 (o ile ma inną wartość), potem nadaje wartość 1, zapisuje i nadaje wartość 0. Tak naprawdę ten bajt działa podobnie do mutexa z tą różnicą, że to proces oczekujący pracuje w pustej pętli, zabierając moc przerobową procesora (menedżer zadań będzie pokazywać użycie 100% jednego rdzenia). Mutex to jest blokada w sysstemie operacyjnym, gdzie oczekiwanie na zwolnienie nie zabiera mocy przerobowej procesora.

Dlaczego miałbym zastosować mutexy/semafory gdy używam pętle while przy wzajemnym sprawdzaniu stanów w obu procesach. Mi się wydaje, że w takim przypadku mógłbym się oprzeć na pętlach while które piszą i czytają nawzajem w procesach i wtedy mutex/semafor mi nie jest potrzebny. Czy nie powinno być tak, że stosuję albo mutexy albo pętle while?

Jeżeli odnosisz się do mojego przykładu, który na szybko wymyśliłem, to mutexy (czyli to, co jest realizowane semaforami, a robią funkcje ReadBegin i ReadEnd) tak naprawde gwarantują blokowanie pamięci przed zapisem w czasie, gdy jest ona czytana i na odwrót. To nazywa się "problem czytelników i pisarzy", szeroko opisane w internecie. W najprostszym wariancie, wystarczy jeden mutex, który blokuje innym dostęp do pamięci w celu zagwarantowania, że tylko jeden proces w dnej chwili może coś zapisać lub odczytać z pamięci, przy czym te dane to może być zarówno jeden baj, jak i długi tekst lub liczby, w zależności od potrzeb.

Na pewno da się to zrobić inaczej, lepiej. Wazne, żeby pomyśleć w ten sposób: Masz tablice współdzieloną, gdzie może sz sobie odczytywać i zapisywać co chcesz i gdzie chcesz w niej, do jednej i tej samej tablicy mają dostep wszystkie procesy, jak tównież jest możliwość wykonania czynności:

  1. zablokowanie dostępu innym procesom
  2. poczekanie, aż blokada założona przez inny proces zostanie zwolniona
  3. zwolnienie blokady przez proces, który zablokował

Nie wiem, czy w przypadku mutexa jest to mozliwe, ale w przypadku semafora jest możliwe, że jeden proces opuści semafor (założy blokadę), a drugi proces podniesie semafor (zwolni blokadę).

A jeżeli jest kilka możliwych stanów, to mozna mieć kilka mutexów, np jest 5 stanów, a więc zostanie założonych od razu 5 mutexów, które będą stopniowo, po jednym zwalniane przy poszczególnych zmianach stanu. Proces czekający na określony stan poczeka na zwolnienie mutexa, który odpowiada danemu stanowi, wtedy uniknie sie pętli while.

Mając taki arsenał środków do dyspozycji, jeszcze raz całościowo spójrz na problem, który chcesz rozwiązać. Zwróć uwagę, że w miare możliwości, czekanie na osiągnięcie jakiegoś stanu lub zdarzenie nie powinno być realizowane pętlą while, tylko poprzez wywołanie funkcji systemowej, która na coś czeka.

A zastanawiałeś się nad użyciem named pipe?
Patrząc na twój scenariusz to wygląda bardziej rozsądnie, a dużo upraszcza.

Tak, teraz sobie przypomniałem, ze w Windows jest takie coś, jak potok nazwany, w Linux chyba też, na pewno w Linux jest kolejka komunikatów (message queue), która działa podobnie, że z jednej strony wchodzi komunikat, z drugiej strony wychodzi, istnieje możliwość poczekania, aż do potoku wejdziej komunikat dopóki potok jest pusty.

0
BartoSAS napisał(a):

Możesz też zerknąć w boost::interprocess, jako ściągawkę/inspirację. Tam do wymiany między informacji procesami dla Windowsa używają plików, jako pamięci współdzielonej.

Plik na dysku to jest jakieś tam rozwiązanie. Problem w tym, ze aby cokolwiek zapisać czy odczytać, należy otworzyć plik, zapisać coś i zamknąć plik. Dwa procesy na pewno nie mogą mieć otwartego pliku do zapisu. Albo jest zasada, ze tylko jeden proces może otworzyc plik, albo jest wg problemu czytelników i pisarzy. Nie wiem, jak te funkcje z boost to robią, ale nie spotkałem się z funkcją, która czeka na możliwośc otwarcia pliku. Funkcja otwierająca plik, to albo otwiera plik, albo rzuca błąd, że nie można otworzyć pliku. Inaczej się nie da, chyba, ze na piechotę w pętli while, az nastapi otwarcie bez rzucania wyjatku.

1

Dokładnie technicznie jak to działa, to Ci nie powiem, musiałbyś doczytać u nich.
Z tego co tak się temu przyglądam, to nawet, w najgorszym przypadku jakby OP zrobił na pętlach, to nie powinno być tragedii, bo i tak A czeka na B, a potem B na A, więc to wygląda, jakby tam pod spodem miało się coś zadziać atomowo. O ile to nie jest tylko po to, aby sobie poćwiczyć, to wygląda na pierwszy rzut oka, jakby się proces źle rozłożył pomiędzy procesy w kodzie.

1

Udało mi się uzyskać zamierzony efekt, użyłem tutaj jednego mutexa, który przejmuje w pojedynczym kroku na każdą sekwencję locka jednocześnie na odczyt i zapis do współdzielonych pamięci (jeden read, drugi write). Dodatkowo zastosowałem pętle while tak bym mógł etapami sprawdzać na jakim statusie jest dana sekwencja, coś w podobie co pokazał @andrzejlisek. W tych pętlach jest używany wspomniany wcześniej mutex, który przejmuje locka, wykonuje operacje i releasuje locka. Moim celem było uzyskanie wymiany danych między procesami praktycznie w czasie rzeczywistym (żeby było maksymalne opóźnienie do 35 milisekund). System i procesor tak szybko tym zarządzają, że jedna taka sekwencja trwa od 0.1 do 0.5 milisekundy. Aż tak szybko nie potrzebuję, więc spowalniam wykonywanie do 16 milisekund za pomocą Sleep(16). Użycie procesora na pierwszym procesie waha się od 5-8%, na drugim prawie 0%. Zapomniałem wspomnieć, że do funkcjonalności Shared File Mapping (współdzielona część RAMu reprezentowana przez plik) użyłem funkcje Windowsa: CreateFile + CreateFileMapping + MapViewOfFile.

Zainteresowaliście mnie również tematem potoków. Czy w nich jest już wbudowany mechanizm wymiany danych na zasadzie, że pierwszy proces zapisuje coś, drugi czeka i pierwszy notyfikuje o tym zapisie drugi proces w sposób blokujący?

0
mj25 napisał(a):

W tych pętlach jest używany wspomniany wcześniej mutex, który przejmuje locka, wykonuje operacje i releasuje locka. Moim celem było uzyskanie wymiany danych między procesami praktycznie w czasie rzeczywistym (żeby było maksymalne opóźnienie do 35 milisekund). System i procesor tak szybko tym zarządzają, że jedna taka sekwencja trwa od 0.1 do 0.5 milisekundy. Aż tak szybko nie potrzebuję, więc spowalniam wykonywanie do 16 milisekund za pomocą Sleep(16).

dzieloną pamięć używa się gdy zależy nam na maksymalnej prędkości, np bazy danych łączą się w ten sposób z lokalnym klientem; robiąc tak częste locki to praktycznie nie ma sensu. Jeśli wystarcza ci aktualizacja co 16 ms to równie dobrze możesz się łączyć po tcp i będziesz miał zapewnioną synchronizację. Polecam po prostu użycie named pipe'a

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.