C++
Zarządzanie zasobami
Szukaj…
Wprowadzenie
Pozyskiwanie zasobów to inicjalizacja
Resource Acquisition Is Initialization (RAII) jest powszechnym idiomem w zarządzaniu zasobami. W przypadku pamięci dynamicznej wykorzystuje inteligentne wskaźniki do zarządzania zasobami. Podczas korzystania z RAII nabyty zasób natychmiast otrzymuje własność inteligentnego wskaźnika lub równoważnego menedżera zasobów. Dostęp do zasobu jest możliwy tylko za pośrednictwem tego menedżera, dzięki czemu menedżer może śledzić różne operacje. Na przykład std::auto_ptr
automatycznie zwalnia odpowiedni zasób, gdy wypadnie poza zakres lub zostanie w inny sposób usunięty.
#include <memory>
#include <iostream>
using namespace std;
int main() {
{
auto_ptr ap(new int(5)); // dynamic memory is the resource
cout << *ap << endl; // prints 5
} // auto_ptr is destroyed, its resource is automatically freed
}
Głównym problemem std::auto_ptr
jest to, że nie można go skopiować bez przeniesienia własności:
#include <memory>
#include <iostream>
using namespace std;
int main() {
auto_ptr ap1(new int(5));
cout << *ap1 << endl; // prints 5
auto_ptr ap2(ap1); // copy ap2 from ap1; ownership now transfers to ap2
cout << *ap2 << endl; // prints 5
cout << ap1 == nullptr << endl; // prints 1; ap1 has lost ownership of resource
}
Z powodu tej dziwnej semantyki kopiowania, std::auto_ptr
nie może być używane między innymi w kontenerach. Powodem tego jest dwukrotne usunięcie pamięci: jeśli istnieją dwa auto_ptrs
posiadające ten sam zasób, obaj próbują go zwolnić, gdy zostaną zniszczone. Uwolnienie już uwolnionego zasobu może generalnie powodować problemy, dlatego ważne jest, aby temu zapobiec. Jednak std::shared_ptr
ma metodę, aby tego uniknąć, nie przenosząc własności podczas kopiowania:
#include <memory>
#include <iostream>
using namespace std;
int main() {
shared_ptr sp2;
{
shared_ptr sp1(new int(5)); // give ownership to sp1
cout << *sp1 << endl; // prints 5
sp2 = sp1; // copy sp2 from sp1; both have ownership of resource
cout << *sp1 << endl; // prints 5
cout << *sp2 << endl; // prints 5
} // sp1 goes out of scope and is destroyed; sp2 has sole ownership of resource
cout << *sp2 << endl;
} // sp2 goes out of scope; nothing has ownership, so resource is freed
Muteksy i bezpieczeństwo wątków
Problemy mogą wystąpić, gdy wiele wątków próbuje uzyskać dostęp do zasobu. Dla prostego przykładu, załóżmy, że mamy wątek, który dodaje jeden do zmiennej. Robi to najpierw czytając zmienną, dodając do niej jedną, a następnie przechowując ją z powrotem. Załóżmy, że inicjalizujemy tę zmienną na 1, a następnie tworzymy dwie instancje tego wątku. Po zakończeniu obu wątków intuicja sugeruje, że ta zmienna powinna mieć wartość 3. Jednak poniższa tabela ilustruje, co może pójść nie tak:
Wątek 1 | Wątek 2 | |
---|---|---|
Krok czasu 1 | Odczytaj 1 ze zmiennej | |
Krok czasu 2 | Odczytaj 1 ze zmiennej | |
Krok czasu 3 | Dodaj 1 plus 1, aby uzyskać 2 | |
Krok czasu 4 | Dodaj 1 plus 1, aby uzyskać 2 | |
Krok czasu 5 | Przechowuj 2 w zmiennej | |
Krok czasu 6 | Przechowuj 2 w zmiennej |
Jak widać, pod koniec operacji 2 znajduje się w zmiennej zamiast 3. Powodem jest to, że Wątek 2 odczytał zmienną przed zakończeniem jej aktualizacji. Rozwiązanie? Muteksy.
Mutex (portmanteau of mut clal ex clusion) to obiekt zarządzania zasobami zaprojektowany w celu rozwiązania tego rodzaju problemu. Gdy wątek chce uzyskać dostęp do zasobu, „nabywa” muteks zasobu. Po uzyskaniu dostępu do zasobu wątek „uwalnia” muteks. Podczas nabywania muteksu wszystkie wywołania muteksu nie będą zwracane, dopóki muteks nie zostanie zwolniony. Aby lepiej to zrozumieć, pomyśl o muteksie jako o linii oczekiwania w supermarkecie: wątki idą w szeregu, próbując zdobyć muteks, a następnie czekając, aż wątki przed nimi zakończą się, a następnie wykorzystując zasoby, a następnie wychodząc wypuszczając muteks. Byłoby pełne pandemonium, gdyby wszyscy próbowali uzyskać dostęp do zasobu na raz.
std::mutex
to implementacja std::mutex
C ++ 11.
#include <thread>
#include <mutex>
#include <iostream>
using namespace std;
void add_1(int& i, const mutex& m) { // function to be run in thread
m.lock();
i += 1;
m.unlock();
}
int main() {
int var = 1;
mutex m;
cout << var << endl; // prints 1
thread t1(add_1, var, m); // create thread with arguments
thread t2(add_1, var, m); // create another thread
t1.join(); t2.join(); // wait for both threads to finish
cout << var << endl; // prints 3
}