Ответ
Нет, стандартный System.Collections.Generic.Stack<T> не является потокобезопасным. Его методы не защищены от состояния гонки при одновременном вызове из нескольких потоков, что может привести к повреждению внутренней структуры данных, потере элементов или исключениям.
Способы обеспечения потокобезопасности:
-
Явная синхронизация с помощью
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; } } -
Использование
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.
Ответ 18+ 🔞
Да ты что, серьёзно? Ну блядь, классический Stack<T> — это же просто банка с печеньем, которую на всеобщее обозрение поставили. Любой поток может туда руку сунуть, когда захочет, и будет тебе, сука, состояние гонки, как на распродаже в Ашане. Данные повредятся, элементы потеряются, исключения полетят — пиздец, короче.
Как с этим жить, если потоки лезут со всех щелей:
-
Взять в замок
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; } } -
Взять
ConcurrentStack<T>: А это, блядь, уже специально заточенная под многопоточку банка с хитрым механизмом. Там внутри не просто замок, а lock-free алгоритмы, чтоб не тормозить почём зря.using System.Collections.Concurrent; ConcurrentStack<int> concurrentStack = new ConcurrentStack<int>(); // Толкай и вытаскивай из разных потоков — не обосрёшься concurrentStack.Push(10); concurrentStack.Push(20); if (concurrentStack.TryPop(out int poppedItem)) // Главное — используй TryPop, а не Pop { Console.WriteLine("Popped: " + poppedItem); // Может выскочить 20, а может и 10 — как повезёт }Вот тут внимание, ёпта: Хотя каждая операция
PushиTryPopсама по себе атомарна, если ты сделаешь по-дурацки:if(stack.Count>0) stack.Pop()— то между проверкой и действием другой поток может всё испортить. Поэтому толькоTryPop, блядь, только хардкор! Для сложных сценариев — внешняя синхронизация.