Концепція блокувань читання-запису проста: це дозволяє одночасно отримувати блокування читання кільком потокам, але лише одному потоку дозволено отримувати блокування запису одночасно, тому це також називають блокуваннями з виключною спільністю. У C# рекомендується використовувати клас ReaderWriterLockSlim для виконання функції блокування читання/запису. У деяких випадках кількість читань об'єкта значно перевищує кількість модифікацій, і якщо його просто заблокувати шляхом блокування, це вплине на ефективність зчитування. Якщо використовується блокування читання-запису, кілька потоків можуть читати об'єкт одночасно, і він блокується лише тоді, коли об'єкт зайнятий блокуванням запису. Простіше кажучи, коли потік переходить у режим читання, інші потоки все одно можуть перейти в режим читання, якщо потік хоче перейти в режим запису в цей момент, його потрібно заблокувати. Поки режим читання не виходить. Аналогічно, якщо потік переходить у режим запису, інші потоки будуть заблоковані, незалежно від того, хочуть вони писати чи читати. Існує 2 способи перейти в режим запису/читання: EnterReadLock намагається перейти в режим блокування в режимі запису. TryEnterReadLock (Int32) намагається перейти в режим блокування режиму читання з опцією вибрати цілочисельний тайм-аут. EnterWriteLock намагається увійти у стан блокування режиму запису. TryEnterWriteLock(Int32) намагається увійти в режим блокування режиму запису, і час тайм-ауту можна обрати. Існує 2 способи вийти з режиму запису/читання: ExitReadLock зменшує рекурсивний підрахунок режиму читання і виходить з режиму читання, коли результат дорівнює 0 (нуль). ExitWriteLock зменшує рекурсивний підрахунок у шаблоні запису і виходить із режиму запису, коли результативний рахунок дорівнює 0 (нуль). Ось як його використовувати:
Видно, що потік 3 і потік 4 можуть одночасно переходити в режим читання, а потік 5 — через 5 секунд (тобто після того, як потоки 3 і 4 виходять із блокування читання). Модифікуючи наведений вище код, спочатку відкриваючи 2 потоки в режимі запису, а потім відкриваємо потоки в режимі читання, код виглядає так:
Результати такі:
Як бачите, потік 3 і потік 4 переходять у режим запису, але потік 3 займає блокування запису першим, тому потік 4 має чекати 10 секунд перед входом. Потоки 5 і 6 мають займати блокування читання, тому чекайте, поки потік 4 вийде з блокування запису, перш ніж продовжувати. TryEnterReadLock і TryEnterWriteLock можуть встановити тайм-аут; при переході до цього речення потік блокуватиметься тут, якщо блокування може бути зайняте в цей момент, то поверніть true, якщо тайм-аут ще не зайняли блокування, потім поверніть false, відмовляйтеся від зайнятості блокування і продовжуйте виконувати наступний код безпосередньо. EnterUpgradeableReadLock Клас ReaderWriterLockSlim забезпечує режим читання з можливістю оновлення, який відрізняється від режиму читання тим, що його також можна оновити до режиму запису, викликавши методи EnterWriteLock або TryEnterWriteLock. Тому що лише один потік може бути в режимі оновлення одночасно. Потік, який переходить у режим оновлення, не вплине на потік у режимі читання, тобто коли потік переходить у режим оновлення, будь-яка кількість потоків може одночасно перейти в режим читання без блокування. Якщо кілька потоків вже чекають на отримання блокування запису, запуск EnterUpgradeableReadLock блокуватиме до того часу, поки ці потоки не закінчать тайм-аут або не вийдуть із блокування запису. Наступний код показує, як оновити до блокування запису в режимі оновленого читання.
Вплив блокувань читання/запису на продуктивність очевидний. Наступний тестовий код:
Вищенаведений код імітує роботу 500 завдань, кожне з яких займає потік пулу потоків, з яких 20 — це потоки запису, а 480 — читання. Читання даних займає 10 мс і 100 мс для тестування методу блокування та методу ReaderWriterLockSlim відповідно. Можна зробити оцінку: для ReaderWriterLockSlim можна припустити, що одночасно читають 480 потоків, то він споживає 10 мс, 20 операцій запису займають 2000 мс, тобто час витраченого становить 2010 мс, а для звичайного методу блокування, оскільки всі вони ексклюзивні, 480 операцій читання займають 4800мс + 20 операцій запису 2000мс = 6800мс. Результати показали помітне покращення продуктивності.
|