Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > многопоточность: Продавец-Продавец


Автор: cupper 14.2.2011, 15:41
есть классическая задача для многопоточности Продавец-покупатель. Она много где описывается, но обычно не где не описывается решение для агресивной политики доступа, т.е. когда пауз между продай - купи нету, и каждый наровит впихнуть/взять как можно больше. 


У меня же задача, чуть более сложней. Когда два разных потока пытаются получить доступ к одному и томуже ресурсу притом один (1) чисто на запись, второй (2) на чтение и запись. ведущий поток это (1), так как если не будет данных  то (2) читать ничего не сможет. Это легко решается с помощью condition_variable.

А вот как организовать возможность разделение доступа к ресурсу между (1) и (2) я не могу понять. 

Должны корректно выполнятся следующие ситуации:
1) если (1) постоянно пишет в ресурс, то как только (2) попробует чтото прочитаться из ресурса (и не сможет этого сделать) то последушию запросы (1) временно блокируются что бы (2) смог получить доступ к ресурсу
2) если (2) постоянно читает/пишет в ресурс, то как только (1) попробует чтото записать в ресурса (и не сможет этого сделать) то последушию запросы (2) временно блокируются что бы (1) смог получить доступ к ресурсу

При этому (1) и (2) не должны покадить то место в котором они не смогли получить доступ, а должны доджаться когда они этот доступ получат.

Это примерно выглядет так
Код

template <typename T>
class SharedBuffer
{
    template<typename Y>
    friend std::ostream& operator<<(std::ostream& os, const SharedBuffer<Y>&);
public:
    SharedBuffer(void);
    ~SharedBuffer(void);
    // Добавляем в конец хранилища новые данные
    void push(std::list<T>&);
    // Возвращает копию первого элемента, и удаляет его из хранилища
    T pop();
private:
    std::list<T> _data;
    // блокировка на чтение/запись
    boost::mutex _mtx;
    // есть что прочитать !
    boost::condition_variable _cond;
};

template <typename T>
void SharedBuffer<T>::push(std::list<T>& buff)
{
    boost::lock_guard<boost::mutex> lock(this->_mtx);
    this->_data.insert(_data.end(), buff.begin(), buff.end());
    this->_cond.notify_one();
}

template <typename T>
T SharedBuffer<T>::pop()
{
    boost::unique_lock<boost::mutex> lock(this->_mtx);
    this->_cond.wait(lock, [this] {
        return !this->_data.empty();
    });

    T t;
    if(!this->_data.empty())
    {
        t = this->_data.front();
        this->_data.pop_front();
        return t;
    }
    // сюда ни когда не дойдект
    throw "Попытка получить данные из пустого хрпанилица";
    return T();
}



Этот работает для случая когда есть искуственная пауза между вызовами push->push и pop->pop
т.е. для случая
Код

поток 1:
while(1)
     x.push()

поток 2:
while(1)
    x.pop()


вероятность что pop() сможет влесть в работу потока1 крайне мала.

Автор: cupper 14.2.2011, 16:26
придумал такую штуку
Код

template <typename T>
void SharedBuffer<T>::push(std::list<T>& buff)
{
    if(this->_giveMeRead)
        boost::this_thread::yield();
    boost::lock_guard<boost::mutex> lock(this->_mtx);
    //threadSleep(2);
    this->_data.insert(_data.end(), buff.begin(), buff.end());
    this->_cond.notify_one();
}

template <typename T>
T SharedBuffer<T>::pop()
{
    this->_giveMeRead = true;
    boost::unique_lock<boost::mutex> lock(this->_mtx);
    this->_cond.wait(lock, [this] {
        return !this->_data.empty();
    });
    this->_giveMeRead = false;
    //threadSleep(1);
    T t;
    if(!this->_data.empty())
    {
        t = this->_data.front();
        this->_data.pop_front();
        return t;
    }
    // сюда ни когда не дойдект
    throw "Попытка получить данные из пустого хрпанилица";
    return T();
}


