0

In my game there are two (relevant for this problem) threads (both are joinable), one is player and one is shot. When the thread player is created, the start routine function is called, in which the shot thread is created. In that function, there is a while loop in which the user decides the direction of movement on the map or shot, in the case of a shot, I create shot thread and the player should move while the shot is active, while only one shot at a time can be active. My problem is, when I use the pthread_join function on the shot (to execute one shot at a time) where I create a shot thread, the player remains frozen. My question is, where is the right place to use pthread_join without it interfering with player movement. I have already tried to use join in the start routine function of the shot thread, and in the main program (main), none of it works as it should. (the code example serves only as an approximate representation of what happens in the program).

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

pthread_t player_tid;
pthread_t shot_tid;
int life_points = 100;

typedef struct
{
    unsigned char x_;
    unsigned char y_;
    unsigned char type_;
    unsigned char index_;
} parametars;

void *placeShot(parametars *params)
{
// TODO start: find if shot hit an alien, remove shot and alien if so. // Hint: I have already implemented that, works fine.
    printf("Shot coordinates: %d, %d", params->x_, params->y_);
    free(params);
// TODO end;
    return NULL;
}

void *playerLogic()
{
    int c;
    
    while (life_points > 0)
    {
        char shot = 0;
        c = getchar();
        switch (c)
        {
        case ' ':
            shot = 1;
            break;
        default:
            break;
        }
        if (c == 'q')
        {
            life_points = 0;
            continue;
        }
// TODO start: spawn the shot and make sure to wait until the last shot is //finished
        parametars *params;
        if (shot)
        {
            params = malloc(sizeof(parametars));
            params->x_ = 3;
            params->y_ = 2;
            if (pthread_create(&shot_tid, NULL, (void *(*)(void *)) & placeShot, params) != 0)
            {
                printf("Failed to create shot thread");
            }
        }
// TODO end
    }
   
    return NULL;
}

int main()
{
    // TODO create a player thread with already provided resources
    if (pthread_create(&player_tid, NULL, (void *(*)(void *)) & playerLogic, NULL) != 0)
        printf("Failed to create player thread");

    if (pthread_join(player_tid, NULL) != 0)
        printf("Failed to join player thread");

    return 0;
}
Milan
  • 1
  • 2
  • 3
    Threads are an inappropriate tool for this task. The complexity of managing and synchronizing your threads correctly will become a nightmare as your game evolves. I recommend you manage all the game entities centrally, and iterate through them in an update step. If they require lots of processing to improve frame rates, you can dispatch unsynchronized processing jobs via a thread pool. But if you don't 100% need threads for this game, I highly recommend you avoid them completely. – paddy Mar 07 '23 at 22:38
  • 1
    I have to use threads, because the assignment is based on it. I am also not allowed to use any kind of locks. – Milan Mar 07 '23 at 22:42
  • Okay, well as a more direct observation, you never reset `shot`. Once it becomes non-zero, it stays that way. It will create a new thread every time around the loop, and the loop never ends because you do not update `life_points`. Your `playerLogic` function spells doom for the program. Eventually, the OS will begin to fail creating more threads as it struggles to keep up, at which time you start leaking memory. Eventually, allocation fails and you dereference a NULL pointer. – paddy Mar 07 '23 at 22:44
  • 2
    On another note, you are using casts to pretend your function signatures are something they are not. You _must_ use the correct signature: `void *threadFunc(void *arg)`. You can cast `arg` to a `parametars` [sp] pointer later. And the player function can just ignore its arg if it doesn't need one. Your thread creation should be able to compile like this: `pthread_create(&shot_tid, NULL, placeShot, params)` – paddy Mar 07 '23 at 22:55
  • I appreciate your time and help, but neither of them is my case, as I'm only allowed to change certain lines of code. So this is not the case with changing parameters from functions. The mentioned shot is reset in the original code example, so this is not the case. I just need to find a place to join the shot thread without stopping the player from moving. – Milan Mar 07 '23 at 23:32
  • 1
    @Milan: Please [edit] the question to specify exactly which lines you are allowed to change and which lines you are not allowed to change. Any restrictions you have should be mentioned in the question itself. – Andreas Wenzel Mar 07 '23 at 23:34
  • Thank you for you patience and advice, I hope that is more clear right now. There are todos in the code and I am only allow to modify code between them. – Milan Mar 07 '23 at 23:47
  • [You may be interested in this question](https://stackoverflow.com/questions/2156353/how-do-you-query-a-pthread-to-see-if-it-is-still-running/9870708#9870708). – user3386109 Mar 07 '23 at 23:56
  • 1
    You only store a single thread ID for a shot: `shot_tid`. So, the only plausible thing you can do here is when you need to fire another shot, you perform the join before creating that thread again. Otherwise you'll overwrite `shot_tid` and lose the ability to know anything about that previous thread. – paddy Mar 07 '23 at 23:58
  • Also consider [pthread_tryjoin_np](https://www.man7.org/linux/man-pages/man3/pthread_tryjoin_np.3.html) – paddy Mar 08 '23 at 00:04
  • @paddy That's true, I have to join the thread before I fire another shot, but again the problem is that the player remains frozen and can't move, while the shot is active. The player thread should run independently of the shot behavior, I thought about using a detached thread in this case, but the threads should be joinable at the end. – Milan Mar 08 '23 at 00:05
  • @user3386109 and paddy - thank you both for the advice, I am going to read about these functions and reconsider the options. – Milan Mar 08 '23 at 00:11
  • If you are not going to maintain any other state about whether a shot is active or not, then your only real option is to use a non-blocking join like I linked to just above. If the join fails with `EBUSY`, then you do _not_ allow the shot to be taken. Just continue looping. – paddy Mar 08 '23 at 00:11
  • Of course the player remains frozen while the thread that controls its movement is blocked on joining the shot thread. To avoid that, you must either use a non-blocking join (of which there is none in POSIX; the `_np` in `pthread_tryjoin_np` is mnemonic for "non-POSIX"), or you must avoid attempting to join until you know you will succeed immediately. Alternatively, you could detach the shot thread and never join it at all. – John Bollinger Mar 08 '23 at 00:55
  • 1
    Not being able to use "locks" seems a pretty limiting restriction in a multi-threaded program. I say "locks" with quotes because POSIX offers a number of synchronization objects that can be used for locking-type operations, but none of the most commonly-used ones (mutexes, semaphores) actually have "lock" in their names. What are the actual limitations? – John Bollinger Mar 08 '23 at 01:01

0 Answers0