====== Lab06 - Multi-thread programming ====== The goal is to implement a program with three threads running simultaneously, where the first thread is devoted for processing input (reading pressed keys), the second thread is dedicated for updating output (single line output), the third thread implements an timer, which increments a variable after a given period, which can be controlled by the user input. In this case, ''pthread'' (POSIX threads) library is used. To do that, implement simple blocks demonstrating usage of ''pthread'' library. ===== First threads ===== * You can use a template project {{:courses:be5b99cpl:labs:lab06.zip|}} * Create two function to encapsulate body of individual threads void* thread1(void*); void* thread2(void*); * In ''thread1()'', implement a loop in which a global value ''counter'' is incremented after 100 ms, e.g., void* thread1(void *v) { bool q = false; /* from the */ while (!q) { usleep(100 * 1000); /* from the */ counter += 1; } return 0; } * In ''thread2()'', implement a loop to update a line with the current value of the ''counter'', e.g., void* thread2(void *v) { bool q = false; while (!q) { printf(*"\rCounter %10i", counter); } return 0; } *Create threads using the ''pthread_create()'' function and passing the pointer to the particular function ''thread1()'' and ''thread2()'' in the main function, e.g., counter = 0; pthread_t thrs[2]; pthread_create(&thrs[0], NULL, thread1, NULL); pthread_create(&thrs[1], NULL, thread2, NULL); Once the thread is created, it is started and it goes to the passed function, which executed immediately. * Wait for pressing ''Enter'' by the user and then wait for the threads by calling ''pthread_join()'', e.g., getchar(); for (int i = 0; i < 2; ++i) { pthread_join(thrs[i], NULL); } ===== Use mutex for protecting critical section ===== In general, whenever we access to shared variable from multiple threads it is desirable to protect the access by a critical section, a lock that will allow only a single thread to accessing to the section. This can be implemented using mutex, i.e., ''pthread_mutex_t''. * Create a global variable ''mtx'' and initialize it using by the ''pthread_mutex_init()'' function with a default attributies, e.g., pthread_mutex_t mtx; pthread_mutex_init(&mtx, NULL); * Initialize the mutex before its usage in the particular threads. Notice, threads may be executed sooner than you expect. * Protected access to the global variable ''counter'' by the critical section using ''pthead_mutex_lock()'' and ''pthread_mutex_unlock()'', e.g., void *thread1(void *v) { ... pthread_mutex_lock(&mtx); counter += 1; pthread_mutex_unlock(&mtx); ... } * Introduce a new global variable ''quit'' to indicate program termination, once the user pressed ''Enter'', e.g., bool quit = false; ... gechar(); pthread_mutex_lock(&mtx); quit = true; pthread_mutex_unlock(&mtx); ... * Use the variable ''quit'' to terminate the loop in ''thread1()'' a ''thread2()'' functions, e.g., void* thread1(void *v) { ... while (!q) { ... pthread_mutex_lock(&mtx); ... q = quit; .... ===== Use conditional variable to signal other threads ===== The current implementation of the printing loop in ''thread2'' is computationally intensive as it is executed as fast as possible using all the available cpu resources. This is not efficient and we would like to update the line only when the variable is changed. Such a synchronization mechanism can be implemented by using conditional variable, the type ''pthread_cond_t'', "suspend" use the execution of a thread by calling ''pthread_cond_wait()'' and signal the "suspended" thread by signaling it using ''pthread_cond_singnal()'' or ''pthread_cond_broadcast()''. * Define global variable ''condvar'' for the conditional variable and initialize it using default attributes, e.g., pthread_cond_t condvar; ... pthread_cond_init(&condvar, NULL); * The execution of the thread can be suspended by calling ''pthread_cond_wait()'', which release locked mutex and wait for the signal on the conditional variable. Once it is received, it will reacquire the mutex and continue with the thread execution. Update body of the ''thread2()'', e.g., void* thread2(void *d) { ... pthread_mutex_lock(&mtx); ... pthread_cond_wait(&condvar, &mtx); ... pthread_mutex_unlock(&mtx); ... * Signal the ''thread2()'' using ''pthread_cond_signal()'' once the counter is incremented, e.g., void* thread1(void *d) { ... pthread_mutex_lock(&mtx); counter += 1; ... pthread_cond_signal(&condvar); ... pthread_mutex_unlock(&mtx); ... * It may be needed to flush the ''stdout'' in the output thread by fflush(stdout); ===== Exercise ===== * Use the above building blocks to create an application with three threads. In fact, there will be four threads including the main thread for the ''main()'' function. * To read press keys without pressing ''Enter'' switch the terminal to the ''raw'' mode, e.g., using one of the technique presented in lec06. * Use a structure to encapsulate all the shared data variables * Protected access to the shared data using critical section (mutex) * Signal the threads to avoid waste of computational resources * Add a name to each thread to be identified by a string: "Input", "Output", and "Alarm" * The "Alarm" thread incrementally increases the alarm counter after the alarm period. * Consider arrays and data structures that will allow you to automate managing threads using for loops. * The program will react to the following pressed keys * 'q' - quits the program, all threads are correctly terminated * 'r' - decreases the period of increasing counter by 10 ms * 'p' - increases the period of increasing counter by 10 ms * The minimal value of the period is 10 ms * The maximal value of the period is 2000 ms * The output of the program is a single line as "\rAlarm period: %10i Alarm counter: %10i"