I have a task in which I need to create a console game in the form of a road junction, it was created to study threads in the C language. My goal is to create a stream that would accept motion commands from the keyboard and at the same time the movement of imaginary typewriters on the screen should work. I read about threads, it seems like I understood the main point, but I can’t do simultaneous work, at startup either one part works or the other.

View of the program in the console (x - typewriter, @ - player), you must switch to the other side

# # # # # # # ##################

XXXX XX XX X XX XXXX XXXX XXXXX XX XX XX @ 

################################################# ###################

 int main(int argc, char** argv) { //Initialize game components srand(time(0)); screen = initscr(); if(screen == NULL) exit(-1); wresize(screen, SCREEN_HEIGHT, SCREEN_WIDTH ); old_cursor = curs_set(0); froggy.x = SCREEN_WIDTH / 2; froggy.y = SCREEN_HEIGHT - OUTER_BORDER - 1; initMotorway(); pthread_t thread; int status; status = pthread_create(&thread, NULL,catch_input(), NULL); if (status != 0) { printf("main error: can't create thread, status = %d\n", status); exit(10); } while(!quit && !error && !player_lost && !player_won) { moveFroggy(); moveCarsOnMotorway(); startCar((SCREEN_WIDTH - OUTER_BORDER)); drawScreen(); usleep(GAME_SPEED); } if(pthread_join(thread, NULL)) { printf("main error: can't join the thread"); exit(11); } ... 

If I place a call to a thread

 status = pthread_create(&thread, NULL, moveFroggy(), NULL); 

before calling basic functions with graphics

 while(!quit && !error && !player_lost && !player_won) { moveCarsOnMotorway(); startCar((SCREEN_WIDTH - OUTER_BORDER)); drawScreen(); usleep(GAME_SPEED); } 

Then when compiling, an empty window appears in the terminal and it seems to read commands, although I am not sure about that either, because the prints that I wrote in the MoveFroggy () function are not displayed, if you do the opposite, then moving machines are displayed but the method does not work commands from the keyboard.

Listing MoveFroggy ()

 void moveFroggy(char buf) { pthread_mutex_lock(&my_mutex); if((first_frogy=false)){ froggy.y = SCREEN_HEIGHT - OUTER_BORDER; first_frogy = true; } if((checker = true)){ if((buf == 'a') && (froggy.x > OUTER_BORDER)){ froggy.x--; refresh(); } if(buf == 'd' && (froggy.x < (SCREEN_WIDTH - OUTER_BORDER))){ froggy.x++; refresh(); } if (buf == 'w' && (froggy.y >= (SCREEN_HEIGHT - NUM_LANES - OUTER_BORDER - GRASS_BORDER))) froggy.y--; if (buf == 's' && (froggy.y < (SCREEN_HEIGHT - OUTER_BORDER))) froggy.y++; if( buf == 'q'){ pthread_mutex_unlock(&my_mutex); exit(1); } if(froggy.y <= (SCREEN_HEIGHT - NUM_LANES - OUTER_BORDER - GRASS_BORDER)) player_won = 1; pthread_mutex_unlock(&my_mutex); } else{ pthread_mutex_unlock(&my_mutex); } } 

Thanks in advance for your help

UPDATE:

Declared global options and structures necessary for the program to work, wrote a mutex from below, will it capture all declared variables when calling and only the last ??

 // Represents a specific screen positions by Cartesian coordinates typedef struct{ int x; int y; } Point; // Cars on the motorway are organized using a linked list // Every list element stores the car's coordinates and a pointer to the next car typedef struct Car { Point position; struct Car* next; } Car; // List of cars on motorway (pointer to first list element) Car* motorway; // Position of froggy on screen Point froggy; // Variables to represent current game state unsigned int error; unsigned int quit; unsigned int player_lost; unsigned int player_won; bool checker; bool first_frogy; // Other global variables WINDOW* screen; int old_cursor = 0; char new_input; pthread_mutex_t my_mutex; 

Made a separate function, according to the idea it should save input to a global variable, and the moveFroggy function will be called with all drawing functions from the main stream, and if there was a new input (checked by a bool variable), then change the state, if not, pass by

 void* catch_input(){ char buf; while((buf = getchar())){ pthread_mutex_lock(&my_mutex); new_input = buf; checker = true; pthread_mutex_unlock(&my_mutex); } pthread_exit(NULL); } 

But still, only the stream catch_input works when compiling, but the main thread for some reason does not work and the drawing does not start, if you comment the catch_input flow, then the drawing works. What’s the problem?

  • Do you use the ncurses library? - Yaroslav
  • Yes (..........) - Anton Barinov
  • set up getchar() processing in non-binary mode - Yaroslav

2 answers 2

If we assume that one of the threads calculates the movement of the machines, and takes over the rendering, and the second, accepts user commands, then you should take into account several important things:

  1. For both your streams, there is a common dataset - say, an array with the current position of the cars and the “pedestrian” on the map, if a separate stream is involved in drawing. In the example you cited, where the calculation of the movement of cars and the display fit in one stream, the position of the pedestrian will be this general data. This data must be global. At each time, these data should be consistently displayed on the screen.
  2. Since these are shared data, calls to them in both threads must be synchronized using a mutex. You cannot change the position of a pedestrian in one of the flows without resorting to capturing a mutex, since the second flow at this time can (or even should!) Use the position of the pedestrian to be displayed on the screen.

Therefore we come to this option:

  1. In the stream function that accepts user commands, after entering the stroke, the position of the pedestrian changes under the mutex

     while((buf=getchar())!='q') { pthread_mutex_lock(my_mutex); if((buf == 'a') && (froggy.x > OUTER_BORDER)) froggy.x--; if((froggy.x < (SCREEN_WIDTH - OUTER_BORDER))) froggy.x++; ... pthread_mutex_unlock(my_mutex); } 
  2. Either capture the same mutex at the time of display, or before displaying the picture on the screen, under the mutex, copy the position of the pedestrian to display correctly:

     ... moveCarsOnMotorway(); startCar((SCREEN_WIDTH - OUTER_BORDER)); pthread_mutex_lock(my_mutex); temp_froggy = froggy; pthread_mutex_unlock(my_mutex); drawScreen(); ... 
  3. When displayed, draw the saved pedestrian coordinates.

  4. It is absolutely wrong to try to display something from both streams on the screen (all output from the stream reading user commands must be removed), nothing good will come of it. It is better when adding a picture to add to the beginning a static label with a hint of keys for moves.

UPDATE

  1. In connection with this question, should they be exclusively in the stream and cannot be called from main () *?

    When you call main (), it is already 1 thread. When you create another pthread_create () function inside, there are already two of them, that's why I described two. Accordingly, the drawing functions can also be called from main (), the main thing is that these functions draw the data already agreed with the second flow (the position of the pedestrian must be copied under the mutex and passed to the drawing function, or the entire drawing function that uses the global variable with the position of the pedestrian, should be called under the mutex).

  2. is it enough to write one mutex, and it will act on all variables declared globally?

    A mutex is protected by a common resource; it can be at least a variable, at least ten variables, arrays or structures, if they are logically connected. It is important that if the mutex is properly used, while it is captured by one thread, other threads cannot capture it, and will hang waiting for the mutex to be released. At this time, the thread that captures the mutex must work alone with the protected data. That is, if there is any data common to the streams, they cannot be read or modified at all without first capturing the mutex.

  • Thanks for the detailed answer. I don’t want to display anything in the state change function (I wrote these prints for the test, to see if this stream works in principle), it will only change the player’s position, I’ll try to deal with the mutex. As I understand it, I will need to implement the second thread, in which the drawing functions will be called? In this regard, the question is, they must be exclusively in the thread and can not be called from main () and if so, is there any difference in the syntax between the function call from the thread and from main ()? or just write a standard call? Thanks - Anton Barinov
  • And one more question, is it enough to write one mutex, and it will act on all variables declared globally? I understand this correctly? - Anton Barinov
  • 1) You need to understand that when you call main (), this is already 1 thread. When you create another pthread_create () function inside, there are already two of them, that's why I describe two. - margosh
  • I changed the listings in main, created a new function that would handle the input, and passed this input to the global variable for further processing in the main stream with the moveFroggy () function, but still not working, described the problem at the bottom of the question - Anton Barinov
  • one
    In the ncurses library, in order for the grtchar() function to work in non-blocking mode, you need to configure the input using the functions of the ncurses library , in particular, nodelay() or halfdelay() - Yaroslav

