Освободете boost::mutex от деструктора

Тъй като std::vector не е безопасен за нишки, аз се опитвах да създам много проста капсулация около него, което го прави безопасен за нишки.

Това работи доста добре, но има един малък проблем. Когато екземплярът на класа се унищожава и друга нишка все още се опитва да прочете данни от нея, нишката продължава да виси завинаги в boost::mutex::scoped_lock lock(m_mutex);

Как мога да разреша това? Най-добре е просто да отключите mutex, за да може нишката, висяща в него, да продължи да се изпълнява. Не съм дефинирал деструктор, защото досега не беше задължителен.

Ето моя код. Имайте предвид, че има повече методи от показаните тук, те са опростени.

template<class T>
class SafeVector 
{
    public:
    SafeVector();
    SafeVector(const SafeVector<T>& other);

    unsigned int size() const;
    bool empty() const;

    void clear();
    T& operator[] (const unsigned int& n);

    T& front();
    T& back();

    void push_back(const T& val);
    T pop_back();

    void erase(int i);

    typename std::vector<T>::const_iterator begin() const;
    typename std::vector<T>::const_iterator end() const;

    const SafeVector<T>& operator= (const SafeVector<T>& other);

    protected:
    mutable boost::mutex m_mutex;
    std::vector<T>  m_vector;

};

template<class T>
SafeVector<T>::SafeVector()
{

}

template<class T>
SafeVector<T>::SafeVector(const SafeVector<T>& other)
{
    this->m_vector = other.m_vector;
}

template<class T>
unsigned int SafeVector<T>::size() const
{
    boost::mutex::scoped_lock lock(m_mutex);
    return this->m_vector.size();
}

template<class T>
bool SafeVector<T>::empty() const
{
    boost::mutex::scoped_lock lock(m_mutex);
    return this->m_vector.empty();
}

template<class T>
void SafeVector<T>::clear()
{
    boost::mutex::scoped_lock lock(m_mutex);
    return this->m_vector.clear();
}

template<class T>
T& SafeVector<T>::operator[] (const unsigned int& n)
{
    boost::mutex::scoped_lock lock(m_mutex);
    return (this->m_vector)[n];
}

template<class T>
T& SafeVector<T>::front()
{
    boost::mutex::scoped_lock lock(m_mutex);
    return this->m_vector.front();
}

template<class T>
T& SafeVector<T>::back()
{
    boost::mutex::scoped_lock lock(m_mutex);
    return this->m_vector.back();
}

template<class T>
void SafeVector<T>::push_back(const T& val)
{
    boost::mutex::scoped_lock lock(m_mutex);
    return this->m_vector.push_back(val);
}

template<class T>
T SafeVector<T>::pop_back()
{
    boost::mutex::scoped_lock lock(m_mutex);
    T back = m_vector.back();
    m_vector.pop_back();
    return back;
}

template<class T>
void SafeVector<T>::erase(int i)
{
    boost::mutex::scoped_lock lock(m_mutex);
    this->m_vector.erase(m_vector.begin() + i);
}

template<class T>
typename std::vector<T>::const_iterator SafeVector<T>::begin() const
{
    return m_vector.begin();
}

template<class T>
typename std::vector<T>::const_iterator SafeVector<T>::end() const
{
    return m_vector.end();
}

Редактиране Трябва да променя дефиницията си. Контейнерът очевидно не е безопасен за нишки, както беше посочено по-горе. Не трябва да го прави - дори номенклатурата да е подвеждаща. Разбира се, можете да правите неща с него, които изобщо не са безопасни за нишки! Но само една нишка пише в контейнера, 2 или 3 четат от нея. Работи добре, докато не опитам да спра процеса. Трябва да кажа, че монитор би бил по-добър. Но времето изтича и не мога да променя това дотогава.

Всяка идея се оценява! Благодаря и поздрави.


