Discussion:
Undoing moving thread parameters
(too old to reply)
Bonita Montero
2024-05-08 18:43:43 UTC
Permalink
I tried to implement a thread-creation function which undos moving
thread-parameters if thread creation fails. That's not because of
necessity but just to try if this is possible:

#include <Windows.h>
#include <iostream>
#include <tuple>
#include <memory>
#include <utility>

using namespace std;

template<typename Fn, typename ... Args>
void xthread( Fn &&fn, Args &&... args )
{
using tupl_t = tuple<decay_t<Fn>, decay_t<Args> ...>;
auto argsSeq = make_index_sequence<sizeof ...(Args)>();
auto thr = []<size_t ... Indices>( index_sequence<Indices ...> )
{
return +[]( LPVOID lpvThreadParam ) -> DWORD
{
tupl_t &tupl = *(tupl_t *)lpvThreadParam;
get<0>( tupl )( get<Indices + 1>( tupl ) ... );
return 0;
};
};
auto fnObj = make_unique<tupl_t>( forward<Fn>( fn ), forward<Args>(
args ) ... );
HANDLE hThread = CreateThread( nullptr, 0, thr( argsSeq ), fnObj.get(),
0, nullptr );
if( hThread )
fnObj.reset(),
WaitForSingleObject( hThread, INFINITE );
else
{
auto back = []<typename Dst>( Dst &&dst, auto &&src )
{
if constexpr( is_rvalue_reference_v<Dst> && !is_const_v<Dst> )
dst = move( src );
};
back( forward<Fn>( fn ), get<0>( *fnObj ) );
[&]<size_t ... Indices>( index_sequence<Indices ...> )
{
(back( forward<Args>( args ), get<Indices + 1>( *fnObj.get() ) ), ...);
}( argsSeq );
}
}

int main()
{
string str( "hello world" );
xthread( []( string &str ) { cout << str << endl; }, move( str ) );
}

Unfortunatly the lambda passed to xthread doesn't see the str
-object from the passed tuple; it just sees nulled memory by
accident and thereby an empty strign object. But the function
-object itself inside the tuple is called correctly.
Bonita Montero
2024-05-08 19:58:34 UTC
Permalink
I changed .reset() for the tuple to .release and attached the thread
-parameter to a new unique_ptr<> inside the thread that the parameter
is released inside the thread.

#include <Windows.h>
#include <iostream>
#include <tuple>
#include <memory>
#include <utility>

using namespace std;

template<typename Fn, typename ... Args>
void xthread( Fn &&fn, Args &&... args )
{
using tupl_t = tuple<decay_t<Fn>, decay_t<Args> ...>;
auto argsSeq = make_index_sequence<sizeof ...(Args)>();
auto thr = []<size_t ... Indices>( index_sequence<Indices ...> )
{
return +[]( LPVOID lpvThreadParam ) -> DWORD
{
unique_ptr<tupl_t> pTupl( (tupl_t *)lpvThreadParam );
get<0>( *pTupl )( get<Indices + 1>( *pTupl ) ... );
return 0;
};
};
auto fnObj = make_unique<tupl_t>( forward<Fn>( fn ), forward<Args>(
args ) ... );
HANDLE hThread = CreateThread( nullptr, 0, thr( argsSeq ), fnObj.get(),
0, nullptr );
if( hThread )
fnObj.release(),
WaitForSingleObject( hThread, INFINITE );
else
{
auto back = []<typename Dst>( Dst &&dst, auto &&src )
{
if constexpr( is_rvalue_reference_v<Dst> && !is_const_v<Dst> )
dst = move( src );
};
back( forward<Fn>( fn ), get<0>( *fnObj ) );
[&]<size_t ... Indices>( index_sequence<Indices ...> )
{
(back( forward<Args>( args ), get<Indices + 1>( *fnObj.get() ) ), ...);
}( argsSeq );
}
}

int main()
{
string str( "hello world" );
xthread( []( string &str ) { cout << str << endl; }, move( str ) );
}
Bonita Montero
2024-05-09 06:46:42 UTC
Permalink
Here the same for Posix:

#include <pthread.h>
#include <iostream>
#include <tuple>
#include <memory>
#include <utility>

using namespace std;

