Vlákna v C na Linuxu - 2.
3.12.2022
V minulém příspěvku o vláknech jsme si ukázali základy práce s vlákny v jazyce C v systému Linux. Nyní si ukážeme trochu více, zejména jak vlákna "ovládat zvenku", konkrétně předčasně ukonči a také jak ho "zapauzovat" a později pokračovat v provádění tam kde jsme ho zastavili.
Nejprve si pro vyzkoušení připravíme funkci vlákna, které bude v sekundovém intervalu vypisovat inkrementované číslo. Aby nám výpis do terminálu "neutíkal pryč", napíšeme si pomocné funkce pro vymazání řádky a celého terminálu a výpis textu na požadovanou pozici
static void term_erase(void) { (void)fprintf(stdout, "\033[1;1H\033[2J"); } static void term_goto(int row, int col) { (void)fprintf(stdout, "\033[%d;%df", row, col); } static void term_erase_cur_line(void) { (void)fprintf(stdout, "\033[2K"); }
A takto bude vypadat funkce vlákna, které standardně poběží cca 40 sekund, a které budeme ovládat zvenku.
static void* thread_func_counter(void* arg) { if (0 != pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL)) { perror("pthread_setcancelstate"); exit(EXIT_FAILURE); } for (size_t i = 0; i < 40; i++) { term_goto(2, 1); term_erase_cur_line(); printf("%d", (int)i); fflush(stdout); sleep(1); if (20 == i) { if (0 != pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL)) { perror("pthread_setcancelstate"); exit(EXIT_FAILURE); } } } return (void*)0; }
Ve výchozím stavu lze vlákno ukončit zvenku zavoláním funkce int pthread_cancel(pthread_t thread);. Pokud bychom chtěli z nějakého důvodu zmenožnit toto ukončení zvenku, můžeme tuto vlastnost nastavit vláknu pomocí funkce int pthread_setcancelstate(int state, int *oldstate);. Jak je vidět v předchozím výpisu, vyzkoušíme zakázat zrušení vlákna až do hodnoty počítadla 20.
Pokud jde o "zapauzování" vlákna, využijeme signály a funkci int pause(void); která přeruší vlákno, v rámci jehož funkce je zavolána a vlákno pokračuje až po přijetí nějakého signálu. Pro poslání signálu specifickému vláknu použijeme funkci int pthread_kill(pthread_t thread, int sig);.
Nadefinujeme si vlastní hodnoty signálů a obslužnou funkci signálu:
#define MY_SIGNAL_PAUSE SIGRTMIN #define MY_SIGNAL_CONTINUE SIGRTMIN+1 static void on_signal(int nsig) { term_goto(3, 1); term_erase_cur_line(); printf("signal: %d", (int)nsig); fflush(stdout); if (MY_SIGNAL_PAUSE == nsig) { term_goto(3, 1); term_erase_cur_line(); printf("signal pause"); fflush(stdout); pause(); } else if (MY_SIGNAL_CONTINUE == nsig) { term_goto(3, 1); term_erase_cur_line(); printf("signal continue"); fflush(stdout); } }
Ve funkci main si ještě nastavíme terminál tak abychom četli ze vstupu jednotlivé ovládací znaky bez nutnosti zadávat Enter a zavoláme vlastní funkcí pro vyzkoušení zrušení, zapauzování a pokračování v běhu vlákna. Vše je vidět v následujícím výpisu celého programu
/* gcc -Wall -std=c99 -pthread main.c */ #define _DEFAULT_SOURCE #include <stdlib.h> #include <stdio.h> #include <string.h> #include <pthread.h> #include <signal.h> #include <unistd.h> #include <termios.h> #include <errno.h> #define MY_SIGNAL_PAUSE SIGRTMIN #define MY_SIGNAL_CONTINUE SIGRTMIN+1 static struct termios _termios; pthread_t _thread_counter; static void term_erase(void) { (void)fprintf(stdout, "\033[1;1H\033[2J"); } static void term_goto(int row, int col) { (void)fprintf(stdout, "\033[%d;%df", row, col); } static void term_erase_cur_line(void) { (void)fprintf(stdout, "\033[2K"); } static void on_signal(int nsig) { term_goto(3, 1); term_erase_cur_line(); printf("signal: %d", (int)nsig); fflush(stdout); if (MY_SIGNAL_PAUSE == nsig) { term_goto(3, 1); term_erase_cur_line(); printf("signal pause"); fflush(stdout); pause(); } else if (MY_SIGNAL_CONTINUE == nsig) { term_goto(3, 1); term_erase_cur_line(); printf("signal continue"); fflush(stdout); } } static void* thread_func_counter(void* arg) { if (0 != pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL)) { perror("pthread_setcancelstate"); exit(EXIT_FAILURE); } for (size_t i = 0; i < 40; i++) { term_goto(2, 1); term_erase_cur_line(); printf("%d", (int)i); fflush(stdout); sleep(1); if (20 == i) { if (0 != pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL)) { perror("pthread_setcancelstate"); exit(EXIT_FAILURE); } } } return (void*)0; } static void test_pause(void) { int iret = pthread_create(&_thread_counter, NULL, &thread_func_counter, NULL); if (0 != iret) exit(EXIT_FAILURE); term_erase(); int ch; while ((ch = getchar()) != 'q') { if ('c' == ch) { pthread_cancel(_thread_counter); } else if ('p' == ch) { pthread_kill(_thread_counter, MY_SIGNAL_PAUSE); } else if ('r' == ch) { pthread_kill(_thread_counter, MY_SIGNAL_CONTINUE); } } } int main(int argc, char** argv) { struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sa.sa_handler = on_signal; sigaction(MY_SIGNAL_CONTINUE, &sa, NULL); sigaction(MY_SIGNAL_PAUSE, &sa, NULL); /* nastaveni terminalu */ tcgetattr(STDIN_FILENO, &_termios); struct termios ti; if (tcgetattr(STDIN_FILENO, &ti) == -1) return errno; ti.c_lflag &= ~ECHO; ti.c_lflag &= ~ICANON; if (0 != tcsetattr(STDIN_FILENO, TCSAFLUSH, &ti)) return errno; cfmakeraw(&ti); test_pause(); /* reset terminalu */ tcsetattr(STDIN_FILENO, TCSAFLUSH, &_termios); term_erase(); printf("konec\n"); return EXIT_SUCCESS; }
Program sestavíme pomocí GCC například příkazem:
$ gcc -Wall -std=c99 -pthread main.c
Funkčnost si každý může vyzkoušet spuštěním vytvořeného souboru a.out a ovládání znaky c, p, r, q jak je zřejmé ze zdrojového kódu.
Nové články
Souborové operace na Linuxu v jazyce C
Využívejte Midnight Commander naplno
Procesor a jádra, běh na určeném jádru v C.
picom - kompozitor pro odlehčená Linuxová prostředí
Kontakt
739 219 991
live:radekchalupa_1