【问题标题】:is it correct way to create a thread safe queue using pthreads?使用 pthreads 创建线程安全队列是正确的方法吗?
【发布时间】:2021-08-30 03:36:03
【问题描述】:

我最近开始学习多线程,所以我还是不明白一些东西。我试图编写一个线程安全的队列,但我怀疑我是否做对了一切。由于我没有注意到我的错误,如果您向我指出错误,我会很高兴。谢谢您的反馈! 另外,我不确定我在这段代码中是否正确使用了条件变量。

template<class T>
class SafeQueue {
private:
    std:: queue<T> _data;
    pthread_mutex_t _mutex;
    pthread_cond_t _condition;
public:
    SafeQueue();
    ~SafeQueue();
    void push(const T &x);
    T front();
    T back();
    void pop();
    int size();
    bool empty();
};

template<class T>
SafeQueue<T>::SafeQueue(){
    pthread_mutex_init(&_mutex, NULL);
    pthread_cond_init(&_condition, NULL);
}

template<class T>
SafeQueue<T>::~SafeQueue(){
    pthread_mutex_destroy(&_mutex);
    pthread_cond_destroy(&_condition);
}

template<class T>
void SafeQueue<T>::push(const T &x){
    pthread_mutex_lock(&_mutex);
    _data.push(x);
    pthread_cond_signal(&_condition);
    pthread_mutex_unlock(&_mutex);
}

template<class T>
T SafeQueue<T>::front(){
    pthread_mutex_lock(&_mutex);
    if(_data.empty() && _data.size() == 0){
        pthread_mutex_unlock(&_mutex);
        throw SafeQueueException("Queue is empty");
    }
    if(_data.empty()){
        pthread_cond_wait(&_condition, &_mutex);
    }
    T temp = _data.front();
    pthread_mutex_unlock(&_mutex);
    return temp;
}

template<class T>
T SafeQueue<T>::back(){
    pthread_mutex_lock(&_mutex);
    if(_data.empty() && _data.size() == 0){
        pthread_mutex_unlock(&_mutex);
        throw SafeQueueException("Queue is empty");
    }
    if(_data.empty()){
        pthread_cond_wait(&_condition, &_mutex);
    }
    T temp = _data.back();
    pthread_mutex_unlock(&_mutex);
    return temp;
}

template<class T>
void SafeQueue<T>::pop(){
    pthread_mutex_lock(&_mutex);
    if(_data.empty() && _data.size() == 0){
        pthread_mutex_unlock(&_mutex);
        throw SafeQueueException("Queue is empty");
    }
    if(_data.empty()){
        pthread_cond_wait(&_condition, &_mutex);
    }
    _data.pop();
    pthread_mutex_unlock(&_mutex);
}

template<class T>
int SafeQueue<T>::size(){
    pthread_mutex_lock(&_mutex);
    int temp = _data.size();
    pthread_mutex_unlock(&_mutex);
    return temp;
}

template<class T>
bool SafeQueue<T>::empty(){
    pthread_mutex_lock(&_mutex);
    bool temp = _data.empty();
    pthread_mutex_unlock(&_mutex);
    return temp;
}

【问题讨论】:

标签: c++ multithreading pthreads


【解决方案1】:

代码中的一些点我不太明白为什么你把_data.empty() &amp;&amp; _data.size() == 0 放在这样的地方?当您只能使用其中一个时。并且大小和空方法也不需要锁定,因为它们只是读取变量并返回它们,它们不会修改任何东西。

顺便说一句。

确实,有些人认为如果他们将互斥锁放在开始和结束操作中是某种数据结构(在你的情况下是队列),那么他们现在认为它是线程安全的,但事实并非如此。

例如,假设您的队列有一个元素。

q = 1

然后你有一些这样的代码:

if (!q.empty()) {
  auto element = q.back();
  q.pop();
}

在这个例子中,两个线程将执行这段代码检查,他们会注意到队列不是空的,然后他们将进入如果条件并且其中一个线程首先取回元素,然后幸运的是同一个线程将弹出元素.在那之后,线程 2 将取回元素,因为线程 1 弹出元素并且现在队列为空,然后线程 2 返回方法失败并出现异常,因此如果队列条件不为空,则进入线程 2,则它失败。所以这个队列不是线程安全的

【讨论】:

  • 根据您的论点,没有容器可以是线程安全的,但您的观点没有实际意义。提供原子操作的容器(原子在:通过互斥锁保护的意义上)可以使用线程安全,不能使用线程安全的容器。这就是制造线程安全容器的原因,而不是它是否可以用错
  • @463035818_is_not_a_number 我的意思是,你可以在操作的开始和结束时放置互斥锁,这将使它成为线程安全的,但你也应该考虑一些点,你仍然可能使用它错误,因为你认为它是线程安全,你可以做任何可能的组合。
  • 是的,但一般来说,如果一个类 Foo 是线程安全的,并且有 AB 方法,那么调用 A 是线程安全的,调用 B 是线程安全的,但是要调用AB “原子地”调用者必须采取额外的措施。 Foo 无法提供帮助,除非它还提供了一个方法 AB
  • Re,“……我的意思是……你仍然有可能用错了。”当然。这是一个很好的观点。调用“线程安全”包/库/函数/类绝对保证您自己的代码将是线程安全的,但这不是第 3 方代码的错。如果无论有多少线程同时使用它,代码都能保证以可预测的方式运行,那么代码就是“线程安全的”。无论您是使用这些保证来证明您自己的代码的线程安全性,还是您是否误解和误用它们都是您的选择——您的问题。
【解决方案2】:

很高兴看到您正在扩展自己的技能。如果您使用 C++,则 C++11 及更高版本在 standard library 中有线程。我建议您改用这些功能。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-07-24
    • 1970-01-01
    • 1970-01-01
    • 2017-06-14
    • 1970-01-01
    • 2012-06-16
    • 1970-01-01
    • 2017-04-29
    相关资源
    最近更新 更多