【问题标题】:Spawning threads in a thread with callable object在具有可调用对象的线程中生成线程
【发布时间】:2018-10-05 10:37:39
【问题描述】:

我在多个场合都看到过这个问题,而且它似乎出现在 Windoes(visual studio) 和 Linux(gcc) 中。 这是它的简化版本:

class noncopyable
{
public:
    noncopyable(int n);
    ~noncopyable();
    noncopyable(const noncopyable&) = delete;
    noncopyable& operator=(const noncopyable&) = delete;
    noncopyable(noncopyable&&);
    int& setvalue();
private:
    int* number;
};

class thread_starter
{
public:
    template<typename callable>
    bool start(callable & o);
};

template<typename callable>
inline bool thread_starter::start(callable & o)
{
    std::thread t(
        [&]() {
        int i = 10;
        while (i-- > 0)
        {
            noncopyable m(i);
            std::thread child(o, std::move(m));
            child.detach();
        }
    });
    return true;
}

class callable
{
public:
    virtual void operator()(noncopyable &m);
};

void callable::operator()(noncopyable & m) { m.setvalue()++; }


int main()
{
    thread_starter ts;
    callable o;
    ts.start(o);
}

代码看起来足够合法,但它不会编译。

Visual Studio 中,它会给出:

error C2893: Failed to specialize function template 'unknown-type std::invoke(_Callable &&,_Types &&...) noexcept(<expr>)'

GCC 中,它会给出:

error: no type named ‘type’ in ‘class std::result_of<callable(int)>’....

我想我知道问题出在某种形式的复制或引用机制上,但所有语法似乎都是正确的。

我错过了什么?

我对示例进行了一些更改,对于造成的混淆,我深表歉意。我正在尝试尽可能纯粹地重现问题,但我自己并不完全理解。

【问题讨论】:

  • 你还在我们身边吗?
  • @JiveDadson 是的。

标签: c++ multithreading callable callable-object


【解决方案1】:

std::thread 的调用会创建一个元组。元组不能用引用初始化。因此,您必须使用假引用 std::ref(i) 来编译它并使用 int-refs int&amp; 调用可调用对象。

template <typename callable>
bool thread_starter::start(callable &o)
{
    // Nonsense
    std::thread t(
        [&]() {
        int i = 10;
        while (i-- > 0)
        {
            std::thread child(o, std::ref(i));
            child.detach();
        }
    });
    return true;
}

但是,生成的代码没有意义。生成的线程与 while 循环竞争。循环递减索引i,而线程尝试递增它。无法保证这些事情何时会发生。增量和减量不是原子的。一个线程可能会在 lambda 完成后尝试增加索引。

简而言之,如果你让它编译,结果是不确定的行为,原因有很多。

你实际上想做什么?

【讨论】:

  • 我正在尝试使用不可复制的套接字生成工作线程,但另一个类似的处理 lambdas 的设置也给出了这样的错误。
  • 我对示例代码做了一些改动,希望它能提供比“int”更多的信息。
  • 更改问题中的代码只会添加更多编译时错误。它还使这个正确的答案无效。
  • 是的,我试图澄清我想要做什么。
【解决方案2】:

我做了一个类似的程序来弄清楚会发生什么。看起来是这样的:

class mynoncopy 
{
public:

    mynoncopy(int resource)
        : resource(resource)
    {

    }

    mynoncopy(mynoncopy&& other)
        : resource(other.resource)
    {
        other.resource = 0;
    }

    mynoncopy(const mynoncopy& other) = delete;

    mynoncopy& operator =(const mynoncopy& other) = delete;

public:

    void useResource() {}

private:
    int resource;
};

class mycallablevaluearg
{
public:

    void operator ()(mynoncopy noncopyablething)
    {
        noncopyablething.useResource();
    }
};

class mycallableconstrefarg
{
public:

    void operator ()(const mynoncopy& noncopyablething)
    {
        //noncopyablething.useResource(); // can't do this becuase of const :(
    }
};

class mycallablerefarg
{
public:

    void operator ()(mynoncopy& noncopyablething)
    {
        noncopyablething.useResource();
    }
};

class mycallablervaluerefarg
{
public:

    void operator ()(mynoncopy&& noncopyablething)
    {
        noncopyablething.useResource();
    }
};

class mycallabletemplatearg
{
public:

    template<typename T>
    void operator ()(T&& noncopyablething)
    {
        noncopyablething.useResource();
    }
};

当您发出std::thread(callable, std::move(thenoncopyableinstance)) 时,这两件事将在内部使用模板魔法发生:

  1. 使用您的可调用对象和所有参数创建一个元组。
    std::tuple<mycallablerefarg, mynoncopy> thetuple(callable, std::move(thenoncopyableinstance));
    在这种情况下将复制可调用对象。

  2. std::invoke() 用于调用可调用对象,并使用移动语义从元组将 arg 传递给它。
    std::invoke(std::move(std::get&lt;0&gt;(thetuple)), std::move(std::get&lt;1&gt;(thetuple)));

因为使用了移动语义,它会期望可调用对象接收一个右值引用作为参数(在我们的例子中是mynoncopy&amp;&amp;)。这将我们限制在以下参数签名中:

  1. mynoncopy&amp;&amp;
  2. const mynoncopy&amp;
  3. T&amp;&amp; 其中 T 是模板参数
  4. mynoncopy 不是引用(这将调用移动构造函数)

这些是使用不同类型的可调用对象的编译结果:

mynoncopy testthing(1337);

std::thread t(mycallablerefarg(), std::move(testthing)); // Fails, because it can not match the arguments. This is your case.
std::thread t(mycallablevaluearg(), std::move(testthing)); // OK, because the move semantics will be used to construct it so it will basically work as your solution
std::thread t(mycallableconstrefarg(), std::move(testthing)); // OK, because the argument is const reference
std::thread t(mycallablervaluerefarg(), std::move(testthing)); // OK, because the argument is rvalue reference 
std::thread t(mycallabletemplatearg(), std::move(testthing)); // OK, because template deduction kicks in and gives you noncopyablething&&
std::thread t(std::bind(mycallablerefarg(), std::move(testthing))); // OK, gives you a little bit of call overhead but works. Because bind() does not seem to use move semantics when invoking the callable
std::thread t(std::bind(mycallablevalue(), std::move(testthing))); // Fails, because bind() does not use move semantics when it invokes the callable so it will need to copy the value, which it can't.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-10
    • 1970-01-01
    • 1970-01-01
    • 2018-05-14
    • 1970-01-01
    • 1970-01-01
    • 2011-09-28
    • 1970-01-01
    相关资源
    最近更新 更多