Code at bottom.
My questions are:
* Why is it crashing with Visual C++?
* Is there a much simpler / efficient / whatever way to do this
(except using C++20 coroutines)?
Output with MinGW g++, with logging suppressed:
Starting.
Thread 3 iteration 0.
Thread 2 iteration 0.
Thread 3 iteration 1.
Thread 2 iteration 1.
Thread 3 iteration 2.
Thread 2 iteration 2.
Thread 3 iteration 3.
Thread 2 iteration 3.
Thread 3 iteration 4.
Thread 2 iteration 4.
Thread 3 iteration 5.
Thread 2 iteration 5.
Thread 3 iteration 6.
Thread 2 iteration 6.
Thread 3 iteration 7.
Thread 2 iteration 7.
Thread 3 iteration 8.
Thread 2 iteration 8.
Thread 3 iteration 9.
Thread 2 iteration 9.
Finished.
With Visual C++ 2019 there's no ordinary output, and the log output
differs from run to run, but typically
Starting.
Thread 12828 yielding.
Thread 4076 yielding.
Thread 12828 registering unlocker.
Thread 12828 entering wait state.
Thread 4076 releasing another thread.
The process exit code is then -1073740791, which Microsoft's errlook
utility tells me is hex 0xC0000409 and that that's an unknown code.
However, mr. Google leads to me to an SO question about it, where an
answer claims that it's STATUS_STACK_BUFFER_OVERRUN, i.e. not an error
code or HRESULT code, but a low level status code.
Which means that I can possibly find out by creating a Visual Studio
project and debugging, but debugging threads is tricky, so I ask first.
-----------------------------------------------------------------------
#include <cppx-core/all.hpp> //
https://github.com/alf-p-steinbach/cppx-core
namespace my
{
$use_cppx( is_empty );
using namespace cppx::basic_string_building; // "<<"-notation.
$use_std(
clog, endl,
exception,
move,
mutex,
queue,
string,
unique_lock
);
$use_std_namespace_names( this_thread );
void log( const string& s )
{
static mutex m;
$with( unique_lock<mutex>( m ) ) { clog << s << endl; }
}
class Mutually_exclusive_execution
{
using Lock = unique_lock<mutex>;
mutex m_q_access;
queue<Lock*> m_execution_locks_q;
static auto popped( queue<Lock*>& q )
-> Lock*
{
Lock* result = q.front();
q.pop();
return result;
}
void yield( const bool do_wait )
{
mutex m;
Lock execution_lock( m );
log( "Thread "s << this_thread::get_id() << " yielding." );
$with( Lock( m_q_access ) )
{
if( not is_empty( m_execution_locks_q ) )
{
log( "Thread "s << this_thread::get_id() << "
releasing another thread." );
popped( m_execution_locks_q )->unlock(); //
Another thread runs.
log( "Thread "s << this_thread::get_id() << "
release succeeded." );
}
if( do_wait )
{
try
{
log( "Thread "s << this_thread::get_id() << "
registering unlocker." );
m_execution_locks_q.emplace( &execution_lock );
}
catch( const exception& x )
{
log( "!"s << x.what() );
}
}
}
if( do_wait )
{
log( "Thread "s << this_thread::get_id() << " entering
wait state." );
//(Lock( m )); // Waits until m is unlocked by
another thread.
$with( Lock( m ) ) {}
}
log( "Thread "s << this_thread::get_id() << " resuming." );
}
public:
void yield() { yield( true ); }
void goodbye() { yield( false ); }
};
} // namespace my
namespace app
{
$use_cppx( up_to );
$use_std( cout, endl, thread );
$use_std_namespace_names( this_thread );
void run()
{
my::Mutually_exclusive_execution mee;
const auto f = [&]()
{
for( const int i: up_to( 10 ) )
{
mee.yield();
cout << "Thread " << this_thread::get_id() << "
iteration " << i << "." << endl;
}
mee.goodbye();
};
thread t1( f );
thread t2( f );
t1.join(); t2.join();
}
} // namespace app
#include <stdexcept> // std::exception
#include <stdlib.h> // EXIT_...
$use_std( exception, cerr, cout, endl );
auto main() -> int
{
try
{
cout << "Starting." << endl;
app::run();
cout << "Finished." << endl;
return EXIT_SUCCESS;
}
catch( const exception& x )
{
cerr << "!" << x.what() << endl;
}
return EXIT_FAILURE;
}
-----------------------------------------------------------------------
Cheers!,
- Alf (perplexed, baffled, confounded, bewildered & for the time being
stumped)