Pytanie Przenieś konstruktor dla std :: mutex


Wiele klas w bibliotece standardowej c ++ posiada teraz konstruktory ruchu, na przykład -

thread::thread(thread&& t)

Ale wydaje się, że std :: mutex nie. Rozumiem, że nie można ich kopiować, ale wydaje się sensownym, aby móc zwrócić np. Funkcję "make_mutex". (Nie mówię, że to przydatne, tylko że ma sens)

Czy istnieje jakiś powód, dla którego std :: mutex nie ma konstruktora ruchu?


30
2017-09-26 15:07


pochodzenie


Mam tutaj ten sam problem, ponieważ w moim kodzie muszę zablokować dostęp do jednej metody, aż do końca. Niestety, obiekt, na którym działają metody, najlepiej jest stworzyć w fabryce, a z powodu tego samego ograniczenia nie mogę po prostu przenieść utworzonego "funktora" z metody fabrycznej. Ten wątek naprawdę zachęca mnie do eksperymentowania std::future zamiast tego jako model podziału. - starturtle


Odpowiedzi:


Cóż ... głównie dlatego, że nie sądzę, że powinni ruszaj się. Dosłownie.

W niektórych systemach operacyjnych muteks może zostać zamodelowany jako uchwyt (aby można było go skopiować), ale IIRC to mutex pthreads, który jest manipulowany w miejscu. Jeśli zamierzasz to przenieść, wszystkie wątki bezpieczeństwa odlecą prosto z okna (w jaki sposób inne wątki wiedzą, że muteks właśnie zmienił adres pamięci ...)?


33
2017-09-26 15:10



OK, wydaje mi się, że to ma sens :) Myślałem o obiekcie mutex reprezentującym mutex, abyś mógł przenieść go do innego obiektu, ale jeśli myślisz o tym jako o istota muteks to nie ma sensu go przenosić, masz rację, - jcoder
@JohnB: Możesz robić, co chcesz unique_lock<mutex>. Jest to blokowany obiekt (ma API muteksa) i można go przenosić. Och, ale nie naraziłbym go na wiele wątków naraz. Ale możesz mieć jeden w każdym wątku, który odwołuje się do tego samego mutex. - Howard Hinnant
To wyjaśniło mi dobrze sprawę. Implikacją tego jest to, że nie możesz (i nie powinieneś) umieścić muteksa bezpośrednio w kontenerze. - T.E.D.
@PRZETRZĄSAĆ. Z wyjątkiem sytuacji, gdy możesz zagwarantować, że miejsce przechowywania nigdy się nie poruszy (std::array<std::mutex, 100> jest kontenerem. Więc jest boost::intrusive::slist<std::mutex, ...> :)). Ale poza tym tak. Możesz zawsze zhakuj go za pomocą shared_ptr<std::mutex> rodzaju. - sehe
@sehe - zacząłem pytanie na ten temat: stackoverflow.com/questions/27276555/... - T.E.D.


Pamiętaj, że C ++ bierze na serce filozofię "nie płać za to, czego nie używasz". Rozważmy na przykład wyimaginowaną platformę, która używa typu nieprzezroczystego do reprezentowania muteksu; nazwijmy ten typ mutex_t. Jeśli interfejs do działania na tym muteksie używa mutex_t* jako argumenty, na przykład podobne void mutex_init(mutex_t* mutex); aby "zbudować" muteks, może się zdarzyć, że adres muteksu jest używany do jednoznacznej identyfikacji muteksa. Jeśli tak, to znaczy, że mutex_t nie można kopiować:

mutex_t kaboom()
{
    mutex_t mutex;
    mutex_init(&mutex);
    return mutex; // disaster
}

Nie ma tu żadnej gwarancji mutex_t mutex = kaboom(); że &mutex ma taką samą wartość jak &mutex w bloku funkcyjnym.

Kiedy nadejdzie dzień, który wykonawca chce napisać std::mutex dla tej platformy, jeśli wymagania są tego rodzaju, ruchome, to znaczy wewnętrzne mutex_t musi być umieszczony w pamięci przydzielanej dynamicznie wraz ze wszystkimi powiązanymi karami.

Z drugiej strony, choć teraz std::mutex jest nie ruchomy, bardzo łatwo jest "zwrócić" jeden z funkcji: return an std::unique_ptr<std::mutex> zamiast. To nadal płaci koszty alokacji dynamicznej ale tylko w jednym miejscu. Cały inny kod, który nie musi się przesuwać std::mutex nie musi tego płacić.

Innymi słowy, ponieważ przenoszenie muteksu nie jest podstawową operacją tego, czym jest muteks, niewymagającym std::mutex ruchomy nie usuwa żadnej funkcjonalności (dzięki nieruchome  T => ruchomy  std::unique_ptr<T> transformacja) i poniesie minimalne koszty ogólne w porównaniu bezpośrednio z rodzimym typem.


std::thread mogło być podobnie określone, aby nie być ruchomym, co spowodowałoby typowy czas życia jako taki: bieg (powiązany z wątkiem wykonania), po wywołaniu wartościowego konstruktora; i odłączone / połączone (powiązane z żadnym wątkiem wykonania), po wywołaniu join lub detach. Jak rozumiem to std::vector<std::thread> nadal byłby użyteczny, ponieważ byłby typ EmplaceConstructible.

edytuj: Niepoprawnie! Typ nadal musiałby być ruchomy (w przypadku ponownego przydzielania). Dla mnie jest to wystarczająco racjonalne uzasadnienie: jest to typowe std::thread do pojemników takich jak std::vectori std::deque, więc funkcjonalność jest mile widziany dla tego typu.


16
2017-09-26 20:01