In this example, I do not describe a workable program, but I show the logic of my implementation, in a simplified form, the interaction of threads using the ncurses library

In the main thread, the main() function works with the ncurses library to draw and handle keystrokes. In the second thread, the calculate() function takes place all the calculations.

To work with threads, we create three blocks of variables (mixing along the X axis and mixing along the Y axis)

 int x_1; /*переменые первого потока*/ int y_1; int x_2; /*переменые второго потока*/ int y_2; int x_t; /*переменые проброса*/ int y_t; 

At the beginning, we initialize the ncurses library and configure the main program variables.

 initscr(); cbreak(); /*убрать режим буфферизации*/ noecho(); /*не выводить набираемые символы */ nodelay(stdsrc,TRUE); /*сделать не блокирующий режим*/ keypad(stdsrc,TRUE); /*не обрабатывать служебные клавиши*/ 

we initialize a mutex for working with threads

 pthread_mutex_t mutex; pthread_mutex_init(&mutex,NULL); 

start the function of the second thread

  pthread_t thread; pthread_create(&thread, NULL,calculate, NULL); 

in the main() function go to the main loop

 for(;;){ buf = getchar(); if(buf != ERR){ /*нажата клавиша*/ switch(buff){ ..... case 'w': y_1++; break; ..... } } /*захватываем мьютекс */ pthread_mutex_lock(&mutex); /*записываем переменые */ y_t = y_1; x_1 = x_t; /*освобождаем мьютекс*/ pthread_mutex_unlock(&mutex); /*прорисовываем элементы на экране */ draw_screen(y_1,x_1); /*если прорисовка 25 кадров в секунду засыпаем на 40 микросекунд*/ usleep(40); } 

displacement calculation function

 calculate() { for(;;){ pthread_mutex_lock(&mutex); /*записываем переменые */ y_2 = y_t; pthread_mutex_unlock(&mutex); /*проводим рассчет перемешения*/ x_2 = calculate(y_2); pthread_mutex_lock(&mutex); /*записываем переменые */ x_t = x_2; pthread_mutex_unlock(&mutex); } usleep(10); } 
  • The problem is that this is a training program, and in it all the libraries and functions are declared and made correctly. My task is only to understand the streams, and to make a parallel work of accepting commands from the keyboard and displaying graphics, and unfortunately there is not much information on streams on the network - Anton Barinov
  • maybe it makes sense to run every single function in its thread? - Anton Barinov
  • Thanks for the answer, and I found the problem, everything turned out to be much simpler, I just had to remove the brackets after the function name in the stream declaration: status = pthread_create (& thread, NULL, moveFroggy, NULL); and it all worked in a magical way - Anton Barinov
  • one
    Yes, a simple typo, and you will not notice right away, - Yaroslav