Mutex
Řešení problému souběhu při zpracování úloh, jak jsem jej uvedl v minulém dílu, spočívá v tom, že se dovolí pouze jednomu vláknu v daný okamžik zpřístupnit frontu úloh. Jakmile začne vlákno prohledávat frontu, žádné jiné vlákno k ní nesmí mít přístup, dokud se naše vlákno nerozhodne, zda úlohu zpracovat, a když ano, dokud ji nezpracuje.
Implentace takového řešení vyžaduje podporu operačního systému. Proto Linux poskytuje tzv. mutexy. Mutex je speciální typ zámku, který v daný okamžik může být uzavřen pouze jediným vláknem. Pokud jedno vlákno uzamkne mutex a pak se druhé vlákno pokusí také mutex uzamknout, zablokuje se. Teprve až první vlákno odemkne mutex, druhé se odblokuje a může dále pokračovat v činnosti. Systémem je garantováno, že se problém souběhu nemůže vyskytnout mezi vlákny pokoušejícími se uzamknout mutex. Vždy pouze jedno vlákno dostane možnost mutex uzamknout a ostatní budou zablokována.
K vytvoření mutexu je určena proměnná typu pthread_mutex_t. Po deklaraci takové proměnné musíte předat její ukazatel funkcipthread_mutex_init(). Druhým argumentem funkce pthread_mutex_init() je ukazatel na atributy mutexu, což je objekt specifikující vlastnosti mutexu. Pokud je ukazatel nulový, předpokládají se implicitní atributy. Proměnná mutexu by měla být inicializována pouze jednou.
Příklad vytvoření mutexu:
pthread_mutex_t mutex; pthread_mutex_init(&mutex, NULL);
Jiný způsob, jak vytvořit mutex s implicitními atributy, spočívá v inicializaci speciální hodnotou PTHREAD_MUTEX_INITIALIZER. Pak není nutné žádné dodatečné volání funkce pthread_mutex_init(). Tento způsob je vhodný pro globální proměnné:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
Vlákno se může pokusit uzamknout mutex voláním funkce pthread_mutex_lock(). Pokud byl mutex odemknut, uzamkne se a funkce se vrátí okamžitě. Pokud ovšem byl mutex uzamknut jiným vláknem, funkce zablokuje další realizaci vlákna a vrátí se, až bude mutex odemknut. Může být také zablokováno více vláken najednou, ale nelze určit, které se po odemknutí mutexu odblokuje. Toto vlákno může mutex opět uzamknout. Ostatní vlákna zůstanou zablokována.
Voláním funkce pthread_mutex_unlock() se mutex odemkne. Tato funkce by měla být vždy volána z téhož vlákna, které mutex uzamklo.
Následující příklad ukazuje využití mutexu s frontou úloh. Jedná se o přepis zdrojového kódu z minulého dílu:
#include <malloc.h>
#include <pthread.h>
struct job{
struct job *next;
};
struct job *job_queue;
/* Vytvoreni mutexu s implicitnimi atributy */
pthread_mutex_t job_queue_mutex = PTHREAD_MUTEX_INITIALIZER;
void *thread_function(void *arg)
{
while(1) {
struct job *next_job;
/* Uzamknuti mutexu pred zpracovanim fronty */
pthread_mutex_lock(&job_queue_mutex);
if (job_queue == NULL)
next_job = NULL;
else {
next_job = job_queue;
job_queue = job_queue->next;
}
/* Odemceni mutexu */
pthread_mutex_unlock(&job_queue_mutex);
if (next_job == NULL)
break;
process_job(next_job);
free(next_job);
}
return(NULL);
}
Veškeré přístupy k proměnné job_queue (sdílenému ukazateli) probíhají mezi voláním funkcí pthread_mutex_lock() a pthread_mutex_unlock(). Objekt job, uložený v proměnné next_job, je zpřístupňován vně této oblasti pouze tehdy, až je tento objekt odstraněn z fronty. Proto je nepřístupný ostatním vláknům.
Všimněte si, že pokud je fronta prázdná (hodnota proměnné job_queue je nulová), neukončuje se cyklus okamžitě, protože by mohl mutex zůstat permanentně uzamknut a k frontě by už nezískalo přístup žádné jiné vlákno.
To by bylo pro dnešek vše, příště se podíváme na problém zvaný uváznutí mutexu a možná, pokud vyzbyde místo, si povíme něco o semaforech pro vlákna.