【发布时间】:2018-07-07 01:12:58
【问题描述】:
我正在测试我最近编写的一个无锁工作窃取队列。但是,我发现了一个奇怪的问题,即当我将新项目推送到队列时,它只会返回最后推送的相同对象。我花了一天时间,我仍然无法弄清楚为什么会发生这种情况。 这是输出,每次推送到队列的数据都是一样的,这就是为什么我只有最后一个结果是 45,对于 4 个 res 项应该都是 45。任何人都可以在这里帮助我:(
- push_back addr: 0x21d3ea0 bk: 0 &data: 0x7ffe36802380
- push_back addr: 0x21d3ea4 bk: 0 &data: 0x7ffe36802380
- push_back addr: 0x21d3ea8 bk: 0 &data: 0x7ffe36802380
- push_back addr: 0x21d3eac bk: 0 &data: 0x7ffe36802380
- res[0]=-1
- res[1]=-1
- res[2]=-1
- res[3]=45
下面是简化代码:
#include <memory>
#include <functional>
#include <type_traits>
class FunctionWrapper {
private:
class ImplInterface {
public:
virtual void invoke() = 0;
virtual ~ImplInterface() {}
};
std::unique_ptr<ImplInterface> impl;
template <typename F, typename... Args>
class Impl : public ImplInterface {
private:
std::function<void()> callBack;
public:
Impl(F&& f_, Args&&... args_) {
callBack = [&f_, &args_...]() { f_(std::forward<Args>(args_)...); };
}
void invoke() override { callBack(); }
};
public:
template <typename F, typename... Args>
FunctionWrapper(F&& f_, Args&&... args_) : impl(new Impl<F, Args...>(std::forward<F>(f_), std::forward<Args>(args_)...)) {}
void operator()() { impl->invoke(); }
FunctionWrapper() = default;
FunctionWrapper(FunctionWrapper&& other) : impl(std::move(other.impl)) {}
FunctionWrapper& operator=(FunctionWrapper&& other) {
impl = std::move(other.impl);
return *this;
}
FunctionWrapper(const FunctionWrapper&) = delete;
FunctionWrapper(FunctionWrapper&) = delete;
FunctionWrapper& operator=(const FunctionWrapper&) = delete;
};
#include <atomic>
#include <array>
#include <iostream>
#include "functionwrapper.h"
class LockFreeWorkStealingQueue {
private:
using DataType = FunctionWrapper;
static constexpr auto DEFAULT_COUNT = 2048u;
static constexpr auto MASK = DEFAULT_COUNT - 1u;
std::array<DataType, DEFAULT_COUNT> q;
unsigned int ft{0};
unsigned int bk{0};
public:
LockFreeWorkStealingQueue() {}
LockFreeWorkStealingQueue(const LockFreeWorkStealingQueue&) = delete;
LockFreeWorkStealingQueue& operator=(const LockFreeWorkStealingQueue&) = delete;
void push_back(DataType data) {
std::cout << "bk: " << (bk&MASK) << " &data: " << &data << std::endl;
q[bk & MASK] = std::move(data);
bk++;
}
bool try_pop_back(DataType& res) {
if (bk > ft) {
res = std::move(q[(bk - 1) & MASK]);
bk--;
return true;
}
return false;
}
};
#include "lockfreeworkstealingqueue.h"
#include <iostream>
#include <algorithm>
#include <numeric>
#include <cassert>
#include <vector>
constexpr unsigned int NUM = 4;
void sumOver(const std::vector<int>& v, int& res) {
res = std::accumulate(v.begin(), v.end(), 0);
//std::cout << "call sumOver, res = " << res << std::endl;
//std::cout << "call sumOver, addr: " << &res << std::endl;
}
int main () {
std::vector<int> v { 1,2,3,4,5,6,7,8,9 };
std::vector<int> res(NUM, -1);
std::vector<LockFreeWorkStealingQueue> wsq(4);
{
for (auto i = 0; i < 4; ++i) {
for (auto j = 0; j < NUM / 4; ++j) {
std::cout << "push_back addr: " << &res[i*(NUM/4)+j] << std::endl;
wsq[i].push_back(FunctionWrapper(sumOver, std::ref(v), std::ref(res.at(i*(NUM/4)+j))));
}
}
FunctionWrapper f;
for (auto i = 0; i < 4; ++i) {
for (auto j = 0; j < NUM / 4; ++j) {
if(wsq[i].try_pop_back(f)) {
f();
}
}
}
}
for (auto i = 0; i < 4; ++i) {
for (auto j = 0; j < NUM / 4; ++j) {
std::cout << "res[" << i*(NUM/4)+j << "]=" << res[i*(NUM/4)+j] << std::endl;
}
}
return 0;
}
编辑:我对 functionwrapper.h 进行了更改以反映 cmets。现在它运行良好。
#include <memory>
#include <functional>
class FunctionWrapper {
private:
std::function<void()> callback;
public:
template <typename F, typename... Args>
FunctionWrapper(F&& f_, Args&&... args_) : callback([f_, args_...]() { f_(args_...); }) {}
void operator()() { callback(); }
FunctionWrapper() = default;
FunctionWrapper(FunctionWrapper&& other) : callback(std::move(other.callback)) {}
FunctionWrapper& operator=(FunctionWrapper&& other) {
callback = std::move(other.callback);
return *this;
}
FunctionWrapper(const FunctionWrapper&) = delete;
FunctionWrapper(FunctionWrapper&) = delete;
FunctionWrapper& operator=(const FunctionWrapper&) = delete;
};
【问题讨论】:
-
Impl通过引用捕获右值。这些引用很容易变得悬而未决。我还没有读到足够多的内容来查看显示的代码中是否确实发生了这种情况,但这是需要注意的事情。无论如何,您为什么要在类型擦除之上堆积类型擦除?为什么没有FunctionWrapper直接有std::function成员? -
这几乎不是minimal reproducible example。但是我看到您将指针推送到固定长度
std::array中的元素,那么您为什么不期望随着时间的推移重复使用相同的内存地址呢?此外,在您的输出消息中,&data是调用堆栈上的 局部变量 的地址,随着时间的推移肯定会被重用。输出q[bk & MASK]的地址而不是data可能更有意义,因为这是您存储data的地方。 -
我不知道,如果我直接使用std::function,那么我无法通过将函数和所有参数推回队列来存储它,然后在我弹出时调用它出来。当我在其他部分声明 FunctionWrapper 时,我也不希望它作为模板。我不知道我怎么能以另一种方式做到这一点......@Igor Tandetnik
-
template <typename F, typename... Args> FunctionWrapper(F&& f_, Args&&... args_) : callback([f_, args_...]() { f_(args_...); } {}。我不太了解您所说的困难的性质。您无缘无故地将Impl设为类模板 - 它的成员callback实际上并不依赖于它的模板参数。 -
声明复制构造函数和复制赋值操作符为已删除,类型为只移动。我不明白为什么这需要通过
unique_ptr持有callback。但如果你坚持:callback(std::make_unique<std::function<void()>>([f_, args_...]() { f_(args_...); }))
标签: c++11 templates c++14 move perfect-forwarding