Hlavní navigace

Programování pod Linuxem pro všechny (18)

28. 5. 2004
Doba čtení: 5 minut

Sdílet

Dnes si povíme o dalším mechanismu k synchronizaci vláken, bude se jednat o semafory. A úplně dokončíme povídání o mutexech.

Neblokující testy mutexu

Někdy je užitečné testovat, zda je mutex uzamknut, aniž bychom jej chtěli zablokovat. Vlákno například potřebuje uzamknout mutex, ale má něco jiného na práci než blokovat ostatní vlákna, je-li mutex již zablokován.

Použití funkce pthread_mutex_loc­k() je nevhodné, protože ta se vrátí až po odemknutí mutexu.

Pro tento účel je v Linuxu poskytována funkcepthread_mu­tex_trylock(). Když tuto funkci zavoláte pro odemknutý mutex, bude uzamknut stejně jako po volání funkce pthread_mutex_loc­k() a funkce pthread_mutex_try­lock() vrátí nulu. Když je však mutex již uzamčen jiným vláknem, funkce pthread_mutex_try­lock() volající vlákno nezablokuje. Místo toho se vrátí s návratovým kódem EBUSY. Uzamknutí mutexu jiným vláknem se tím neovlivní. Mutex se můžete pokusit uzamknout později.

Semafory pro vlákna

Mutexy fungují, pokud jsou všechny úlohy předem zařazeny do frony nebo pokud je nová úloha zařazena do fronty tak rychle, jak rychle vlákna jednotlivé úlohy zpracovávají. Pokud však budou vlákna pracovat příliš rychle, fronta úloh se vyprázdní a vlákna ukončí svoji činnost. Když se potom do fronty zařadí nová úloha, nebude existovat žádné vlákno, které by ji zpracovalo. Bylo by proto lepší mít při vyprazdňování fronty mechanismus pro zablokování vlákna až do doby, než se začne zpracovávat další úloha.

Tímto prostředkem jsou semafory. Semafor je čítač, který může být použit k synchronizaci činnosti více vláken. Podobně jako v případě mutexů je garantováno, že kontrola a modifikace semaforu se provádí bezpečně a že nemůže dojít k souběhu.

Každý semafor je celočíselnou nezápornou hodnotou. Semafory podporují dvě základní operace:

  • Operace wait sníží hodnotu semaforu o jedničku. Pokud je hodnota semaforu nula, operace se zablokuje, dokud nenabude semafor opět kladné hodnoty (v důsledku činnosti jiného vlákna). Jakmile je hodnota semaforu kladná, sníží se o jedničku a operace wait se vrátí.
  • Operace post zvýší hodnotu semaforu o jedničku. Jestliže byla předtím hodnota semaforu nula a ostatní vlákna byla zablokovaná operací wait pro tento semafor, jedno ze zablokovaných vláken se odblokuje a ukončí se jeho operace wait (čímž se hodnota semaforu nastaví na nulu).

Operační systém Linux poskytuje dvě mírně odlišné implementace semaforů. Právě popisovaná odpovídá standardu POSIX. Je vhodné ji používat pro komunikaci mezi vlákny. Druhou imlementaci, používanou ke komunikace mezi procesy, popíšu v některém z příštích dílů. Pokud budete používat semafory ve svých programech, vkládejte do zdrojového kódu hlavičkový souborsemaphore.h.

Semafor je reprezentován proměnnou typu sem_t. Před jejím použitím ji musíte inicializovat pomocí funkce sem_init(). Jako argument se funkci předává ukazatel na proměnnou sem_t. Druhým argumentem je nula (nenulová hodnota by znamenala, že semafor může být sdílen mezi procesy, což není pro tento typ semaforů v Linuxu podporováno) a třetím je počáteční hodnota semaforu. Pokud již semafor nepotřebujete, dealokujte jej pomocí funkce sem_destroy().

Pro čekání na semafor požijte funkci sem_wait(). Pro operaci post použijte funkci sem_post(). Pro neblokující vlákna lze použít funkci sem_trywait(). Tato funkce je podobná funkci pthread_mutex_try­lock() – jestliže má být operace wait blokována, protože hodnota semaforu je nula, vrátí se funkce okamžitě s návratovou hodnotouEAGAIN a volající vlákno se nazablokuje.

Dále je také možné použít funkci pro získání aktuální hodnoty semaforu, sem_getvalue(). Ta uloží hodnotu semaforu do celočíselné proměnné, na kterou ukazuje druhý argument. Neměli byste však hodnotu semaforu získanou pomocí této funkce použít k rozhodnutí, zda zvolit operaci post, nebo wait. Mohli byste tak přivodit stav souběhu. Jiné vlákno by totiž během volání funkce sem_getvalue() mohlo změnit hodnotu semaforu. Proto raději používejte atomické operace post a wait.

Zde je výpis našeho příkladu s frontou úloh. Použijeme semafor k načítání počtu úloh čekajících ve frontě. Funkce enqueue_job přidává do fronty novou úlohu.

#include <malloc.h>
#include <pthread.h>
#include <semaphore.h>

struct job {
  struct job next;
};

struct job *job_queue;

pthread_mutex_t job_queue_mutex = PTHREAD_MUTEX_INITIALIZER;

/* Semafor k citani poctu ukolu ve fronte */
sem_t job_queue_count;

/* Provede pocatecni inicializaci fronty uloh */

void initialize_job_queue()
{
  /* Fronta je inicializovana jako prazdna */
  job_queue = NULL;
  /* Initializace semaforu s poctem ukolu ve
     fronte. Inicializacni hodnotou je nula */
  sem_init(&job_queue_count, 0, 0);
}

/* Zpracovani serazenych ukolu do vyprazdneni fronty */

void *thread_function(void *arg)
{
  while(1) {
    /* Cekani na semafor. Pokud je hodnota
    kladna, znamena to, ze fronta neni prazdna.
    Jestlize je fronta prazdna, semafor blokuje
    vlakna, dokud neni pridan dalsi ukol do
    fronty */
    sem_wait(&job_queue_count);
    pthread_mutex_lock(&job_queue_mutex)
    next_job = job_queue;
    job_queue = job_queue->next;
    pthread_mutex_unlock(&job_queue_mutex);

    process_job(next_job);
    free(next_job);
  }
  return(NULL);
}

/* Prida novy ukol do fronty uloh */

void enqueue_job(/* Data specificka pro ukoly...*/)
{
  struct job *new_job;

  /* Alokace noveho objektu pro ukol */
  new_job = (struct job *) malloc(sizeof(struct job));
  /* Nastaveni dalsich oblasti struktury pro ukol...*/

  /* Uzamknuti mutexu fronty uloh pred jejim zpristupnenim */
  pthread_mutex_lock(&job_queue_mutex);
  /* Umisteni noveho ukolu na zacatek fronty */
  new_job->next = job_queue;
  job_queue = new_job;

  /* Zvyseni semaforu o 1 */
  sem_post(&job_queue_count);

  /* Odemknuti mutexu fronty */
  pthread_mutex_unlock(&job_queue_mutex)
} 

Před vyjmutím úlohy z fronty čeká každé vlákno na semafor. Je-li hodnota semaforu nulová (což znamená, že fronta je prázdná), vlákno se jednoduše zablokuje, dokud se hodnota semaforu opět nestane kladnou (což znamená, že do fronty byla přidána úloha).

CS24_early

Funkce enqueue_job() přidává do fronty úlohu. Podobně jako funkce thread_function() potřebuje před modifikací fronty uzamknout mutex. Po přidání úlohy do fronty zvýší pomocí operace post hodnotu semaforu a pro ostatní vlákna je to signál, že ve frontě je opět nějaká úloha. Program uvedený výše nikdy neskončí. Není-li ve frontě žádná úloha, zablokují se všechna vlákna funkcí sem_wait().

Dnešní díl byl o něco delší, ale stihli jsme probrat téměř vše o semaforech. V príštím dílu vám povím něco o třetím prostředku k synchronizaci činnosti vláken – o podmíněných proměnných.

Byl pro vás článek přínosný?

Autor článku