template<typename Fn, typename ... Args>
void xthread( Fn &&fn, Args &&... args )
{
using thread_t = void *(*)( void * );
using tupl_t = tuple<decay_t<Fn>, decay_t<Args> ...>;
auto argsSeq = make_index_sequence<sizeof ...(Args)>();
auto thr = []<size_t ... Indices>( index_sequence<Indices ...> ) ->
thread_t
{
return []( void *param ) -> void *
{
unique_ptr<tupl_t> pTupl( (tupl_t *)param );
get<0>( *pTupl )( get<Indices + 1>( *pTupl ) ... );
return nullptr;
};
};
auto fnObj = make_unique<tupl_t>( forward<Fn>( fn ), forward<Args>(
args ) ... );
if( pthread_t handle; !pthread_create( &handle, nullptr, thr( argsSeq
), fnObj.get() ) )
fnObj.release(),
pthread_join( handle, nullptr );
else
{
auto back = []<typename Dst>( Dst &&dst, auto &&src )
{
if constexpr( is_rvalue_reference_v<Dst> && !is_const_v<Dst> )
dst = move( src );
};
back( forward<Fn>( fn ), get<0>( *fnObj ) );
[&]<size_t ... Indices>( index_sequence<Indices ...> )
{
(back( forward<Args>( args ), get<Indices + 1>( *fnObj.get() ) ), ...);
}( argsSeq );
}
}

int main()
{
string str( "hello world" );
xthread( []( string &str ) { cout << str << endl; }, move( str ) );
}
Ross Finlayson
2024-05-10 01:20:18 UTC
Permalink
Post by Bonita Montero
#include <pthread.h>
#include <iostream>
#include <tuple>
#include <memory>
#include <utility>
using namespace std;
template<typename Fn, typename ... Args>
void xthread( Fn &&fn, Args &&... args )
{
using thread_t = void *(*)( void * );
using tupl_t = tuple<decay_t<Fn>, decay_t<Args> ...>;
auto argsSeq = make_index_sequence<sizeof ...(Args)>();
auto thr = []<size_t ... Indices>( index_sequence<Indices ...> ) ->
thread_t
{
return []( void *param ) -> void *
{
unique_ptr<tupl_t> pTupl( (tupl_t *)param );
get<0>( *pTupl )( get<Indices + 1>( *pTupl ) ... );
return nullptr;
};
};
auto fnObj = make_unique<tupl_t>( forward<Fn>( fn ), forward<Args>(
args ) ... );
if( pthread_t handle; !pthread_create( &handle, nullptr, thr(
argsSeq ), fnObj.get() ) )
fnObj.release(),
pthread_join( handle, nullptr );
else
{
auto back = []<typename Dst>( Dst &&dst, auto &&src )
{
if constexpr( is_rvalue_reference_v<Dst> && !is_const_v<Dst> )
dst = move( src );
};
back( forward<Fn>( fn ), get<0>( *fnObj ) );
[&]<size_t ... Indices>( index_sequence<Indices ...> )
{
(back( forward<Args>( args ), get<Indices + 1>(
*fnObj.get() ) ), ...);
}( argsSeq );
}
}
int main()
{
string str( "hello world" );
xthread( []( string &str ) { cout << str << endl; }, move( str ) );
}
I think that you're saying that there's a verification bug in
these compilers and that random code via linkages can
set otherwise inaccessible (according to the language) internals.

Yet, I'm not so sure that I understand the implications.

I think you mean that the threads switch, and reload, context.

Then I don't know if that's defined behavior or not.

Perhaps, if you use thread_local storage class, there is
anything to do with locals, as what would see a guarantee, ....

At any rate I'm rather awed.
Bonita Montero
2024-05-10 05:09:53 UTC
Permalink
Post by Ross Finlayson
I think that you're saying that there's a verification bug in
these compilers and that random code via linkages can
set otherwise inaccessible (according to the language) internals.
No, there's not a bug but a flaw in the runtime which doesn't
move back parameters of a thread if thread-creation fails.
Post by Ross Finlayson
Yet, I'm not so sure that I understand the implications.
I think you mean that the threads switch, and reload, context.
Then I don't know if that's defined behavior or not.
Perhaps, if you use thread_local storage class, there is
anything to do with locals, as what would see a guarantee, ....
At any rate I'm rather awed.
Seems you don't understand what I'm talking about.

Loading...