在并发队列中的dereferenced shared_ptr上使用std :: move

时间:2014-10-15 06:37:13

标签: c++ c++11 shared-ptr move smart-pointers

以下是Anthony Williams'中线程安全队列的代码。我在.h文件中收集的 C ++并发操作

#ifndef THREADSAFE_QUEUE_H
#define THREADSAFE_QUEUE_H

// Anthony Williams' fine-grained lock-based thread-safe queue.

#include <mutex>                // for std::mutex
#include <condition_variable>   // for std::condition_variable
#include <memory>               // for std::shaerd_ptr and std::unique_ptr
#include <utility>              // for std::move


template <typename T>
class threadsafe_queue
{
private:
    struct node
    {
        std::shared_ptr<T> data;
        std::unique_ptr<node> next;
    };

    std::mutex head_mutex;
    std::unique_ptr<node> head;
    std::mutex tail_mutex;
    node* tail;
    std::condition_variable data_cond;
public:
    threadsafe_queue():
        head(new node), tail(head.get())
    {}
    threadsafe_queue(const threadsafe_queue& other)=delete;
    threadsafe_queue& operator=(const threadsafe_queue& other)=delete;

    std::shared_ptr<T> try_pop();
    bool try_pop(T& value);
    std::shared_ptr<T> wait_and_pop();
    void wait_and_pop(T& value);
    void push(T new_value);
    void empty();

private:
    node* get_tail()
    {
        std::lock_guard<std::mutex> tail_lock(tail_mutex);
        return tail;
    }

    std::unique_ptr<node> pop_head()
    {
        std::unique_ptr<node> old_head = std::move(head);
        head = std::move(old_head->next);
        return old_head;
    }

    std::unique_lock<std::mutex> wait_for_data()
    {
        std::unique_lock<std::mutex> head_lock(head_mutex);
        data_cond.wait(head_lock, [&]{return head.get()!=get_tail();});
        return std::move(head_lock);
    }

    std::unique_ptr<node> wait_pop_head()
    {
        std::unique_lock<std::mutex> head_lock(wait_for_data());
        return pop_head();
    }

    std::unique_ptr<node> wait_pop_head(T& value)
    {
        std::unique_lock<std::mutex> head_lock(wait_for_data());
        value=std::move(*head->data);
        return pop_head();
    }

    std::unique_ptr<node> try_pop_head()
    {
        std::unique_lock<std::mutex> head_lock(head_mutex);
        if(head.get()==get_tail())
        {
            return std::unique_ptr<node>();
        }
        return pop_head();
    }

    std::unique_ptr<node> try_pop_head(T& value)
    {
        std::unique_lock<std::mutex> head_lock(head_mutex);
        if(head.get()==get_tail())
        {
            return std::unique_ptr<node>();
        }
        value=std::move(*head->data);
        return pop_head();
    }

};

/*
 * PUBLIC INTERFACE
 */

// try pop.
template <typename T>
std::shared_ptr<T> threadsafe_queue<T>::try_pop()
{
    std::unique_ptr<node> const old_head=try_pop_head();
    return old_head?old_head->data:std::shared_ptr<T>();
}

template <typename T>
bool threadsafe_queue<T>::try_pop(T& value)
{
    std::unique_ptr<node> const old_head=try_pop_head(value);
    return old_head;
}

// wait and pop.
template <typename T>
std::shared_ptr<T> threadsafe_queue<T>::wait_and_pop()
{
    std::unique_ptr<node> const old_head=wait_pop_head();
    return old_head->data;
}

template <typename T>
void threadsafe_queue<T>::wait_and_pop(T& value)
{
    std::unique_ptr<node> const old_head=wait_pop_head(value);
}

// push.
template <typename T>
void threadsafe_queue<T>::push(T new_value)
{
    std::shared_ptr<T> new_data(
            std::make_shared<T>(std::move(new_value)));
    std::unique_ptr<node> p(new node);
    {
        std::lock_guard<std::mutex> tail_lock(tail_mutex);
        tail->data=new_data;
        node* const new_tail=p.get();
        tail->next=std::move(p);
        tail=new_tail;
    }
    data_cond.notify_one();
}

// empty.
template <typename T>
void threadsafe_queue<T>::empty()
{
    std::lock_guard<std::mutex> head_lock(head_mutex);
    return (head.get()==get_tail());
}

#endif

代码中有一件事我无法推理,它出现在两点上。在wait_pop_head(T& value)try_pop_head(T& value)中,有value=std::move(*head->data);。基本上,为了将shared_ptr的解除引用结果分配给引用,它会将其传递给std::move。如果你让我知道为什么要这样做,我感激不尽?为什么不应该使用value=*head->data;代替?

评论中提出的另一个问题是,为什么要std::shared_ptr代替std::unique_ptr

2 个答案:

答案 0 :(得分:1)

如果类型T定义了移动赋值运算符,那么提供通过调用std::move()获得的右值引用将允许使用更高效的运算符,而不是强制使用复制赋值运算符。

如果类型T没有定义移动赋值运算符,则可以向其赋值运算符提供T&&,这可能需要{{1}类型的参数1}}或者只是const T&。在任何一种情况下,类型T的参数都可以转换为参数类型。

至于使用T&&而不是std::shared_ptr,我不明白为什么它也是必要的。看起来可以从std::unique_ptrstd::unique_ptr返回try_pop(),将指针移出即将被销毁的wait_and_pop()实例。我能想出的唯一一个论点是允许来自&#34; pop&#34;功能结束了可共享的参考,认为这是一个更灵活的选择。我粗略地阅读实现并没有表明任何情况下两个threadsafe_queue::node实例会指向相同的&#34;数据&#34;价值,所以我无法找到该设计选择的任何内部原因。

答案 1 :(得分:0)

对于第一个,如果T已经分配了动态内存,那么move可以减少很多资源。在这种情况下,函数名为 *_pop_head,这意味着调用后头节点的 data 应该被视为无用。 所以使用move将T的动态内存的所有权转移给目标T对象是合理的。

对于第二个,我能想到的唯一原因是,如果我们想要一个top成员函数,我们可以将头节点的data放在多个地方。