Wątek konsumenta i odczyt asynchroniczny.

0

Jest sobie lista wskaźnikowa, kolejka LIFO elementów przekazywanych z wątków pobocznych do wątku głównego:

Element * volatile lista;

Wątki producentów wrzucają na listę elementy zatrzaskując muteks:

muteks->Zatrzasnij();
element->nastepny = lista;
lista = element;
muteks->Zwolnij();

Konsumentem jest wątek główny, który co każdą główną pętlę programu pobiera wszystkie dostępne elementy:

void Konsumuj()
{
    Element *elementy;

    muteks->Zatrzasnij();
    elementy = lista;
    lista = NULL;
    muteks->Zwolnij();

    while (elementy != NULL) {
        /* ... */
    }
}

int main()
{
    for (;;) {
        /* ... */
        Konsumuj();
        /* ... */
    }
}

Zastanawiam się, czy poprawnym i dobrym rozwiązaniem będzie sprawdzenie, czy lista jest pusta zanim zatrzaśniemy muteks w celu zwiększenia wydajności (lista jest zazwyczaj pusta):

void Konsumuj()
{
    if (lista == NULL) return; // sprawdzamy, czy lista jest pusta bez zatrzaskiwania muteksa

    Element *elementy;

    muteks->Zatrzasnij();
    elementy = lista;
    lista = NULL;
    muteks->Zwolnij();

    while (elementy != NULL) {
        /* ... */
    }
}

Nawet jeśli czasem lista == NULL zwróci wartość inną od prawidłowej to nic się nie powinno stać. Pytanie, czy to przekłamanie może występować notorycznie? Czy volatile rozwiewa wszelkie wątpliwości? Czy może jest to złe rozwiązanie z jakichś innych względów i olać tą krztynę wydajności?
Pytanie zadaję w kontekście wieloplatformowym.

0

Jeśli zależy Ci na "krztynie wydajności" to dużo lepszym rozwiązaniem byłoby wybrać CriticalSection niż Mutex.

Co do rozwiązania powyżej:
Lista będzie pusta, tylko wtedy, kiedy
a) Żaden z wątków pracujących nie rozpoczął jeszcze pracy na liście
b) Któryś z wątków rozpoczął pracę, a lista nie przedstawia jeszcze tego, co powinna

W przypadku a) Mutex i tak będzie zwolniony, więc Zatrzasnij() wiąże się z pomijalnym (a przynajmniej bardzo niskim) kosztem czasowym.
W przypadku b) otrzymujesz przekłamanie, a w następnym obiegu pętli i tak będziesz musiał czekać na muteks.

Chociaż rozwiązanie samo w sobie nie jest błędne (i tak, jeśli nie zależy ci na precyzji można notorycznie "przekłamywać w ten sposób), nie widzę za bardzo gdzie tutaj korzyść ;]

0
4ggr35510n napisał(a):

Jeśli zależy Ci na "krztynie wydajności" to dużo lepszym rozwiązaniem byłoby wybrać CriticalSection niż Mutex.
Muteksy są już "wątkowe" (dla Windowsa jest CriticalSection, jest też implementacja z wykorzystaniem pthread oraz inne).

4ggr35510n napisał(a):

Mutex i tak będzie zwolniony, więc Zatrzasnij() wiąże się z pomijalnym (a przynajmniej bardzo niskim) kosztem czasowym.
A para Zatrzasnij-Zwolnij? Co jeśli żaden inny wątek nie będzie żądać muteksa w tym czasie, czy obydwa wywołania powinny być "szybkie"?

0

volatile nie rozwiewa wszystkich wątpliwości, bo nawet zwykłe przypisanie nie jest atomowe(chyba, że coś mi się pomieszało).
Ja bym zastanowił się nad trochę innym podejściem. Skoro lista jest przeważnie pusta, to sprawdzanie czy coś na niej jest zbyt często może nie mieć sensu. Możesz konsumenta zrobić w osobnym wątku, który czekałby na sygnał i byłby budzony dopiero po dodaniu elementu do listy - wait i notify.

0
byku_guzio napisał(a):

Możesz konsumenta zrobić w osobnym wątku
To nie załatwia sprawy, bo produkty muszą trafić do wątku głównego.

Jest pewien zasób, zbiór elementów, z którego głównie korzysta wątek główny. I z rzadka wątki poboczne muszą dodać element do tego zbioru. Przerzucenie obsługi zbioru na całkowicie osobny wątek będzie wymagało dodatkowej synchronizacji z wątkiem głównym więc to rozwiązanie zupełnie nie ma sensu.

1 użytkowników online, w tym zalogowanych: 0, gości: 1