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:
- Pamięć współdzielona
- Semafory
- 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.