Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Start constexpr N threads at runtime

65 views
Skip to first unread message

Frederick Gotham

unread,
Mar 23, 2020, 4:45:56 AM3/23/20
to

If the amount of threads I want to create is a constexpr, but if this constexpr can be changed between compilations (for example from 3 to 4 depending on the platform), and if I don't want to allocate the "std::thread" objects on the heap, then is there a better way to do it than the following?

auto main(void) -> int
{
array<aligned_union<0,thread>::type, g_N_threads> storage_for_threads;

for ( unsigned i = 0; i != g_N_threads; ++i )
{
::new(&storage_for_threads[i]) thread(EntryPoint, i);
}

/* Do stuff */

for ( unsigned i = 0; i != g_N_threads; ++i )
{
thread &t = *reinterpret_cast<thread*>(&storage_for_threads[i]);

t.join();

t.~thread();
}
}

Previously when the amount of threads was set at 3, I just did:

thread t0(EntryPoint, 0);
thread t1(EntryPoint, 1);
thread t2(EntryPoint, 2);

but now I want something that automagically adjusts itself whenever I change the constexpr g_N_threads.


Melzzzzz

unread,
Mar 23, 2020, 6:13:19 AM3/23/20
to
On 2020-03-23, Frederick Gotham <cauldwel...@gmail.com> wrote:
>
> If the amount of threads I want to create is a constexpr, but if this constexpr can be changed between compilations (for example from 3 to 4 depending on the platform), and if I don't want to allocate the "std::thread" objects on the heap, then is there a better way to do it than the following?
>
> auto main(void) -> int
> {
> array<aligned_union<0,thread>::type, g_N_threads> storage_for_threads;
>
> for ( unsigned i = 0; i != g_N_threads; ++i )
> {
> ::new(&storage_for_threads[i]) thread(EntryPoint, i);

Why do you think this is better then std::vector<thread>?
Anyway only char[] is safe for placement new...

>


--
press any key to continue or any other to quit...
U ničemu ja ne uživam kao u svom statusu INVALIDA -- Zli Zec
Svi smo svedoci - oko 3 godine intenzivne propagande je dovoljno da jedan narod poludi -- Zli Zec
Na divljem zapadu i nije bilo tako puno nasilja, upravo zato jer su svi
bili naoruzani. -- Mladen Gogala

Paavo Helde

unread,
Mar 23, 2020, 6:33:27 AM3/23/20
to
On 23.03.2020 10:45, Frederick Gotham wrote:
>
> If the amount of threads I want to create is a constexpr, but if this constexpr can be changed between compilations (for example from 3 to 4 depending on the platform), and if I don't want to allocate the "std::thread" objects on the heap, then is there a better way to do it than the following?
>
> auto main(void) -> int
> {
> array<aligned_union<0,thread>::type, g_N_threads> storage_for_threads;
>
> for ( unsigned i = 0; i != g_N_threads; ++i )
> {
> ::new(&storage_for_threads[i]) thread(EntryPoint, i);
> }

You said that you don't want to allocate thread objects on heap, but
why? Is this some obsession about saving a nanosecond at the start of
the program?

Is your program meant for high-frequency trading at NYSE? No? Then
what's wrong with a std::vector<std::thread>?

I would understand this if you said this is about learning placement new
and implementing one's own containers from scratch. However, for holding
thread objects this does not make any sense as thread creation is anyway
a massively expensive operation when compared to a dynamic memory
allocation.

>
> /* Do stuff */
>
> for ( unsigned i = 0; i != g_N_threads; ++i )
> {
> thread &t = *reinterpret_cast<thread*>(&storage_for_threads[i]);

You should avoid reinterpret_cast if possible, it's not well-behaved in
general.

In this example you should either remember the addresses returned by
placement new, or use std::launder() for making the result pointer legal
(std::launder is meant for low-level container implementers, if you
insist on doing that you need to learn about it).

Alf P. Steinbach

unread,
Mar 23, 2020, 7:28:01 AM3/23/20
to
The above code is just weird.

You can do things like this:


#include <stdlib.h>

#include <array>
#include <chrono>
#include <iostream>
#include <mutex>
#include <thread>
using std::array, std::mutex, std::thread, std::cout, std::endl;
namespace this_thread = std::this_thread;
namespace chrono = std::chrono;

void output( const int i )
{
static mutex mx_streams;
mx_streams.lock();
cout << i << endl;
mx_streams.unlock();
}

void thread_func( const int i )
{
this_thread::sleep_for( chrono::milliseconds( rand() % 10 ) );
output( i );
}

auto main()
-> int
{
array< thread, 7 > threads;

// Start 'em up.
srand( 42 );
for( int i = 0, n = int( threads.size() ); i < n; ++i ) {
threads[i] = thread( thread_func, i );
}

// Join all.
for( int i = 0, n = int( threads.size() ); i < n; ++i ) {
threads[i].join();
}
}


- Alf

Öö Tiib

unread,
Mar 23, 2020, 8:04:11 AM3/23/20
to
On Monday, 23 March 2020 10:45:56 UTC+2, Frederick Gotham wrote:
> If the amount of threads I want to create is a constexpr, but if this constexpr can be changed between compilations (for example from 3 to 4 depending on the platform), and if I don't want to allocate the "std::thread" objects on the heap, then is there a better way to do it than the following?
>
> auto main(void) -> int
> {
> array<aligned_union<0,thread>::type, g_N_threads> storage_for_threads;

Use simply:

array<thread, g_N_threads> threads{};

Threads will be default constructed in "not representing a thread" state.
>
> for ( unsigned i = 0; i != g_N_threads; ++i )
> {
> ::new(&storage_for_threads[i]) thread(EntryPoint, i);


That becomes:

threads[i] = thread(EntryPoint, i);

> }
>
> /* Do stuff */
>
> for ( unsigned i = 0; i != g_N_threads; ++i )
> {
> thread &t = *reinterpret_cast<thread*>(&storage_for_threads[i]);
>
> t.join();
>
> t.~thread();

And that is just:

if (threads[i].joinable()) threads[i].join();

> }
> }
>
> Previously when the amount of threads was set at 3, I just did:
>
> thread t0(EntryPoint, 0);
> thread t1(EntryPoint, 1);
> thread t2(EntryPoint, 2);
>
> but now I want something that automagically adjusts itself whenever I change the constexpr g_N_threads.

Yes but I did not get the point of usage of aligned_union. The std::thread
is itself most likely just operating system resource handle enwrapped,
perhaps with size of single pointer.

Bonita Montero

unread,
Mar 23, 2020, 8:29:02 AM3/23/20
to
> That becomes:
> threads[i] = thread(EntryPoint, i);

The assignment-operator of thread is move-only.

Bonita Montero

unread,
Mar 23, 2020, 8:32:17 AM3/23/20
to
>> That becomes:
>>                   threads[i] = thread(EntryPoint, i);

> The assignment-operator of thread is move-only.

Sorry, nonsense, move isn't necessary here.

Öö Tiib

unread,
Mar 23, 2020, 8:36:33 AM3/23/20
to
What is your point? A rvalue of type std::thread can be assigned
as the quoted line of code does.

cda...@gmail.com

unread,
Mar 23, 2020, 9:10:36 AM3/23/20
to
On Monday, March 23, 2020 at 3:33:27 AM UTC-7, Paavo Helde wrote:
> On 23.03.2020 10:45, Frederick Gotham wrote:
> >
> > If the amount of threads I want to create is a constexpr, but if this constexpr can be changed between compilations (for example from 3 to 4 depending on the platform), and if I don't want to allocate the "std::thread" objects on the heap, then is there a better way to do it than the following?
> >
> > auto main(void) -> int
> > {
> > array<aligned_union<0,thread>::type, g_N_threads> storage_for_threads;
> >
> > for ( unsigned i = 0; i != g_N_threads; ++i )
> > {
> > ::new(&storage_for_threads[i]) thread(EntryPoint, i);
> > }
>
> You said that you don't want to allocate thread objects on heap, but
> why? Is this some obsession about saving a nanosecond at the start of
> the program?
>
> Is your program meant for high-frequency trading at NYSE? No? Then
> what's wrong with a std::vector<std::thread>?
>
> I would understand this if you said this is about learning placement new
> and implementing one's own containers from scratch. However, for holding
> thread objects this does not make any sense as thread creation is anyway
> a massively expensive operation when compared to a dynamic memory
> allocation.
>
> >
> > /* Do stuff */
> >
> > for ( unsigned i = 0; i != g_N_threads; ++i )
> > {
> > thread &t = *reinterpret_cast<thread*>(&storage_for_threads[i]);
>
> You should avoid reinterpret_cast if possible, it's not well-behaved in
> general.
>

Captain numb nuts was told about this in a earlier thread. But just like everything else, the moron chooses to ignore this advice. Or maybe he doesn't ignore the advice and instead, is like...uhhh..what's a nice way to this this.. maybe he is mentally deficient.

Bonita Montero

unread,
Mar 23, 2020, 9:17:22 AM3/23/20
to
>>> That becomes:
>>> threads[i] = thread(EntryPoint, i);

>> The assignment-operator of thread is move-only.

> What is your point? A rvalue of type std::thread can be assigned
> as the quoted line of code does.

Read my other post before responding overhasty.
I didn't consider that the temporary is implicitly moveable.

Frederick Gotham

unread,
Mar 25, 2020, 5:32:19 AM3/25/20
to
On Monday, March 23, 2020 at 12:04:11 PM UTC, Öö Tiib wrote:

> if (threads[i].joinable()) threads[i].join();


I only ever call "join" without checking that it's 'joinable' beforehand (even if I know that the thread entry point function might have returned).

Even if you do check that it's joinable first, it might not be joinable a few microseconds later when you try to join it.

Can't we just simply call "join"?

Öö Tiib

unread,
Mar 25, 2020, 5:50:30 AM3/25/20
to
That sounds like you call join concurrently?
It is AFAIK specified to be data race (results with undefined behavior).

>
> Can't we just simply call "join"?

AFAIK it will throw std::system_error with code() returning
std::errc::invalid_argument .

Alf P. Steinbach

unread,
Mar 25, 2020, 6:17:46 AM3/25/20
to
For the scenario at hand you can, and should.

However, a default-constructed `std::thread` is not joinable.


- Alf

Bonita Montero

unread,
Mar 25, 2020, 6:23:25 AM3/25/20
to
> And that is just:
>
> if (threads[i].joinable()) threads[i].join();


If you have C++20, better use jthread.

Öö Tiib

unread,
Mar 25, 2020, 6:26:44 AM3/25/20
to
The joined thread is also not joinable and ...

/* Do stuff */

... does not specify if it contains joins or not so my spinal cord
added the check of joinable(), I did not even think of it.


Öö Tiib

unread,
Mar 25, 2020, 6:31:11 AM3/25/20
to
C++2020 can not be used in real projects. It usually takes 2 years
or more for compilers to fix their defects and for teams to prepare
to migrate.

Frederick Gotham

unread,
Mar 25, 2020, 7:16:58 AM3/25/20
to
On Wednesday, March 25, 2020 at 10:31:11 AM UTC, Öö Tiib wrote:

> C++2020 can not be used in real projects. It usually takes 2 years
> or more for compilers to fix their defects and for teams to prepare
> to migrate.

You know those guys who still program in K&R C and refuse to move on to C89?

Well 20 years from now I'm gonna be the guy stuck in C++11. They're just adding too much stuff.

David Brown

unread,
Mar 25, 2020, 7:23:12 AM3/25/20
to
On 25/03/2020 12:16, Frederick Gotham wrote:
> On Wednesday, March 25, 2020 at 10:31:11 AM UTC, Öö Tiib wrote:
>
>> C++2020 can not be used in real projects. It usually takes 2 years
>> or more for compilers to fix their defects and for teams to prepare
>> to migrate.
>
> You know those guys who still program in K&R C and refuse to move on to C89?

No. They don't exist. (There are programs written in K&R C that are
still maintained, and people may do so without modernising the language.
But that's a different thing.)

There are plenty of people who program in C89/C90 and haven't moved on
to C99.

>
> Well 20 years from now I'm gonna be the guy stuck in C++11. They're just adding too much stuff.
>

There are plenty of people who stick to C++11. It seems a bit silly to
me, if you have the choice (there can be many reasons why you might not
have a choice). If there are features of C++14 or later that you find
useful, use them - if you don't like a feature, don't use it.

Öö Tiib

unread,
Mar 25, 2020, 8:38:24 AM3/25/20
to
On Wednesday, 25 March 2020 13:16:58 UTC+2, Frederick Gotham wrote:
> On Wednesday, March 25, 2020 at 10:31:11 AM UTC, Öö Tiib wrote:
>
> > C++2020 can not be used in real projects. It usually takes 2 years
> > or more for compilers to fix their defects and for teams to prepare
> > to migrate.
>
> You know those guys who still program in K&R C and refuse to move on to C89?

No but if your C code has to be portable to MSVC compilers then you
are forced by that fact alone to use something between C89 and C90.

> Well 20 years from now I'm gonna be the guy stuck in C++11. They're just adding too much stuff.

Nah. If people their thoughts at least semi-coherently expressed in
some human language (like English) then C++20 should not be too hard
for them either.
I'm just advocating not using it for production code at least two
years because initially compilers and standard libraries contain
defects, non-standard libraries and development tools are
not up to date for it and all developers are also not yet trained
well enough to use it.

Chris Vine

unread,
Mar 25, 2020, 9:16:34 AM3/25/20
to
On Mon, 23 Mar 2020 10:13:09 GMT
Melzzzzz <Melz...@zzzzz.com> wrote:
[snip]
> Anyway only char[] is safe for placement new...

I suspect that as, regards std::aligned_union, this might be another
technical defect in the C++ standard. The explicit purpose of
std::aligned_union is to provide a member typedef 'type' which "shall
be a POD type suitable for use as uninitialized storage for any object
whose type is listed in Types" (§23.15.7.6 of C++17, table 50)

C++17 has inherited std::aligned_union from C++11, but §4.5/3 of C++17
seems to have forgotten about it - even more curiously, the example in
§4.5/3 provides a user-version of an AlignedUnion type rather than
employ std::aligned_union.

One possible interpretation of C++17 is that it now requires
std::aligned_union::type to be a typedef to a struct having as it only
member an array of unsigned char or of std::byte. In that case, the
OP's code does not work because in C++17 it should use std::launder to
access the objects created in the underlying storage (or store the
return value of placement new, which is not feasible in the OP's case).

James Kuyper

unread,
Mar 25, 2020, 10:14:21 AM3/25/20
to
On 3/25/20 8:38 AM, Öö Tiib wrote:
...
> No but if your C code has to be portable to MSVC compilers then you
> are forced by that fact alone to use something between C89 and C90.
I think you mean C90 and C99.

C89 is the original ANSI standard for C. C90 is the first ISO standard
for C. ISO's standards for how ISO standards must be written required
that three sections be added - those are the first three sections of the
current standard. Except for those three sections, C89 and C90 are
essentially identical.
"between C89 and C90" would therefore mean that it's missing some part
of the first three sections of C90. As far as I can see, that wouldn't
have any effect that matters as far the compiler is concerned.

Öö Tiib

unread,
Mar 25, 2020, 11:07:36 AM3/25/20
to
On Wednesday, 25 March 2020 16:14:21 UTC+2, James Kuyper wrote:
> On 3/25/20 8:38 AM, Öö Tiib wrote:
> ...
> > No but if your C code has to be portable to MSVC compilers then you
> > are forced by that fact alone to use something between C89 and C90.
> I think you mean C90 and C99.

Yes, thanks for correcting, that was what I meant but somehow didn't type.

Melzzzzz

unread,
Mar 26, 2020, 4:21:45 AM3/26/20
to
No. joining on detached thread is UB.

Öö Tiib

unread,
Mar 26, 2020, 4:37:23 AM3/26/20
to
On Thursday, 26 March 2020 10:21:45 UTC+2, Melzzzzz wrote:
> On 2020-03-25, Frederick Gotham <cauldwel...@gmail.com> wrote:
> > On Monday, March 23, 2020 at 12:04:11 PM UTC, Öö Tiib wrote:
> >
> >> if (threads[i].joinable()) threads[i].join();
> >
> >
> > I only ever call "join" without checking that it's 'joinable' beforehand (even if I know that the thread entry point function might have returned).
> >
> > Even if you do check that it's joinable first, it might not be joinable a few microseconds later when you try to join it.
> >
> > Can't we just simply call "join"?
>
> No. joining on detached thread is UB.

AFAIK it throws on non-joinable thread (not UB) and uncaught exception
(is also not UB but) just has often undesirable result.

Melzzzzz

unread,
Mar 26, 2020, 4:40:05 AM3/26/20
to
Well, I use pthreads, I concluded from there ;)
0 new messages