Day 11 - Multithreading Question

https://youtu.be/_4vnV2Eng7M?t=3802

In Day 11 Casey Answers a question about multi-threading:
"would you always orchestrate how you do threading in the platform layer or does it make sense for the platform layer to also provide a more generic threading job service?"

Casey's Answer, as linked in the video is that he prefers it to be orchestrated by the Platform/OS code.

I really did not get his answer/reasoning though or really what the question was asking? What exactly are the 2 options being debated here. I guess by Job system the questioner meant a ability for the actual game code to specify they wanted a certain operation to be multi-threaded (for example specific parts of a Physics simulation). But, Casey says this is bad because "threading often times in games is directly tied to resource management in a lot of places;" he says threads are often used to "service resource readiness," such as File IO, and thus should be wholly managed by the platform Layer. I do not get what he means by this. It seems like Casey is proposing that all multi-threading code should be entirely in the Platform Layer, but I don't even know how this could be achieved, since wouldn't the game actually be the thing that needs to multi-thread stuff (rendering, physics, etc) + i simply don't get how IO/Resource operations are actually related at all to wanting Thread code in the platform layer.

Edited by Blu342 on Reason: Initial post
I think the question was about whose responsibility is it to manage threads. Is it the platform layer, or the game layer.

If it's the responsibility of the platform, you have code that looks like this:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* In the platform layer */
void thread_execute( ){
    while ( true ){
        wait_for_work( );
        game_function( game_data ); /* This executes game code from the platform layer */
        signal_finished( );
    }
}

int platform_main( ){
    threads t[ x ] = { 0 };
    for ( int i = 0; i < x; i++ ) {
        t[ i ] = thread_create( );
    }
    ...
    while ( running ) {
        game_update_and_render( );
    }
}
/* In the game layer*/
int game_main( ){
    ...
    platform_add_work_in_thread_queue( game_function, game_data );
    ...
}


If it's the responsibility of the game:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* In the platform layer*/
int thread_create( void thread_function( thread_data* ) ){ ... }

int platform_main( ) {
    while ( running ) {
        game_update_and_render( );
    }
}

/* In the game layer */
void game_thread_function( ) {
    while ( true ) {
        thread_wait_for_work( );
        game_func( game_data );
        thread_signal_finished( );
    }
}

int game_main( ) {
    ...
    /* executed once */
    thread t[x] = { 0 };

    for ( int i = 0; i < x; i++ ) {
        thread_create( game_thread_function );
    }

    /* when needed */
    add_work_in_work_queue( game_func, game_data );
    ...
}


The second case looks very similar to the first one, the changes are basically that some code switched from the platform layer to the game layer. But to allow that, the platform layer has to abstract how threads work on every platform we want the application to run on to provide a common API. Creating, waiting and signaling thread is pretty different on each platform, so it adds complexity when none is necessary.

Threading is used in two cases: when you need more performance and want to execute more instructions at the same time, or when you want to do some work without blocking the main thread. I/O is in that second case. You don't want your application to stop while you're loading your textures, or waiting for a network response. Here once again, the API between platform for asynchronous I/O is different so you don't want to abstract that.
great answer. i certainly understand waaaay better, and I agree with the first part wholeheartedly, but I am still a bit confused about why Casey brought up IO stuff. Absolutely, on Windows we may use something like overlapped IO, etc over a worker thread for doing IO work, but how would this contribute to Casey wanting to keep thread management in the Platform. I suppose the reason is that, in your example, the game_thread_function would need to change to accomodate different async IO methods, should a specific platform use threads; however, that effectively means we require an #if, which is kinda what we are avoiding in the first place.

Edited by Blu342 on
You can do async I/O with a Thread + regular I/O functions, or using a specific OS API. In either way, the API and OS specificity will be different on each platform, so it's best to keep that code in the platform layer instead of trying to make all platform version fit in a single API that the platform layer would provide to the game layer.

Threads and async I/O can be different systems so you can choose to put them where you want independently.