Да кажем, че има клас, който съдържа публично int броячно поле, достъпно от няколко нишки, и това число само ще се увеличава или намалява.
При добавяне на това поле, коя от следните схеми трябва да се използват и защо?
- lock(this.locker) this.counter++;
- Interlocked.Increment (реф. this.counter);
- Променете модификатора за достъп на counter на публичен волатилен
Най-лошото (нито един от тях не работи)
Променете модификатора за достъп на counter на публичен волатилен
Този подход всъщност изобщо не е сигурен, а същността при волатилността е, че множество нишки, работещи на няколко процесора, буферират данните и пренареждат изпълнените инструкции.
Ако е неволатилен, когато CPU A се увеличава с определена стойност, CPU B трябва да изчака известно време, за да види увеличената стойност, което може да доведе до проблеми.
Ако е волатилен, това гарантира, че и двата процесора виждат една и съща стойност едновременно. Но това не избягва кръстосаните операции по четене и запис.
Добавянето на стойност към променлива всъщност изисква три стъпки
1. Четене, 2. Добави 3. Пиши
Да предположим, че нишка A чете стойността на брояча като 1 и не е готова да увеличи, тогава нишка B също чете стойността на брояча като 1, и след това и двете нишки започват да извършват инкрементални и записни операции. Стойността на последния брояч е 2. Това не е вярно, и двете нишки са извършили операция за увеличаване и правилният резултат трябва да е 3. Затова етикетирането като нестабилно е просто опасно.
По-добре е
lock(this.locker) this.counter++;
Така е безопасно (разбира се, не забравяйте да заключвате навсякъде, където искате да имате достъп до този брояч). Това предотвратява изпълнението на заключения код от която и да е друга нишка. Освен това предотвратява проблема с последователността на инструкциите при многопроцесорни процесори, споменат по-горе. Проблемът е, че заключването е бавно в производителността и ако го използваш на други несвързани места, може да блокира другите ти нишки.
Най-добро
Interlocked.Increment (реф. this.counter);
Това е безопасно и много ефективно. Извършва четене, увеличаване, записване на три операции в един атом, без да бъде прекъснат по средата. Тъй като това не влияе на другите кодове, не е нужно да помниш заключванията другаде. И е много бърз (както казва MSDN, при днешните процесори често е просто инструкция).
Но не съм напълно сигурен дали може да реши проблема с подреждането на инструкциите на процесора, или трябва да се използва заедно с волатилния и този инкремент.
Допълнение: Кои проблеми решава волатилният предмет добре?
Тъй като волатилното не може да предотврати мултитрединга, какво може да направи? Добър пример е, че имате две нишки, едната винаги записва към променлива, да кажем, че тази променлива е queneLength, а другата винаги чете данни от тази променлива. Ако queueLenght не е волатилен, нишка A може да чете 5 пъти, но нишка B може да види забавени данни или дори данни в грешен ред. Едно решение е да използваш lock, но в този случай можеш да използваш и волатил. Това гарантира, че нишка Б винаги вижда най-новите данни, написани от нишка А, но тази логика работи само ако не ги четете при писане и ако не ги записвате при четене. След като няколко нишки искат да извършват операции по четене-модифициране-записване, трябва да използвате Interlocked или lock.
|