The following code crashes in random intervals when built with MSVC under Debug
mode, unlike the Release
.
#include <future>
using namespace std;
int main() {
auto l = [](){};
auto f = async(launch::async, l);
for (int i = 0; i < 1000000; ++i)
f = async(launch::async, l);
}
The console output says:
f:\dd\vctools\crt\crtw32\stdcpp\thr\mutex.c(51): mutex destroyed while busy
The full call stack is: https://pastebin.com/0g2ZF5C1
Now obviously it's just a stress test, but am I doing something utterly stupid? It seems to me it's fine to reassign a new task to an existing future, as it's said that operator=
:
Releases any shared state and move-assigns the contents of other to *this
(Due to http://en.cppreference.com/w/cpp/thread/future/operator%3D).
Is it a bug in MSVC's runtime?
Remarkably, the program stops crashing if I manually call wait() before the assignment, thus making the loop into:
for (int i = 0; i < 1000000; ++i) {
f.wait();
f = async(launch::async, l);
}
Isn't operator=
itself supposed to call wait
?
Background:
_MSC_VER
equals 1911
Code was built with help of:
Microsoft Visual Studio Community 2017 Preview(2)
Version 15.4.0 Preview 2.0
Just opened a brand new C++ project.
Isn't
operator=
itself supposed to callwait
?
I don't know whether it is supposed to, but a cursory glance at the MSVC15.3.4 implementation of <future>
seems to strongly suggest it doesn't.
//User Code
future f = /*...*/;
f = /*...*/; //(1)
//MSVC Code future& operator=(future&& _Right) _NOEXCEPT //(1) { // assign from rvalue future object _Mybase::operator=(_STD move(_Right)); //(2) return (*this); } _State_manager& operator=(_State_manager&& _Other) //(2) { // assign from rvalue _Other _Move_from(_Other); //(3) return (*this); } void _Move_from(_State_manager& _Other) //(3) { // move stored associated asynchronous state object from _Other if (this != _STD addressof(_Other)) { // different, move if (_Assoc_state) _Assoc_state->_Release(); //(4) _Assoc_state = _Other._Assoc_state; _Other._Assoc_state = 0; _Get_only_once = _Other._Get_only_once; } } void _Release() //(4) { // decrement reference count and destroy when zero if (_MT_DECR(_Refs) == 0) _Delete_this(); //(5) } void _Delete_this() //(5) { // delete this object if (_Deleter) _Deleter->_Delete(this); //External Code else delete this; }
Seeing as calling wait
helps synchronize things and ensure that the future
object is in a safe state to be modified, it might be better to include the wait
statement.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With