簡體   English   中英

在具有可調用對象的線程中生成線程

[英]Spawning threads in a thread with callable object

我已經多次看到此問題,並且似乎在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)>’....

我想我知道問題出在某種形式的復制或引用機制中,但是所有語法似乎都是正確的。

我想念什么?

我對示例進行了一些更改,對於造成的混淆,我深表歉意。 我正嘗試盡可能地重現問題,而我本人並不完全理解。

std::thread的調用將創建一個元組。 元組不能使用引用初始化。 因此,您必須使用偽引用std::ref(i)進行編譯,並使用int-refs int&調用可調用對象。

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完成后,線程可能會嘗試增加索引。

簡而言之,如果您對其進行編譯,則由於多種原因,結果是未定義的行為。

您實際上想做什么?

我做了一個類似的程序來弄清楚會發生什么。 它是這樣的:

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. 使用可調用對象和所有arg創建一個元組。
    std::tuple<mycallablerefarg, mynoncopy> thetuple(callable, std::move(thenoncopyableinstance));
    在這種情況下,將復制可調用對象。

  2. std::invoke()用於調用可調用對象,並且使用移動語義將arg從元組傳遞給它。
    std::invoke(std::move(std::get<0>(thetuple)), std::move(std::get<1>(thetuple)));

由於使用了移動語義,它將期望可調用對象接收一個右值引用作為參數(在我們的例子中為mynoncopy&& )。 這將我們限制為以下參數簽名:

  1. mynoncopy&&
  2. const mynoncopy&
  3. T&&其中T是模板參數
  4. mynoncopy不是參考(這將稱為move-constructor)

這些是使用不同類型的可調用對象的編譯結果:

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.

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM