[英]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))
將使用模板魔術在內部發生以下兩件事:
使用可調用對象和所有arg創建一個元組。
std::tuple<mycallablerefarg, mynoncopy> thetuple(callable, std::move(thenoncopyableinstance));
在這種情況下,將復制可調用對象。
std::invoke()
用於調用可調用對象,並且使用移動語義將arg從元組傳遞給它。
std::invoke(std::move(std::get<0>(thetuple)), std::move(std::get<1>(thetuple)));
由於使用了移動語義,它將期望可調用對象接收一個右值引用作為參數(在我們的例子中為mynoncopy&&
)。 這將我們限制為以下參數簽名:
mynoncopy&&
const mynoncopy&
T&&
其中T是模板參數 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.