A vezérlési szerkezetek kapcsán emlÃtésre került, hogy a leÃrt kódunkat a fordÃtó átrendezheti és optimalizálhatja, egészen addig, amÃg a kimenet azonos eredményt produkál, mint amit leÃrtunk.
Ez egy szálon futó alkalmazások esetén nem is okoz problémát, de ha több szálon fog futni az alkalmazásunk, akkor okozhat, mivel elÅ‘fordulhat az az eset, hogy az egyik szál látszólag nem használja a változót, de egy másik módosÃtja és az optimalizációk miatt teljesen másként fog viselkedni a program.
Itt jön képbe a volatile kulcsszó, amit osztály szintű változókra tudunk alkalmazni. Ha egy változót ezzel jelölünk meg, akkor az arra utasÃtja a fordÃtót, hogy azokat a kifejezéseket, amiben szerepel a változó, ne kezdje el optimalizálni.
Nézzünk erre egy példát:
using System.Diagnostics;
class Program
{
// a volatile tiltja a compiler optimalizaciot
private static volatile bool volatileStop = false;
private static bool nonVolatileStop = false;
static void Main()
{
Thread threadVolatile = new Thread(VolatileWorker);
threadVolatile.Start();
volatileStop = true;
Thread.Sleep(100);
var sw = new Stopwatch();
sw.Start();
threadVolatile.Join();
sw.Stop();
Console.WriteLine(sw.Elapsed.TotalMicroseconds);
sw.Reset();
volatileStop = false;
Thread threadNonVolatile = new Thread(NonVolatileWorker);
threadNonVolatile.Start();
nonVolatileStop = true;
Thread.Sleep(100);
sw.Start();
threadNonVolatile.Join();
sw.Stop();
Console.WriteLine(sw.Elapsed.TotalMicroseconds);
}
static void VolatileWorker()
{
while (!volatileStop)
{
// ugyan üres ciklus, de mivel a volatileStop
// volatile, ezért a fordÃtó nem szedi ki
// ezt az üres ciklust
}
Console.WriteLine("Worker with volatile: Stopped.");
}
static void NonVolatileWorker()
{
while (!nonVolatileStop)
{
// mivel nem volatile a nonVolatileStop
// ezért ezt az üres ciklust kioptimalizálja
// a fordÃtó
}
Console.WriteLine("Worker without volatile: Stopped.");
}
}
A program egy lehetséges kimenete:
Worker with volatile: Stopped.
38,7
Worker without volatile: Stopped.
7,8
A fenti példában két majdnem egyforma szálat indÃtunk. A különbség az, hogy az egyik szál esetén a ciklusban a leállÃtási feltétel volatile kulcsszóval jelezve van, a másikban pedig nincs. EbbÅ‘l adódóan a NonVolatileWorker kódjában az üres ciklust a fordÃtó eltávolÃtja, mivel az ezt követÅ‘ kiÃrásra nincs hatása.
A VolatileWorker kódjában lévÅ‘ ciklust azonban nem fogja eltávolÃtani, mivel a volatile kulcsszóval azt jeleztük a fordÃtónak, hogy ennek a változónak az értékét egy másik szál is módosÃthatja.
Ez meg is látszik a futási eredményeken. Mind a két esetben a szál Join eredményére várunk és egy Stopwatch segÃtségével mérjük az idÅ‘t. Mivel nagyon rövid idÅ‘n belül történik minden, ezért itt mikroszekundum (10-6 s) nagyságrendrÅ‘l beszélünk és ennek megfelelÅ‘en történik a kiÃratás.
A számokon ez meglátszik. A volatile módosÃtót tartalmazó ciklus majdnem 5x annyi ideig fut, mint a nem volatile változat. Ez elsÅ‘ olvasatban optimalizáció szempontjából rossznak tűnik, de valójában ez volt a célunk: jelezni a fordÃtónak, hogy ne távolÃtson el látszólag értelmetlen kódrészleteket.
A volatile kulcsszó hasznos tud lenni többszálú környezetben, de amint láthattuk, a teljesÃtményre negatÃv hatása van, mert a fordÃtó azon kifejezéseket, amiben ilyen változó szerepel, nem fogja optimalizálni. Éppen ezért csak nagyon indokolt helyzetekben használjuk.