При каждой попытке чтения (pop) мы пишем в буле переменную true свидетельствующую о том что нужно прочитать данные. Далее все как обычно пробуем залочить мютекс, и если не получается, то значит выполняется поток (1).
В этом самом потоке (1) вызывается операция push в которой первый делом проверяется, а нге было ли попыток ЧИТАТЬ, и если была (значение ture у переменной) то мы для текущего потока отдаем остаток времение yield().

Единственно что меня смущает так это что я не совсем понимаю как работает yield() и всегда ли будет поведение которое я описал выше ?

Автор: azesmcar 15.2.2011, 08:34
Цитата(cupper @  14.2.2011,  15:41 Найти цитируемый пост)
вероятность что pop() сможет влесть в работу потока1 крайне мала. 

С чего это ей быть крайне малой? Назови причину, по которой у потока, вызывающего push вдруг появился приоритет. Они одинаково блокируют тут же mutex, почему именно у pop нет шансов?
После push()-а первый поток освобождает мьютекс, тем самым давая возможность второму потоку вызвать pop().

Цитата(cupper @  14.2.2011,  15:41 Найти цитируемый пост)
    // сюда ни когда не дойдект
    throw "Попытка получить данные из пустого хрпанилица";
    return T();

это зачем еще?

Автор: cupper 15.2.2011, 10:21
Цитата(azesmcar @ 15.2.2011,  08:34)
Цитата(cupper @  14.2.2011,  15:41 Найти цитируемый пост)
вероятность что pop() сможет влесть в работу потока1 крайне мала. 

С чего это ей быть крайне малой? Назови причину, по которой у потока, вызывающего push вдруг появился приоритет. Они одинаково блокируют тут же mutex, почему именно у pop нет шансов?
После push()-а первый поток освобождает мьютекс, тем самым давая возможность второму потоку вызвать pop()

т.е. даже при таком раскладе
Код

Поток1:
while(1)
    push()

Поток2
...
pop()
...


В потоке2 операция pop() особо долго не будет задерживаться из за того что в первом потоке постоянно занимается доступ ?


Я видать попутал эту задачу с задачей писатель-много читателей, когда читатель действительно может не дождаться доступа к ресурсу из за того что к общей куче читателей постоянно могут прибавляться новые...

Цитата

это зачем еще?

это вылезло из за 
Код

if(!this->_data.empty())
    {

но вы правы, это излишне.

Автор: azesmcar 15.2.2011, 10:30
Цитата(cupper @  15.2.2011,  10:21 Найти цитируемый пост)
В потоке2 операция pop() особо долго не будет задерживаться из за того что в первом потоке постоянно занимается доступ ?

cond.wait разблокирует мьютекс на время ожидания (пока не получит notification), в свою очередь push разблокирует его как только закончит свою работу. Т.е. на каждой итерации мьютекс освобождается, давая другому потоку возможность сделать свое дело. Любой, уважающий себя мьютекс должен дать поработать обоим потокам (смотри bounded waiting) smile 

Цитата(cupper @  15.2.2011,  10:21 Найти цитируемый пост)
Я видать попутал эту задачу с задачей писатель-много читателей, когда читатель действительно может не дождаться доступа к ресурсу из за того что к общей куче читателей постоянно могут прибавляться новые...

Там как раз пишущим потокам дается приоритет, так-как их мало.
вот http://www.data-race.com/2011/02/01/readers-writers-%D0%B1%D0%BB%D0%BE%D0%BA%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8/ я описывал.

Добавлено @ 10:30
Цитата(cupper @  15.2.2011,  10:21 Найти цитируемый пост)
это вылезло из за 


проверка
Цитата(cupper @  15.2.2011,  10:21 Найти цитируемый пост)
if(!this->_data.empty())

тоже лишняя.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)