Реализация поточно-ориентированного универсального стека в C ++ в Linux - PullRequest
5 голосов
/ 26 июня 2009

В одном из недавних интервью меня попросили реализовать универсальный (основанный на i.e.template) стек в C ++ на Linux-машине.
Я быстро придумал следующее (возможно, есть ошибки компиляции).
Я прошел. Интервьюеру, вероятно, что-то понравилось в этой реализации. Может быть, часть дизайна:)
Вот несколько проблем, которые могут возникнуть в этой реализации: -
1. Неправильная реализация для обозначения переполнения / недостаточного заполнения. Нет обработки переполнения, так как я использую вектор STL в качестве базовой структуры данных. Должна ли быть такая обработка? Кроме того, underflow (в Pop ()) возвращает false в качестве возвращаемого значения. Должно ли это быть сделано с помощью исключения?
2. Внедрение рутины PopElem. Является ли приведенная ниже реализация правильной?
3. Нет реального использования верхнего элемента.
4. Лучшее время между началом записи и потоком чтения.

Пожалуйста, оставляйте любые комментарии / предложения / улучшения.
Спасибо.

// Реализация универсального многопоточного стека.

#include<pthread.h>
#include<iostream>
#include<vector>

using namespace std;

template<typename T>
class MyStack
{
public:
//interface
bool Push(T elem);
bool Pop(T& elem);
bool IsEmpty();

//constructor
MyStack() {
pthread_mutex_init(&lock);
top = 0;
}

//destructor
~MyStack() {
pthread_mutex_destroy(&lock);
}

private:
pthread_mutex_t lock;
int top;
vector<T> stack;

bool MyStack::Push(T elem);
bool MyStack::PopElem(T& elem);
}; //end of MyStack

template<typename T>
bool MyStack<T>::Push(T elem)
{
    pthread_mutex_lock(&lock);
    PushElem(elem);
    pthread_mutex_unlock(&lock);
}

template<typename T>
bool MyStack<T>::Pop(T& elem)
{
    pthread_mutex_lock(&lock);
    PopElem(elem);
    pthread_mutex_unlock(&lock);
}

template<typename T>
bool MyStack<T>::PushElem(T elem)
{
    stack.push_back(elem);
     top = stack.size();
}

template<typename T>
bool MyStack<T>::PopElem(T& elem)
{
   if(this.IsEmpty())
   {
        return false;
   }

   elem = stack.back(); //tricky, returns a reference to the last element
   stack.pop_back(); // is elem valid after this ??
   top = stack.size();
   return true;
}      


template<typename T>
bool MyStack<T>::IsEmpty()
{
    return stack.empty();
}


class MyStackTest
{
public:
  void Initialize() {
  pthread_init(&readerT);
  pthread_init(&writerT);
  }

  void Run() {
 pthread_create(writerT,0,writer,0); 
 pthread_create(readerT,0,reader,0);
 pthread_join(&writerT);
 pthread_join(&readerT);
}

private:
pthread_t readerT;
pthread_t writerT;
MyStack<int> stack;

void reader(void);
void writer(void);
};

void MyStackTest::writer() {
  for(int i=0;i<20;i++) {
      stack.Push(i);
      cout<<"\n\t Pushed element: "<<i;
   } //end for
}

void MyStackTest::reader() {
   int elem;
   while(stack.Pop(elem))
   {
     cout<<"\n\t Popped: "<<elem;
   }
}

int main()
{
    MyStackTest Test;

    Test.Run();
}

Ответы [ 7 ]

8 голосов
/ 26 июня 2009

Некоторые проблемы:

  • Я бы реализовал класс Locker для запроса и освобождения мьютекса с помощью RAII
  • Я бы использовал std :: stack
  • Я бы заставил пользователя std :: stack использовать Locker для реализации политики блокировки - наличие стека, который сам блокирует себя, - плохой дизайн, поскольку стек не может знать, как его использовать
2 голосов
/ 27 июня 2009

Нейл, Онбионе:
Попытка использования RAII для блокировки мьютекса. Любые комментарии?

template<typename T> 
class MyStack
{
public:
//interface
bool Push(T elem);
bool Pop(T& elem);
bool IsEmpty();

//constructor
MyStack() {
//top = 0;
}

//destructor
~MyStack() {

}

private:
    class Locker {          //RAII
    public:
        Locker() {
            pthread_mutex_init(&lock);
        }
        ~Locker() {
            pthread_mutex_destroy(&lock);
        }
        void Lock() {
            pthread_mutex_lock(&lock);
        }
        void UnLock() {
            pthread_mutex_unlock(&lock);
        }
    private:
        pthread_mutex_t lock;
    };
Locker MyLock;
//int top;
stack<T> mystack;

bool MyStack::Push(T elem);
bool MyStack::PushElem(T elem);
bool MyStack::Pop(T& elem);
bool MyStack::PopElem(T& elem);
}; //end of MyStack

template<typename T>
bool MyStack<T>::Push(T elem)
{
    MyLock.Lock();
    PushElem(elem);
    MyLock.UnLock();
}

template<typename T>
bool MyStack<T>::Pop(T& elem)
{
    MyLock.Lock();
    PopElem(elem);
    MyLock.UnLock();
}
1 голос
/ 26 июня 2009

// хитрый, возвращает ссылку на последний элемент

Присвоение копирует последний элемент, прежде чем он вытолкнет из вектора, так что все в порядке.

Как вы говорите, "верх" бессмысленен. Вы можете получить размер вектора в любое время, когда захотите.

Вы должны когда-либо вызывать stack.empty () только с удержанной блокировкой, так как нет гарантии, что он делает атомарный доступ. Вы можете получить противоречивый ответ, если вызовите его, когда другой поток находится в процессе обновления стека. Таким образом, ваша общедоступная функция IsEmpty должна принимать мьютекс, а это значит, что вы не хотите вызывать его самостоятельно из других источников.

Но в любом случае IsEmpty не очень полезен в параллельном коде. То, что когда вы звоните, это ложно, это не значит, что оно будет ложным через одну строку, когда вы щелкаете. Так что либо вы должны избавиться от него из открытого интерфейса, либо вы должны выставить блокировку, чтобы пользователи могли писать свои собственные атомарные операции. В этом случае у меня не было бы никакой проверки недостаточного значения, кроме assert в режиме отладки. Но тогда я никогда не верил в тех, кто бездельничает, которые заходят в режим релиза, не читая документацию и не тестируя свой код.

[Редактировать: Как использовать RAII для блокировок

Когда люди говорят использовать RAII для блокировки, они не просто хотят убедиться, что мьютекс уничтожен. Они хотят использовать его, чтобы убедиться, что мьютекс разблокирован. Дело в том, что если у вас есть код, который выглядит так:

lock();
doSomething();
unlock();

и doSomething () выдает исключение, тогда вы не разблокируете мьютекс. Уч.

Итак, вот пример класса вместе с использованием:

class LockSession;
class Lock {
    friend class LockSession;
    public:
    Lock()        { pthread_mutex_init(&lock); }
    ~Lock()       { pthread_mutex_destroy(&lock); }

    private:
    void lock()   { pthread_mutex_lock(&lock); }
    void unlock() { pthread_mutex_unlock(&lock); }

    private:
    Lock(const Lock &);
    const Lock &operator=(const Lock &);

    private:
    pthread_mutex_t lock;
};

class LockSession {
    LockSession(Lock &l): lock(l) { lock.lock(); }
    ~LockSession()                { lock.unlock(); }
    private:
    LockSession(const LockSession &);
    LockSession &operator=(const LockSession &);

    private:
    Lock &lock;
};

Тогда где-то ваш код будет иметь блокировку, связанную с данными, которые вы хотите защитить, и будет использовать что-то вроде следующего:

void doSomethingWithLock() {
    LockSession session(lock);
    doSomething();
}

или

void doSeveralThings() {
    int result = bigSlowComputation();  // no lock
    {
        LockSession s(lock);
        result = doSomething(result); // lock is held
    }
    doSomethingElse(result);     // no lock
}

Теперь не имеет значения, выдает ли doSomething() исключение или возвращает нормально (ну, во втором примере doSomethingElse не произойдет при исключении, но я предполагаю, что это не нужно сделано в ситуации ошибки). В любом случае, session уничтожается, и его деструктор освобождает мьютекс. В частности, такие операции, как «push» в стеке, выделяют память и, следовательно, могут сбрасывать, и поэтому вам нужно с этим справиться.

RAII расшифровывается как «Приобретение ресурсов - инициализация». В случае doSomethingWithLock () ресурс, который вы хотите получить, - это то, что вы хотите удерживать блокировку. Итак, вы пишете класс, который позволяет вам делать это путем инициализации объекта (LockSession). Когда объект разрушен, замок освобождается. Таким образом, вы относитесь к «блокированию / разблокированию мьютекса» точно так же, как вы относитесь к «инициации / деинсталляции мьютекса», и вы таким же образом защищаете себя от утечки ресурсов.

Один немного раздражающий факт заключается в том, что этот код полностью сломан и содержит ошибки, и вы должны быть уверены, что случайно его не сделаете, даже если он выглядит неосторожным взглядом, как правильный код:

void doSomethingWithLock() {
    LockSession(lock);
    doSomething();
}

Здесь первая строка создает временный объект и немедленно уничтожает его, снова снимая блокировку. doSomething() не вызывается при удерживаемой блокировке.

Boost имеет шаблон класса scoped_lock, который делает то же, что и LockSession, и многое другое.]

1 голос
/ 26 июня 2009

Я бы добавил условную переменную, чтобы "попперс" мог ждать без записи времени процессора.

0 голосов
/ 26 июня 2009

Одна вещь, которую вы не затронули, это проблема отмены потока. Stl ведет себя плохо, когда поток отменяется во время операции над контейнером stl. Вам нужно отключить отмену, когда вы работаете с вектором. Я выяснил трудный путь по этому поводу. Это не весело, когда у вас тупик, и все потоки в шаблонном stl-коде, а вы пытаетесь отладить именно то, что произошло. Используйте pthread_setcancelstate, чтобы изменить состояние отмены потоков.

0 голосов
/ 26 июня 2009

Это не идиоматический C ++ и не может иметь никаких преимуществ, но только для новизны, вы рассматривали возможность реализации неизменяемого стека? Таким образом, он автоматически станет потокобезопасным.

Эрик Липперт сделал C # реализацию . По общему признанию, код C ++ был бы более вовлеченным.

0 голосов
/ 26 июня 2009

Сначала я бы выбросил верх. Когда тебе это не нужно, это просто пустая трата!

Маленькое - это прекрасно

Также, если вы хотите оптимизировать доступ к вектору: Двойная обработка управляющей информации (здесь: длина стека) всегда подвержена ошибкам. Лучше надеяться, что вектор очень быстро (STL большую часть времени есть) и поэтому пустой () тоже есть.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...