原子 std::vector::push_back() 和返回索引

发布于 2024-09-14 08:00:23 字数 408 浏览 2 评论 0原文

我需要创建一个函数,它将一个值附加到一个向量并返回刚刚附加的值的索引。

示例:

int append(std::vector<int>& numbers, int number){
  int retval = numbers.size();
  // what if some other thread calls push_back(number) in between these calls?
  numbers.push_back(number);
  return retval;
}

我想以原子方式执行此操作,以便即使可能有多个线程向向量附加值,返回的索引也始终正确。如果 push_back 返回刚刚添加的项目的索引,那就很容易了。如何保证返回正确的索引?

I need to create a function which appends a value to a vector and returns the index of the value that was just appended.

Example:

int append(std::vector<int>& numbers, int number){
  int retval = numbers.size();
  // what if some other thread calls push_back(number) in between these calls?
  numbers.push_back(number);
  return retval;
}

I would like to do this atomically so that the returned index is always correct even when there may be multiple threads appending values to the vector. It would have been easy if push_back returned the index of the item just added. How can I guarantee that the correct index is returned?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

遗心遗梦遗幸福 2024-09-21 08:00:23

std::vector 没有内置线程支持。您可以使用 boost::mutex 来扩展它:

int append(std::vector<int>& numbers, int number){
  boost::mutex::scoped_lock slock( my_lock );
  int retval = numbers.size();
  numbers.push_back(number);
  return retval;
}

您需要以这种方式保护任何读/写操作。另一种方法是为 std::vector 创建包装类,以通过线程支持来扩展它。有关详细信息,请查看问题。

std::vector has no built in thread support. You could use boost::mutex to extend it:

int append(std::vector<int>& numbers, int number){
  boost::mutex::scoped_lock slock( my_lock );
  int retval = numbers.size();
  numbers.push_back(number);
  return retval;
}

You need to protect any read/write operation in such way. Another way is to create wrapper class for std::vector that will extend it with thread support. Check this question for details.

摘星┃星的人 2024-09-21 08:00:23

STL 容器不是线程安全的(即使是单独调用 push_back()),您必须自己解决这个问题 - 在 STL 之外使用一些合适的同步原语。

STL containers are not thread-safe (even the call to push_back() alone), you'll have to solve this problem on your own - use some suitable synchronization primitives outside STL.

情痴 2024-09-21 08:00:23

在 Visual Studio 2010 中,您可以使用 concurrent_vector 来实现此目的,它提供同步增长功能。 本主题列出了每个并发容器。

请注意,这些在英特尔的 TBB 中也可用,具有相同的语法 + 语义,因此可以跨平台使用。

In Visual Studio 2010, you can use a concurrent_vector for this in , it offers synchronized grow functionality . This topic lists each of the concurrent containers.

Note that these are also available in Intel's TBB with identical syntax + semantics and as such are available cross platform.

迷路的信 2024-09-21 08:00:23

您需要使用互斥体来保证返回正确的索引

You need to use a mutex to guarantee that the correct index is returned

∝单色的世界 2024-09-21 08:00:23

最有力的保证解决方案是在所有此类操作上锁定整个向量(这意味着从代码中的任何位置控制每个操作,这实际上意味着创建一个同步向量)。

也许像这样简单的事情就可以满足您的目的:

int append(std::vector<int>& numbers, int number){
  int retval = numbers.size();
  // what if some other thread calls push_back(number) in between these calls?
  numbers.push_back(number);
  int newSize = numbers.size();
  //this bit is as a short-cut in common, easy, cases
  if(newSize = retval + 1) //no need for further complication
    return retval;
  while(++retval < newSize)
    if(numbers[retval] == number)
      return retval;
  //If we get this far, numbers have been deleted, not added. More discussion below.
}

关于这一点的一件事是,如果线程推送 3, 3, 3, 3,那么返回的索引将是错误的,尽管它仍然是 3 的索引。这是否可以取决于您的目的。

另一个是,如果向量同时被弹出或以其他方式缩短,那么最好的情况是我们只是在上面的代码中添加注释,最糟糕的是它会出错(因为它们在我们获得 newSize 后再次弹出,然后访问[retval]无效)。您需要考虑这种情况是否会发生(也许您从代码的其余部分知道它永远不会发生)以及如果发生了该怎么办。

如果这种限制对于您的用例来说太大,那么恐怕生成完全同步的向量是我能想到的最好的。

The strongest-guarantee solution is to lock the entire vector on all such operations (which means controlling every operation from everywhere in the code, which really means creating a synchronised vector).

It may be that something as simple as this will do for your purposes:

int append(std::vector<int>& numbers, int number){
  int retval = numbers.size();
  // what if some other thread calls push_back(number) in between these calls?
  numbers.push_back(number);
  int newSize = numbers.size();
  //this bit is as a short-cut in common, easy, cases
  if(newSize = retval + 1) //no need for further complication
    return retval;
  while(++retval < newSize)
    if(numbers[retval] == number)
      return retval;
  //If we get this far, numbers have been deleted, not added. More discussion below.
}

One thing about this is that if threads push 3, 3, 3, 3 then the index returned will be wrong, though it will still be an index to a 3. Whether that's okay or not depends on your purposes.

Another is that if the vector is popped or otherwise shortened in the meantime, then at best we get to the point where I just put a comment in the code above, at worse it errors (as they pop again after we obtain newSize, and then accessing [retval] becomes invalid). You need to consider if this case can happen (maybe you know from the rest of the code that it never will) and what to do if it does.

If the limitations of this are too great for your use case, then producing a fully synchronised vector is the best I can think of I'm afraid.

遇到 2024-09-21 08:00:23

如果您知道所需的最大大小并使用原子计数器来增加当前元素索引,则可以使用自己的容器在没有互斥锁的情况下执行此操作,例如:

template <typename T> class atomicvector
{
public:
    atomicvector(size_t _size) :
        m_data(_size),
        m_counter(0)
    {

    }

    void clear()
    {
        #ifdef _DEBUG
        memset(m_data.data(), 0x0, m_data.size() * sizeof(T));
        #endif
        m_counter = 0;
    }

    size_t size() const
    {
        return m_counter;
    }

    const T & operator[] (size_t _index) const
    {
        return m_data[_index];
    }

    // Only 'push_atomic' is lock-free, 'size' or 'clear' shouldn't be called while the vector is being filled
    size_t push_atomic(const T & _value)
    {
        size_t index = m_counter.fetch_add(1);
        assert(index < size());
        m_data[index] = _value;
        return index;
    }

private:
    std::vector<T>         m_data;
    std::atomic<size_t>    m_counter;
};

You can do this without Mutex with your own container if you can know the maximum required size and use an atomic counter to increment the current element index, e.g. :

template <typename T> class atomicvector
{
public:
    atomicvector(size_t _size) :
        m_data(_size),
        m_counter(0)
    {

    }

    void clear()
    {
        #ifdef _DEBUG
        memset(m_data.data(), 0x0, m_data.size() * sizeof(T));
        #endif
        m_counter = 0;
    }

    size_t size() const
    {
        return m_counter;
    }

    const T & operator[] (size_t _index) const
    {
        return m_data[_index];
    }

    // Only 'push_atomic' is lock-free, 'size' or 'clear' shouldn't be called while the vector is being filled
    size_t push_atomic(const T & _value)
    {
        size_t index = m_counter.fetch_add(1);
        assert(index < size());
        m_data[index] = _value;
        return index;
    }

private:
    std::vector<T>         m_data;
    std::atomic<size_t>    m_counter;
};
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文