Szukaj…


Wprowadzenie

Jedną z najtrudniejszych rzeczy w C i C ++ jest zarządzanie zasobami. Na szczęście w C ++ mamy wiele sposobów projektowania zarządzania zasobami w naszych programach. Ten artykuł ma na celu wyjaśnienie niektórych idiomów i metod używanych do zarządzania przydzielonymi zasobami.

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
}
C ++ 11

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.

C ++ 11

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
}


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow