Řešením většiny race conditions problémů je dovolit v daném čase přístup ke
sdíleným datům pouze jednomu vláknu. Když jedno vlákno data mění, žádné
jiné by k nim nemělo přistupovat, dokud změna dat neskončí.
Mutex (MUTual EXclusion lock) je speciální zámek,
který může v daném čase zamčít pouze jedno vlákno. Když se druhé vlákno
pokusí zamčít již zamčený mutex, je blokováno, dokud první vlákno mutex
neodemče.
pthread_mutex_init(3) |
#include <pthread.h> |
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t
*mutexattr);
|
|
pthread_mutex_destroy(3) |
#include <pthread.h> |
int pthread_mutex_destroy(pthread_mutex_t *mutex); |
|
pthread_mutex_lock(3) |
#include <pthread.h> |
int pthread_mutex_lock(pthread_mutex_t *mutex); |
|
pthread_mutex_trylock(3) |
#include <pthread.h> |
int pthread_mutex_trylock(pthread_mutex_t *mutex); |
|
pthread_mutex_unlock(3) |
#include <pthread.h> |
int pthread_mutex_unlock(pthread_mutex_t *mutex); |
|
Před přístupem (čtení i zápis) ke sdíleným datům si každé vlákno uzamče mutex. Až po dokončení operace s daty mutex odemče.
Př. Nahraďte patřičné semafory z příkladu ze cvičení 'Linux - Synchronizace', část 'Semafory pro vlákna', mutexy.
Blokování vláken mutexem může způsobit deadlock, který vznikne, když vlákna čekají na něco, co se nikdy nestane. Např. když se vlákno pokusí uzamčít jím zamčený mutex, ale to záleží na typu mutexu:
pthread_mutexattr_init(3) |
#include <pthread.h> |
int pthread_mutexattr_init(pthread_mutexattr_t *attr); |
|
pthread_mutexattr_setkind_np(3) |
#include <pthread.h> |
int pthread_mutexattr_setkind_np(pthread_mutexattr_t *attr, int kind);
|
|
pthread_mutexattr_getkind_np(3) |
#include <pthread.h> |
int pthread_mutexattr_getkind_np(const pthread_mutexattr_t *attr, int *kind);
|
|
pthread_mutexattr_destroy(3) |
#include <pthread.h> |
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); |
|
Condition variable je třetí synchronizační prostředek v Linuxu, který umožňuje implementovat složitější podmínky běhu více vláken.
Například funkce vlákna, která vykonává nekonečnou smyčku. Pokud je nastavena určitá vlajka, vykoná se při iteraci nějaká činnost.
void* thread_function(void* thread_arg) { while (1) { int flag_is_set; pthread_mutex_lock(&thread_flag_mutex); flag_is_set = thread_flag; pthread_mutex_unlock(&thread_flag_mutex); if (flag_is_set) do_work (); } return NULL; } void set_thread_flag(int flag_value) { pthread_mutex_lock(&thread_flag_mutex); thread_flag = flag_value; pthread_mutex_unlock(&thread_flag_mutex); }
Tohle je správně, ale ne dostatečně. Pokud není vlajka nastavena, spotřebuje se spousta času CPU testováním vlajky a zamykáním a odemykáním mutexu. Je potřeba vlákno uspat, dokud se nestane něco, co nastaví vlajku. Condition variable implementuje podmínku, při které vlákno poběží a naopak podmínku, při které bude vlákno blokováno.
Pokud vlákno čeká na condition variable, je blokováno, dokud jiné vlákno nesignalizuje condition variable. Condition variable (na rozdíl od semaforu) není čítač, vlákno na ni musí čekat před tím, než je signalizována. Jinak je signál ztracen a vlákno čeká na další.
Zpět k příkladu, pokud není vlajka nastavena, vlákno bude čekat na condition variable. Po nastavení vlajky se condition variable signalizuje a blokované vlákno otestuje vlajku znovu (úspěšně). Je tu ale jeden problém: race condition mezi testováním vlajky a čekáním na condition variable. Např. vlákno zjistí, že vlajka není nastavena. Plánovač přepne vlákna, vlajka se nastaví a signalizuje se condition variable. Signál je ztracen, plánovač přepne vlákno zpět a to začne čekat na condition variable, které se možná nedočká. Potřebujeme zamčít mutexem vlajku i condition variable. Každá condition variable proto musí být použita spolu s mutexem:
pthread_cond_init(3) |
#include <pthread.h> |
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
|
|
pthread_cond_destroy(3) |
#include <pthread.h> |
int pthread_cond_destroy(pthread_cond_t *cond); |
|
pthread_cond_signal(3) pthread_cond_broadcast(3) |
#include <pthread.h> |
int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond);
|
|
pthread_cond_wait(3) |
#include <pthread.h> |
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
|
|
Při změně podmínky (vlajky), která je chráněna pomocí condition variable, se musí postupovat takto:
Př. Opravte předložený příklad tak, aby se použila condition variable.
Condition variable se může použít i bez podmínky, jednoduše pro blokování vlákna, dokud ho jiné "neprobudí".
OTÁZKA: Pro tohle se dá použít i semafor. Jaké jsou principiální rozdíly mezi condition variable a semaforem?
Další funkce týkající se mutexů nebo condition variables jsou: