Кога да се използва летливата ключова дума в C #

Техниките за оптимизация, използвани от компилатора JIT (точно навреме) в Common Language Runtime, могат да доведат до непредсказуеми резултати, когато вашата .Net програма се опитва да извършва енергонезависими четения на данни в многонишков сценарий. В тази статия ще разгледаме разликите между летливия и енергонезависимия достъп до паметта, ролята на ключовата дума с променлива в C # и как трябва да се използва летливата ключова дума.

Ще дам няколко примера за код в C #, за да илюстрирам концепциите. За да разберем как работи летливата ключова дума, първо трябва да разберем как работи стратегията за оптимизация на JIT компилатора в .Net.

Разбиране на оптимизациите на JIT компилатора

Трябва да се отбележи, че JIT компилаторът, като част от стратегията за оптимизация, ще промени реда на четене и запис по начин, който не променя значението и евентуалния изход на програмата. Това е илюстрирано в кодовия фрагмент, даден по-долу.

x = 0;

x = 1;

Горният фрагмент от код може да бъде променен на следния - като същевременно се запази оригиналната семантика на програмата.

x = 1;

Компилаторът JIT може също да приложи концепция, наречена „постоянно разпространение“, за да оптимизира следния код.

x = 1;

y = x;

Горният кодов фрагмент може да бъде променен на следния - отново, като същевременно се запази оригиналната семантика на програмата.

x = 1;

y = 1;

Летлив срещу нелетлив достъп до паметта

Моделът на паметта на съвременните системи е доста сложен. Разполагате с процесорни регистри, различни нива на кеш памет и основна памет, споделена от множество процесори. Когато вашата програма се изпълни, процесорът може да кешира данните и след това да осъществи достъп до тези данни от кеша, когато това е поискано от изпълняващата нишка. Актуализациите и четенията на тези данни може да се изпълняват срещу кешираната версия на данните, докато основната памет се актуализира по-късно. Този модел на използване на паметта има последици за многонишковите приложения. 

Когато една нишка взаимодейства с данните в кеша и втората нишка се опитва да чете същите данни едновременно, втората нишка може да прочете остаряла версия на данните от основната памет. Това е така, защото когато стойността на енергонезависим обект се актуализира, промяната се извършва в кеша на изпълняващата нишка, а не в основната памет. Когато обаче се актуализира стойността на летлив обект, не само се прави промяната в кеша на изпълняващата нишка, но този кеш се изтрива в основната памет. И когато се прочете стойността на летлив обект, нишката опреснява кеша си и чете актуализираната стойност.

Използване на летливата ключова дума в C #

Волатилната ключова дума в C # се използва, за да информира JIT компилатора, че стойността на променливата никога не трябва да се кешира, тъй като тя може да бъде променена от операционната система, хардуера или едновременно изпълняваща се нишка. По този начин компилаторът избягва да използва всякакви оптимизации на променливата, които могат да доведат до конфликти на данни, т.е. до различни нишки, осъществяващи достъп до различни стойности на променливата.

Когато маркирате обект или променлива като нестабилни, тя става кандидат за нестабилни четения и записи. Трябва да се отбележи, че в C # всички записи в паметта са нестабилни, независимо дали записвате данни в летлив или енергонезависим обект. Неяснотата обаче се случва, когато четете данни. Когато четете данни, които не са летливи, изпълняващата нишка може или не винаги може да получи най-новата стойност. Ако обектът е променлив, нишката винаги получава най-актуалната стойност.

Можете да декларирате променлива като летлива, като я предшествате с volatileключовата дума. Следният кодов фрагмент илюстрира това.

клас Програма

    {

        публична променлива int i;

        static void Main (низ [] аргументи)

        {

            // Напишете кода си тук

        }

    }

Можете да използвате volatileключовата дума с всякакви типове справка, указател и преброяване. Можете също да използвате променливия модификатор с байтови, къси, int, char, float и bool типове. Трябва да се отбележи, че локалните променливи не могат да бъдат декларирани като нестабилни. Когато посочите обект от референтен тип като променлив, само показалецът (32-битово цяло число, което сочи към местоположението в паметта, където всъщност се съхранява обектът) е изменчив, а не стойността на екземпляра. Също така двойна променлива не може да бъде изменчива, тъй като е с размер 64 бита, по-голям от размера на думата в x86 системи. Ако трябва да направите двойна променлива volatile, трябва да я увиете вътре в клас. Можете да направите това лесно, като създадете клас на обвивка, както е показано в кодовия фрагмент по-долу.

публична класа VolatileDoubleDemo

{

    private volatile WrappedVolatileDouble volatileData;

}

публичен клас WrappedVolatileDouble

{

    публични двойни данни {get; комплект; }

Обърнете внимание обаче на ограничението на горния пример за код. Въпреки че ще имате най-новата стойност на volatileDataреферентния указател, не ви се гарантира най-новата стойност на Dataсвойството. Работата около това е да се направи WrappedVolatileDoubleтипа неизменим.

Въпреки че променливата ключова дума може да ви помогне в безопасността на нишките в определени ситуации, тя не е решение на всичките ви проблеми с едновременността на нишките. Трябва да знаете, че маркирането на променлива или обект като нестабилна не означава, че не е необходимо да използвате ключовата дума lock. Волатилната ключова дума не е заместител на ключовата дума lock. Той е там, за да ви помогне да избегнете конфликти на данни, когато имате няколко нишки, които се опитват да получат достъп до едни и същи данни.