Witam,
staram się napisać program rozwiązujący pewną odmianę problemu producentów i konsumentów, którą sobie wymyśliłem na zaliczenie przedmiotu "Programowanie rozproszone", przy czym założenia są następujące:

  • "Produkowane" są atomy H, C, S, O przez jednego producenta, po kolei, w pętli nieskończonej;
  • Atomy są przechowywane w magazynie (buforze) posiadającym określoną pojemność, jest to sekcja krytyczna;
  • Producent dodaje w pętli while(true) elementy do magazynu do czasu aż zostanie osiągnięta określona pojemność, następnie czeka aż konsument pobierze elementy, żeby znowu móc zacząć dodawać;
  • Konsument w pętli nieskończonej losuje sobie do produkcji jedną z czterech cząsteczek: H2O, CO2, SO2, H2S;
  • Po pobraniu odpowiedniej ilości atomów są one "wiązane" i losowana jest kolejna cząsteczka;
  • Jeżeli brak danego atomu to konsument powinien poczekać aż producent go wyprodukuje.
  • Najważniejsze założenie - program ma teoretycznie działać w nieskończoność i być w pełni samowystarczalny, czyli raz uruchomiony powinien działać do zatrzymania przez użytkownika/ubicia procesu itp
    Generalnie problem jest dość klasyczny. Napisałem sobie poniższy kod bufora, który ma "przechowywać" atomy (nie są one oczywiście w żaden sposób przechowywane, jedynie licznik ulega zwiększaniu/zmniejszaniu):
class AtomsBuffer
    /* Bufor przechowuje, określoną w konstruktorze, ilość obiektów.
     * Jeżeli ilość obiektów == 0, dalsze pobieranie jest blokowane.
     * Jeżeil ilość obiektów == maxPojemnosc, dalsze dodawanie jest blokowane. */
    {
        private int bufferSize;         // Maksymalna pojemność bufora
        private int currentCount = 0;   // Aktualne zapełnienie bufora

        private Semaphore mutex = new Semaphore(1, 1);  // Ochrania dostęp do zmiennej currentCount
        private Semaphore full = new Semaphore(1, 1);   // Określa czy bufor został zapełniony, czy są jeszcze wolne miejsca
        private Semaphore empty = new Semaphore(0, 1);  // Określa czy bufor został opróżniony, czy zostały jeszcze jakieś obiekty

        public AtomsBuffer(int bufferSize)
        {
            mutex.WaitOne();
                this.bufferSize = bufferSize;
            mutex.Release();
        }// koniec konstruktora

        public void addOne()
        /* "Umieszcza" jeden element w buforze; zwiększa zapełnienie bufora o jeden */
        {
            while (currentCount == bufferSize)
                full.WaitOne();

            mutex.WaitOne();
                currentCount++;
            mutex.Release();

            if (currentCount == 1)
            {
                try
                {
                    empty.Release();
                }
                catch (Exception e)
                {
                    ;
                }
            }
        } // koniec metody add()

        public void removeOne()
        /* "Pobiera" jeden element z bufora; zmniejsza zapełnienie bufora o jeden */
        {
            while (currentCount == 0)
                empty.WaitOne();

            mutex.WaitOne();
                currentCount--;
            mutex.Release();

            if (currentCount == bufferSize - 1)
            {
                try
                {
                    full.Release();
                }
                catch (Exception e)
                {
                    ;
                }
            }
        }// koniec metody remove()
} // koniec class AtomsBuffer

Zaznaczam od razu, że nie jestem orłem jeżeli chodzi o C#, w zasadzie to mój pierwszy program w tym języku, także pewne konstrukcje są z pewnością dość karkołomne.
Każdy rodzaj atomu posiada swój egzemplarz klasy buforu (czyli mamy 4 bufory) i producent przy tworzeniu atomu wywołuje na rzecz danego egzemplarza AtomsBuffer metodę addOne(), natomiast konsument przy pobieraniu wywołuje metodę removeOne() odpowiednią ilość razy. Dzieje się to we wspomnianej pętli while(true). Obecnie algorytm obsługują 2 wątki, wywoływane w następujący sposób:

 
            SharedDataContainer share = new SharedDataContainer(5); // Dane współdzielone pomiędzy wątkami; zawiera m.in. bufory; jako argument podana jest wielkość bufora
            Producer prod = new Producer();
            Consumer cons = new Consumer();

            Thread prodThread = new Thread(() => prod.start(ref share)); // Uruchamiam w wątku metodę produkującą atomy
            Thread consThread = new Thread(() => cons.start(ref share)); // Uruchamiam metodę pobierającą atomy i tworzącą cząsteczki
            prodThread.Start();
            consThread.Start();

Do momentu zapełnienia któregoś z buforów nawet to działa. Problem pojawia się, gdy chcę dodać atom do pełnego buforu. W tym momencie wątek powinien zaczekać na pobranie atomu i wtedy dodać nowy co się oczywiście nie dzieje. Moje podejrzenia odnośnie przyczyny problemu:
Cały wątek producenta, dodający atomy, zostaje wstrzymany i nic się nie dzieje. W tym czasie wątek konsumenta sobie coś tam pobiera ale trafia na pusty bufor i też zostaje wstrzymany, oczekując na dodanie elementu, które nigdy nie nastąpi. I tak sobie trwają w nieskończoność.

Moje pytanie brzmi - czy jest jakiś w miarę łatwy sposób na rozwiązanie tego problemu? Pomyślałem, że można by każdą operację (add i remove) uruchamiać w osobnym wątku dla każdego z atomów ale nie bardzo wiem jak się do tego zabrać bo musiałbym mieć stale uruchomione 8 wątków do których co jakiś czas przekazywałbym metody do wykonania. Czytałem też na temat bufora cyklicznego tzn. producent by sobie dodawał cały czas w kółko atomy, po przekroczeniu pojemności zaczynał od pierwszego, ale to jest dość brzydkie rozwiązanie moim zdaniem...
Będę wdzięczny za wszelkie wskazówki jak w ogóle podejść do tematu i czy jest to w ogóle wykonalne.
Z góry wielkie dzięki za pomoc!
Paweł