Является ли коллекция Stack потокобезопасной?

«Является ли коллекция Stack потокобезопасной?» — вопрос из категории Многопоточность, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Нет, стандартный System.Collections.Generic.Stack<T> не является потокобезопасным. Его методы не защищены от состояния гонки при одновременном вызове из нескольких потоков, что может привести к повреждению внутренней структуры данных, потере элементов или исключениям.

Способы обеспечения потокобезопасности:

  1. Явная синхронизация с помощью lock: Самый простой способ для контролируемого доступа.

    private Stack<int> _stack = new Stack<int>();
    private readonly object _syncLock = new object();
    
    public void PushSafely(int item)
    {
        lock (_syncLock)
        {
            _stack.Push(item);
        }
    }
    
    public bool TryPopSafely(out int result)
    {
        lock (_syncLock)
        {
            if (_stack.Count > 0)
            {
                result = _stack.Pop();
                return true;
            }
            result = default;
            return false;
        }
    }
  2. Использование ConcurrentStack<T>: Специализированная потокобезопасная коллекция из пространства имён System.Collections.Concurrent. Она использует эффективные lock-free алгоритмы для отдельных операций Push и TryPop.

    using System.Collections.Concurrent;
    
    ConcurrentStack<int> concurrentStack = new ConcurrentStack<int>();
    
    // Потокобезопасные операции
    concurrentStack.Push(10);
    concurrentStack.Push(20);
    
    if (concurrentStack.TryPop(out int poppedItem))
    {
        Console.WriteLine("Popped: " + poppedItem); // Может быть 20 или 10
    }

    Важно: Хотя ConcurrentStack гарантирует атомарность отдельных операций, последовательность вызовов (например, if(stack.Count>0) stack.Pop()) по-прежнему требует внешней синхронизации, так как между проверкой и действием состояние может измениться другим потоком. Для таких сценариев используйте TryPop.