person Atmocreations    schedule 14.01.2012    source източник
comment
Мисля, че имате проблеми с нишките на по-високо ниво. Изглежда като грешка за нишка 1 да мисли, че е законно да унищожи обект, към който нишка 2 има препратка и вярва, че може да чете от него. Какво ще стане, ако нишка 1 действително успее да унищожи напълно обекта и след това нишка 2 се опита да чете от него?   -  person CB Bailey    schedule 14.01.2012
comment
Не би ли трябвало конструкторът за копиране също да заключи вектора other?   -  person Kerrek SB    schedule 14.01.2012
comment
Ще ми е интересно да видя и реализацията на оператора за присвояване на копие. Би било лесно да го сбъркате грандиозно.   -  person CB Bailey    schedule 14.01.2012
comment
Не виждам какво е безопасно в този контейнер. Вие свободно раздавате препратки на итератори към елементи, като същевременно позволявате операции за мутиране на самия контейнер - тъй като ги пазите, вие очевидно очаквате множество нишки да мутират контейнерите едновременно, което обезсилва всички итератори и препратки. Изберете: или направете напълно паралелен контейнер, който позволява достъп до елементи само чрез копиране, или просто внимавайте как използвате контейнера.   -  person Kerrek SB    schedule 14.01.2012
comment
Това не е безопасността на нишката. std::vector не е безопасен за нишки поради причина. Безопасните за нишки контейнери обикновено са неизменни. Нещо повече, дори за обекти, безопасни за нишки (напр. std::atomic‹int› и самият boost::mutex) конструирането и унищожаването не са безопасни за нишки.   -  person Yakov Galka    schedule 14.01.2012
comment
Винаги има само една нишка, която променя контейнера и неговото съдържание. Другите нишки само четат данните от него. И докато върху данните се използва итератор, данните никога не се променят от никого. Някой има ли идея за освобождаване на ключалката от резба, която не я държи?   -  person Atmocreations    schedule 14.01.2012
comment
@Atmocreations: Не, няма начин да освободите заключване на mutex от нишка, различна от тази, която я държи. Какво би се случило с изпълнението на нишката, която държеше ключалката? Ако сте показали пълен компилируем пример за това как се опитвате да използвате вашия вектор, тогава би било възможно да ви дам малко помощ за най-добрия начин за решаване на действителния ви проблем.   -  person CB Bailey    schedule 14.01.2012
comment
@ybungalobill Това очевидно е преувеличено какво представляват безопасните за нишки класове. Общият модел на променливи нишково-безопасни структури от данни е този на монитор ( което предхожда изобретяването на нишките с няколко десетилетия). Класът под ръка наистина е основен проблем с нишката!   -  person Dietmar Kühl    schedule 14.01.2012
comment
@Atmocreations Не трябва да имате никоя нишка, задържаща ключалка за дълго време, освен ако не можете да приемете, че само тази нишка има някакъв напредък. От това звучи, че може да искате да разгледате условни променливи, защото това е начинът да уведомите друга нишка, че е време да продължите по някакъв начин.   -  person Dietmar Kühl    schedule 14.01.2012
comment
@DietmarKühl: Напълно грешно си прочел коментара. Имах две твърдения: 1) относно неизменността: безопасните за нишки контейнери са обикновено неизменни и 2) относно безопасните за нишки класове като цяло: дори за безопасни обекти...   -  person Yakov Galka    schedule 14.01.2012
comment
@DietmarKühl: Добре, знам това. И аз не държа ключалката за дълго време. Операциите по контейнера не отнемат много време. Имам обект A, съхраняващ някои SafeVectors. Нишка (не същата, която създаде обект A) променя данните в тези SafeVectors по време на цикъл. Друг чете от тях. Сега, когато искам да затворя целия процес, изглежда, че една нишка винаги виси в ключалката, всъщност предотвратявайки A да бъде унищожен.   -  person Atmocreations    schedule 14.01.2012
comment
@Atmocreations Е, преди да можете да започнете да унищожавате опасния за нишки SafeVector, трябва да сте сигурни, че никой не се опитва да го използва. Тоест всеки, който започне да унищожава обекта, трябва да знае, че никоя друга нишка не прави достъп до тази структура от данни. Съответна индикация, напр. преброяване на препратки, може да се съхранява в клас, но контролът на този обект, който се изключва, е нещо, което трябва да имате във всеки клас: когато унищожаването започне, не можете да имате достъп до него.   -  person Dietmar Kühl    schedule 14.01.2012
comment
В зависимост от вашата среда може да искате да опитате <concurrent_vector>, който е наличен във Visual Studio от 2010 IIRC.   -  person Xeo    schedule 15.01.2012
comment
Използвам gcc на linux. Така че няма Visual Studio. Но все пак благодаря.   -  person Atmocreations    schedule 15.01.2012


Отговори (1)


РЕДАКТИРАНЕ: Актуализирано, за да бъде по-пълен пример.

Други посочиха недостатъците с вашата „безопасност на нишките;“ Ще се опитам да отговоря на въпроса ви.

Единственият правилен начин да направите това, което сте си поставили за цел, е да се уверите, че всички ваши нишки са затворени, преди да опитате да унищожите самия вектор.

Често срещан метод, който съм използвал, е просто да използвам RAII, за да определя реда на изграждане и унищожаване.

void doSomethingWithVector(SafeVector &t_vec)
{
  while (!boost::this_thread::interruption_requested())
  {
    //operate on t_vec
  }
}

class MyClassThatUsesThreadsAndStuff
{
  public:
    MyClassThatUsesThreadsAndStuff()
      : m_thread1(&doSomethingWithVector, boost::ref(m_vector)),
        m_thread2(&doSomethingWithVector, boost::ref(m_vector))
    {
      // RAII guarantees that the vector is created before the threads
    }

    ~MyClassThatUsesThreadsAndStuff()
    {
      m_thread1.interrupt();
      m_thread2.interrupt();
      m_thread1.join();
      m_thread2.join();
      // RAII guarantees that vector is freed after the threads are freed
    }

  private:
    SafeVector m_vector;
    boost::thread m_thread1;
    boost::thread m_thread2;
};

Ако търсите по-пълна структура на данни, безопасна за нишки, която позволява множество читатели и писатели, не се колебайте да проверите опашка, която написах, използвайки нишки за усилване преди известно време.

http://code.google.com/p/crategameengine/source/browse/trunk/include/mvc/queue.hpp

person lefticus    schedule 15.01.2012
comment
Благодаря ви за отговора. знам за RAII. проблемът тук е, че присъединяванията не винаги се връщат... ако пренебрегна вашата реализация на опашката, принципът ми изглежда същият: поставяне на scoped_lock навсякъде, където извършвате операции в контейнера - с изключение на това, че в моята реализация липсва частта за анулиране. - person Atmocreations; 15.01.2012
comment
Имам блокиращи четения в моя клас Queue, поради което е необходимо анулирането. Нямате никакъв блок и изчакайте характеристиките на данните на вашия клас. Това ме кара да предположа, че действителната грешка е, че Mutex, до който се опитвате да получите достъп, е бил унищожен преди / по време на заключване. Единственият начин това да се случи е, ако векторът бъде унищожен преди нишката. С моя пример по-горе вече имате пример за анулиране, както и гаранции, че мютексът не може да бъде унищожен, преди да е изтекъл полезният му живот. - person lefticus; 15.01.2012