Bezpečnost závitu - Thread safety

Bezpečnost podprocesů je koncept počítačového programování použitelný pro vícevláknový kód. Kód bezpečný pro vlákna manipuluje pouze se sdílenými datovými strukturami způsobem, který zajišťuje, že se všechna vlákna chovají správně a splňují specifikace jejich návrhu bez nechtěné interakce. Existují různé strategie pro vytváření datových struktur bezpečných pro vlákna.

Program může spouštět kód v několika podprocesech současně ve sdíleném adresním prostoru, kde každé z těchto podprocesů má přístup k prakticky celé paměti každého dalšího podprocesu. Bezpečnost podprocesů je vlastnost, která umožňuje spuštění kódu v prostředích s více podprocesy obnovením některých korespondencí mezi skutečným tokem řízení a textem programu pomocí synchronizace .

Úrovně bezpečnosti nití

Softwarové knihovny mohou poskytovat určité záruky bezpečnosti vláken. Například je možné zaručit, že souběžná čtení budou bezpečná pro vlákna, ale souběžné zápisy nemusí být. Zda je program využívající takovou knihovnu bezpečný pro vlákna, závisí na tom, zda knihovnu používá způsobem, který je v souladu s těmito zárukami.

Různí prodejci používají mírně odlišnou terminologii pro bezpečnost vláken:

  • Bezpečné pro vlákna : Při přístupu k více vláknům současně je zaručeno, že implementace bude bez podmínek závodu .
  • Podmíněně bezpečné : Různá vlákna mohou přistupovat k různým objektům současně a přístup ke sdíleným datům je chráněn před rasovými podmínkami.
  • Není bezpečné pro vlákna : K datovým strukturám by neměly být současně přistupovány různá vlákna.

Záruky bezpečnosti podprocesů obvykle také zahrnují konstrukční kroky k zabránění nebo omezení rizika různých forem zablokování a také optimalizace k maximalizaci souběžného výkonu. Nelze však vždy poskytnout záruky bez zablokování, protože zablokování může být způsobeno zpětnými voláními a porušením architektonického vrstvení nezávisle na samotné knihovně.

Implementační přístupy

Níže probereme dvě třídy přístupů, jak se vyhnout rasovým podmínkám k dosažení bezpečnosti vláken.

První třída přístupů se zaměřuje na vyhýbání se sdílenému stavu a zahrnuje:

Znovuzavření
Psaní kódu takovým způsobem, že může být částečně proveden vláknem, spuštěn stejným vláknem nebo současně spuštěn jiným vláknem a stále správně dokončit původní provedení. To vyžaduje uložení informací o stavu do proměnných lokálních pro každé spuštění, obvykle do zásobníku, namísto do statických nebo globálních proměnných nebo do jiného nelokálního stavu. Všechny nelokální státy musí být přístupné prostřednictvím atomových operací a datové struktury musí být také znovu zadány.
Místní úložiště vláken
Proměnné jsou lokalizovány, takže každé vlákno má svou vlastní soukromou kopii. Tyto proměnné si zachovávají své hodnoty přes podprogram a další hranice kódu a jsou bezpečné pro vlákna, protože jsou lokální pro každé vlákno, i když kód, který k nim přistupuje, může být spuštěn současně jiným vláknem.
Neměnné objekty
Stav objektu nelze po konstrukci změnit. To znamená, že jsou sdílena pouze data jen pro čtení, a že je dosaženo vlastní bezpečnosti podprocesů. Proměnlivé (nekonstantní) operace lze potom implementovat takovým způsobem, že místo úpravy stávajících vytvoří nové objekty. Tento přístup je charakteristický pro funkční programování a je také používán implementacemi řetězců v Javě, C # a Pythonu. (Viz Neměnný objekt .)

Druhá třída přístupů souvisí se synchronizací a používá se v situacích, kdy nelze zabránit sdílenému stavu:

Vzájemné vyloučení
Přístup ke sdíleným datům je serializován pomocí mechanismů, které zajišťují, že pouze jedno vlákno čte nebo zapisuje na sdílená data kdykoli. Začlenění vzájemného vyloučení je třeba dobře promyslet, protože nesprávné použití může vést k vedlejším účinkům, jako jsou zablokování , blokování živých vstupů a hladovění zdrojů .
Atomové operace
Ke sdíleným datům se přistupuje pomocí atomových operací, které nelze přerušit jinými vlákny. To obvykle vyžaduje použití speciálních instrukcí jazyka stroje , které mohou být k dispozici v běhové knihovně . Jelikož jsou operace atomické, sdílená data se vždy udržují v platném stavu, bez ohledu na to, jak k nim přistupují ostatní vlákna. Atomové operace tvoří základ mnoha mechanismů zamykání vláken a používají se k implementaci primitiv vzájemného vyloučení.

Příklady

V následujícím kus Java kódu Java klíčové slovo synchronizované činí způsob thread-safe:

class Counter {
    private int i = 0;

    public synchronized void inc() {
        i++;
    }
}

V programovacím jazyce C má každé vlákno svůj vlastní zásobník. Nicméně, statická proměnná není udržován v zásobníku; všechna vlákna k němu sdílejí současný přístup. Pokud se při spuštění stejné funkce překrývá více vláken, je možné, že statická proměnná může být změněna jedním vláknem, zatímco jiné je uprostřed kontroly. Tato obtížně diagnostikovaná logická chyba , která se může většinu času zkompilovat a správně spustit, se nazývá závodní podmínka . Jeden obyčejný způsob, jak se vyhnout, je použít jiný sdílené proměnné, jako „zámek“ nebo „mutexu“ (od mut UAL ex clusion).

V následující části kódu C je funkce bezpečná pro vlákna, ale ne znovu zadaná:

# include <pthread.h>

int increment_counter ()
{
  static int counter = 0;
  static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

  // only allow one thread to increment at a time
  pthread_mutex_lock(&mutex);

  ++counter;

  // store value before any other threads increment it further
  int result = counter;

  pthread_mutex_unlock(&mutex);

  return result;
}

Ve výše uvedeném increment_counter lze bez problémů volat různými vlákny, protože mutex se používá k synchronizaci veškerého přístupu ke sdílené counter proměnné. Ale pokud je funkce použita v reentrantním obslužném programu přerušení a dojde k druhému přerušení, zatímco je mutex uzamčen, druhá rutina bude navždy viset. Protože servis přerušení může deaktivovat další přerušení, mohl by utrpět celý systém.

Stejnou funkci lze implementovat tak, aby byla bezpečná i reentrantní pomocí bezzámkové atomiky v C ++ 11 :

# include <atomic>

int increment_counter ()
{
  static std::atomic<int> counter(0);

  // increment is guaranteed to be done atomically
  int result = ++counter;

  return result;
}

Viz také

Reference

externí odkazy