繁体   English   中英

在Linux上用C ++实现一个线程安全的通用堆栈

[英]Implementing a thread-safe, generic stack in C++ on linux

在最近的一次采访中,我被要求在Linux机器上用C ++实现一个线程安全的通用(基于ietemplate)堆栈。
我很快想出了以下内容(它可能有编译错误)。
我完成了。 面试官可能喜欢这个实现中的一些东西。 也许设计部分:)
以下是此实现可能存在的一些问题: -
1.表示溢出/下溢的实现不正确。 因为我使用STL向量作为底层数据结构,所以没有溢出处理。 应该有这样的处理吗? 此外,下溢(在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();
}

一些问题:

  • 我将实现一个Locker类来声明并使用RAII释放互斥锁
  • 我会使用std :: stack
  • 我会让std :: stack的用户使用Locker来实现锁定策略 - 拥有一个锁定自身的堆栈是糟糕的设计,因为堆栈无法知道它是如何被使用的

尼尔,Onebyone:
尝试使用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();
}

我会添加一个条件变量,以便“poppers”可以等待而不会占用CPU时间。

//棘手,返回对最后一个元素的引用

赋值在向量弹出之前复制最后一个元素,这样就可以了。

正如你所说,“顶级”毫无意义。 您可以随时获取矢量的大小。

您应该只使用锁定调用stack.empty(),因为无法保证它进行原子访问。 如果在另一个线程正在更新堆栈的过程中调用它,则可能会得到一个不一致的答案。 因此,您的公共IsEmpty函数应该使用互斥锁,这意味着您不希望自己从其他地方调用它。

但无论如何,IsEmpty在并行代码中并不是很有用。 只是因为当你调用它时它是错误的并不意味着当你弹出它时它仍会是假的。 所以要么你应该从公共接口中删除它,否则你应该公开锁,以便用户可以编写自己的原子操作。 在这种情况下,除了调试模式下的断言之外,我根本没有任何下溢检查。 但是,我从来没有相信过那些在没有阅读文档或测试代码的情况下达到发布模式的人。

[编辑:如何使用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;
};

然后在某处你的代码将有一个与你想要保护的数据相关联的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所做的事情,以及更多。]

我会先扔掉顶部。 当你不需要它时,它只是浪费!

小是美丽的

此外,如果您想优化对向量的访问:管理信息的重复处理(此处:stacklength)总是容易出错。 更好的希望,那个矢量非常快(STL大部分时间都是),所以空()也是。

这不是惯用的C ++,可能没有任何优势,但仅仅是为了新颖性,您是否考虑过实现不可变堆栈? 这样,它将自动成为线程安全的。

Eric Lippert完成了C#实现 不可否认,C ++代码将更加复杂。

你没有解决的一件事是线程取消的问题。 在对stl容器执行操作期间取消线程时,stl表现不佳。 在向量上操作时,需要禁用取消。 我发现了很难的方法。 当你遇到死锁并且线程都是模板化的stl代码并且你正在尝试调试发生的事情时,这并不好玩。 使用pthread_setcancelstate更改线程的取消状态。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM