[C++/Qt4/WinAPI] Mutex i sekcja krytyczna, synchronizacja

0

Witam. Piszę w C++ pewną bibliotekę. Głównym jej założeniem jest to, aby była ona niezależna od innych bibliotek (oprócz biblioteki standardowej C++). Ostatnio zabrałem się za pisanie klasy obsługującej wątki - Thread. Ten fragment jest OK, ale problem pojawił się przy klasach odpowiedzialnych za synchronizację wątków. Napisałem klasę CriticalSection oraz Mutex (za semafory zabiorę się dopiero po ukończeniu muteksów).

Tu jest miejsce na pierwsze pytanie: czy w danym procesie może istnieć kilka/kilkanaście sekcji krytycznych (nie chodzi mi o sytuację synchronizacji dwóch wątków za pomocą dwóch sekcji, gdyż wiadomo, że to nie ma sensu :-) ) oraz czy to nie jest w jakiś sposób "niebezpieczne" (albo może lepiej bez cudzysłowie... ;] ).

Drugi problem dotyczy muteksów. Porównałem to co oferuje Windows z tym co daje biblioteka Qt4. I doszedłem do wniosku, że klasa QMutex działa o wiele sprawniej niż funkcje Windowsa. Czy ktoś mógłby mi odpowiedzieć na pytanie: dlaczego? Qt jest biblioteką wielopratformową więc może ona wcale nie korzysta z funkcji CreateMutex(), CloseHandle() itd. ? A jeśli tak rzeczywiście miałoby być, to jaka jest alternatywa?

Jeśli chcecie sprawdzić obie wersje (funkcje Windowsa i klasa QMutex) u siebie, to zamieszczę tutaj mój kod, żeby się nie przemęczać ;]. Program uruchamia dwa wątki i w jednym z nich inkrementuje a w drugim dekrementuje globalną zmienną.

#include <iostream>
#include <windows.h>
#include <QMutex>
using namespace std;


int a = 0;
DWORD id1, id2;
HANDLE thread1, thread2;
HANDLE mutex;
QMutex qmutex;

//#define lock_by_qmutex  // <-- usuń komentarz aby użyć klasy QMutex

// niekiedy makrodefinicje ułatwiają życie :D
#ifdef lock_by_qmutex
#define lock qmutex.lock();
#define unlock qmutex.unlock();
#else
#define lock WaitForSingleObject(mutex, INFINITE);
#define unlock ReleaseMutex(mutex);
#endif

DWORD WINAPI threadMain(void*)
{
  for(unsigned u = 0; u < 1000000; ++u)
  {
    lock;  // blokada zasobów
    if(GetCurrentThreadId() == id1) // żeby nie pisać osobno ciał obu funkcji
      ++a;
    else --a;
    unlock;  // odblokowanie zasobów
  }

  return 0;
}


int main(int argc, char* argv[])
{
  // "inicjalizacja czasomierza"
  DWORD time1 = GetCurrentTime();

  // utworzenie mutex'u i dwóch wątków
  mutex = CreateMutex(0, false, 0);
  thread1 = CreateThread(0, 0, threadMain, 0, 0, &id1);
  thread2 = CreateThread(0, 0, threadMain, 0, 0, &id2);

  cout << "Czekam\n";

  // czekanie na zakończenie pracy obu wątków
  WaitForSingleObject(thread1, INFINITE);
  WaitForSingleObject(thread2, INFINITE);

  // wynik zgodnie z oczekiwaniami jest zerem
  cout << "a = " << a << endl;

  // różnicę w czasie wykonywania programu w obu wariantach
  // będzie widać gołym okiem, ale tak dla porządku jeszcze
  // krótkie info nt. czasu trwania.
  DWORD time2 = GetCurrentTime();
  double time = (time2-time1)/1000.0;
  cout << "Wykonano po " << time << endl;

  cout << "Wcisnij dowolny klawisz.";
  cin.get();

  return 0;
}
0

I doszedłem do wniosku, że klasa QMutex działa o wiele sprawniej niż funkcje Windowsa.

Że niby jak, nakładka na WinAPI, jaką jest niewątpliwie biblioteka Qt, działa szybciej niż gołe WinAPI? :>

0

Dokładnie. Skompiluj sobie mój kod. Raz ze zdefiniowanym makrem lock_by_qmutex a raz bez. Zobaczysz różnicę.

0

Nie skompiluje, ponieważ nie używam Qt.

W sumie mógłbyś podać różnice czasowe, bo podejrzewam, że nie są zbyt wielkie.

0

Są dosyć... hmm... zauważalne. Ten kod z użyciem klasy QMutex wykonuje się ok. 0,5 sekundy, a bez niej aż. 6,5 sekundy!
Natomiast po dodaniu jednego zera do licznika pętli czasy urosły do 14 sekund przy użyciu QMutex i prawie minuty (56sek) z WinAPI.
To są oczywiście wyniki przykładowych uruchomień, wyniki wahają się wokół tych liczb, ale różnica w szybkości działania jest dość znacząca. Dlatego zależy mi na ominięciu mutexów z WinAPI.

Próbowałem zrealizować swój mutex z pomocą sekcji krytycznej. Wtedy kod wykonywał się nawet szybciej niż QMutex. Ale musiałbym wiedzieć czy jest dozwolone utworzenie kilku osobnych sekcji krytycznych (po jednej dla jednego mutex'u).

0

Przejrzałem sobie źródła Qt4, i z tego co wyczytałem, QMutex działa w oparciu o eventy, a nie systemowe muteksy. Pytanie, czy takie udawane muteksy dają 100% pewności. Wątpię. Zapewne nie bez wpływu są czasy między wywołaniami funkcji systemowych. W wersji WinAPI po ReleaseMutex natychmiast następuje WaitForSingleObject. W wersji Qt jest jeszcze kod, który daje drugiemu wątkowi więcej czasu na przejęcie muteksa.

Próbowałem zrealizować swój mutex z pomocą sekcji krytycznej.

Do lokalnej synchronizacji preferowane są sekcje krytyczne, ponieważ są szybsze w porównaniu do muteksów, które są obiektami globalnymi i służą raczej do synchronizacji międzyprocesowej.

0

Dzięki, teraz rozumiem dlaczego są takie różnice. A propos sekcji krytycznych. Czy można z nich korzystać tak jak z muteksów? Tzn. utworzyć tyle sekcji ile jest potrzebne i używać odpowiednich sekcji w odpowiednich wątkach?

0

W międzyczasie jeszcze troche pokombinowałem ze zwykłymi muteksami. Zamiast napisać po prostu WaitForSingleObject(mutex, INFINITE), napisałem tam taką pętlę

while(WaitForSingleObject(mutex, 0) != WAIT_OBJECT_0)
      SwitchToThread();
/// cośtam cośtam...
ReleaseMutex(mutex);

Dzięki temu program wykonuje się dwa razy krócej. Wydaje mi się, że to jest bezpieczne rozwiązanie i nie powinno być z tym żadnych problemów. Jak sądzisz?

0

Czy można z nich korzystać tak jak z muteksów? Tzn. utworzyć tyle sekcji ile jest potrzebne i używać odpowiednich sekcji w odpowiednich wątkach?

Tak, po to one w końcu są.

Jak sądzisz?

To INFINITE w drugim parametrze jest właśnie po to, żebyś nie musiał pisać własnych pętli. A że szybciej działa, no cóż, zapewne z podobnych powodów co wersja Qt.

0

OK. Wiem już to co chciałem wiedzieć. Generalnie BARDZO zależy mi na szybkości działania, stąd to kombinowanie z pętlami ;]. Dzięki za pomoc.

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