← Назад к вопросам

Что такое Lock?

1.8 Middle🔥 121 комментариев
#Асинхронность и многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Lock в C#

Lock — это синхронизационный механизм в C#, который используется для обеспечения потокобезопасности при работе с общими ресурсами в многопоточном приложении. Он гарантирует, что только один поток может одновременно выполнять критическую секцию кода.

Как работает Lock

var lockObj = new object();

lock (lockObj)
{
    // Критическая секция
    // Только один поток может здесь находиться
    // Другие потоки ждут, пока текущий поток выйдет из блока
}

Проблема без Lock

public class BankAccount
{
    public decimal Balance { get; set; }

    public void Withdraw(decimal amount)
    {
        if (Balance >= amount)
        {
            Thread.Sleep(100); // имитация задержки
            Balance -= amount;
        }
    }
}

// Использование
var account = new BankAccount { Balance = 1000 };
var tasks = new List<Task>();

// 10 потоков пытаются снять по 200
for (int i = 0; i < 10; i++)
{
    tasks.Add(Task.Run(() => account.Withdraw(200)));
}

Task.WaitAll(tasks.ToArray());
Console.WriteLine($"Balance: {account.Balance}");
// Ожидаемо: 0
// На самом деле: может быть 600, 800 или другое значение!
// Это Race Condition

Решение с Lock

public class BankAccount
{
    private readonly object _lockObj = new object();
    public decimal Balance { get; private set; }

    public void Withdraw(decimal amount)
    {
        lock (_lockObj)
        {
            if (Balance >= amount)
            {
                Thread.Sleep(100);
                Balance -= amount;
                Console.WriteLine($"Снято {amount}, остаток: {Balance}");
            }
            else
            {
                Console.WriteLine($"Недостаточно средств");
            }
        }
    }
}

Варианты синхронизации

ReaderWriterLockSlim для чтения/записи:

private readonly ReaderWriterLockSlim _rw = new();
private decimal _balance = 1000;

public decimal GetBalance()
{
    _rw.EnterReadLock();
    try { return _balance; }
    finally { _rw.ExitReadLock(); }
}

SemaphoreSlim для асинхронного кода:

private readonly SemaphoreSlim _semaphore = new(1);

public async Task WithdrawAsync(decimal amount)
{
    await _semaphore.WaitAsync();
    try { Balance -= amount; }
    finally { _semaphore.Release(); }
}

Interlocked для простых операций:

private int _counter = 0;
Interlocked.Increment(ref _counter);
Interlocked.Add(ref _counter, 5);

Лучшие практики

  • Минимизируй время в lock — выполняй только необходимый код
  • Используй private readonly object для lockObj
  • Избегай вложенных locks — риск дедлока
  • Для async используй SemaphoreSlim вместо lock
  • Профилируй узкие места — lock может быть bottleneck
  • Предпочитай Interlocked операции для простых случаев

Дедлок пример

var lock1 = new object();
var lock2 = new object();

// Thread 1
lock (lock1)
{
    Thread.Sleep(100);
    lock (lock2) { } // Ждет lock2
}

// Thread 2
lock (lock2)
{
    Thread.Sleep(100);
    lock (lock1) { } // Ждет lock1 — ДЕДЛОК!
}

Lock — это базовый инструмент для синхронизации потоков, но требует аккуратности в использовании. Для сложных сценариев рассмотри более высокоуровневые механизмы как async/await, TPL (Task Parallel Library) или использование thread-safe коллекций.