Linux - Synchronizace (Synchronization)

Mutexy (Mutexes)

Ř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);
  • inicializuje mutex na ukazateli mutex, nastaví atributy podle mutexattr, pokud je NULL, atributy jsou defaultní, viz pthread_mutexattr_init
  • proměnná typu pthread_mutex_t může být inicializována také staticky pomocí konstanty PTHREAD_MUTEX_INITIALIZER, např. pro globální a statické proměnné
  • vždy vrací 0

pthread_mutex_destroy(3)
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • zruší mutex, který musí být odemčený, uvolní prostředky systému (v Linuxu nejsou mutexům přiděleny žádné prostředky, tzn. funkce se nemusí používat pro zrušení mutexu)
  • vrací 0, při chybě nenulové číslo

pthread_mutex_lock(3)
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
  • zamče mutex, pokud je již zamčený, blokuje vlákno, dokud není odemčen
  • vrací 0, při chybě nenulové číslo

pthread_mutex_trylock(3)
#include <pthread.h>
int pthread_mutex_trylock(pthread_mutex_t *mutex);
  • zamče mutex, pokud je již zamčený, vrací chybu
  • vrací 0, při chybě nenulové číslo

pthread_mutex_unlock(3)
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • odemče mutex a jedno (náhodné) z blokovaných vláken je odblokováno
  • mutex musí být uzamčený stejným vláknem, ze kterého je volána tato funkce
  • vrací 0, při chybě nenulové číslo

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);
  • inicializuje atributy mutexu attr a naplní je defaultními hodnotami
  • vrací vždy 0

pthread_mutexattr_setkind_np(3)
#include <pthread.h>
int pthread_mutexattr_setkind_np(pthread_mutexattr_t *attr, int kind);
  • nastaví typ mutexu v atributech attr na hodnotu kind
  • kind může být PTHREAD_MUTEX_FAST_NP, PTHREAD_MUTEX_RECURSIVE_NP nebo PTHREAD_MUTEX_ERRORCHECK_NP, NP znamená nonportable, tj. nepřenosné rozšíření POSIX standardu
  • vrací 0, při chybě nenulové číslo

pthread_mutexattr_getkind_np(3)
#include <pthread.h>
int pthread_mutexattr_getkind_np(const pthread_mutexattr_t *attr, int *kind);
  • vrátí typ mutexu v atributech attr na adresu kind
  • vrací vždy 0

pthread_mutexattr_destroy(3)
#include <pthread.h>
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
  • zruší atributy mutexu, před novým použitím se musí znovu inicializovat
  • vrací vždy 0

Condition variables

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);
  • inicializuje condition variable na ukazateli cond, atributy cond_attr nejsou v Linuxu implementovány, tzn. zadává se NULL
  • proměnná typu pthread_cond_t může být inicializována také staticky pomocí konstanty PTHREAD_COND_INITIALIZER, např. pro globální a statické proměnné
  • vždy 0, při chybě nenulové číslo

pthread_cond_destroy(3)
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
  • zruší condition variable, na kterou nesmí žádné vlákno čekat, uvolní prostředky systému (v Linuxu nejsou mutexům přiděleny žádné prostředky, tzn. funkce se nemusí používat pro zrušení condition variable)
  • vrací 0, při chybě nenulové číslo

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);
  • signalizuje condition variable, odblokuje nejvýše jedno (náhodné) blokované vlákno nebo všechna blokovaná vlákna (pthread_cond_broadcast)
  • vrací 0, při chybě nenulové číslo

pthread_cond_wait(3)
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
  • atomicky odemče mutex a čeká na condition variable, mutex musí být zamčený
  • při signalizování znovu zamče mutex ještě před odblokováním vlákna
  • je to bod zrušení vlákna, před zrušením zamče mutex
  • vrací 0, při chybě nenulové číslo

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:



Jan Outrata
outrata@phoenix.inf.upol.cz