ConcurrentStack
ConcurrentStack<T>
는 .NET의 동시성 컬렉션 중 하나로, LIFOLast In, First Out 방식으로 데이터를 관리하는 스택 구조를 제공합니다. 멀티스레드 환경에서도 안전하게 사용할 수 있도록 설계된 ConcurrentStack<T>
는 여러 스레드에서 동시에 데이터를 추가하거나 제거할 때도 안전성을 보장합니다. 이는 스레드 간 동시 접근이 빈번한 환경에서 효율적으로 스택을 사용할 수 있도록 돕습니다.
ConcurrentStack의 특성
LIFO 구조
일반적인 스택처럼 마지막에 추가된 요소가 가장 먼저 제거되는 구조를 따릅니다.
ConcurrentStack<T>
는 멀티스레드 환경에서 스택을 사용하는 경우에 적합하며, 비슷한 기능을 제공하는 다른 동시성 컬렉션(ConcurrentQueue, ConcurrentBag)과의 차별점은 데이터의 처리 순서에 있습니다. ConcurrentStack<T>
는 후입선출 방식으로 데이터를 처리하기 때문에 가장 최근에 추가된 데이터를 우선적으로 처리하고자 할 때 유용합니다.
주요 메서드와 사용법
생성자와 초기화
기본 생성자
빈 ConcurrentStack
을 생성합니다.
ConcurrentStack<int> stack = new ConcurrentStack<int>();
컬렉션으로 초기화
다른 컬렉션을 사용해 ConcurrentStack
을 초기화할 수 있습니다.
int[] numbers = { 1, 2, 3, 4, 5 };
ConcurrentStack<int> stackFromCollection = new ConcurrentStack<int>(numbers);
요소 추가 및 제거
Push
스택의 맨 위에 요소를 추가합니다.
stack.Push(10);
stack.Push(20);
- 여러 요소를 한 번에 추가할 수도 있습니다.
stack.PushRange(new int[] { 30, 40, 50 });
TryPop
스택의 맨 위 요소를 제거하고 반환합니다.
if (stack.TryPop(out int result))
{
Console.WriteLine(result); // 스택에서 꺼낸 요소 출력
}
- 스택이 비어 있는 경우
TryPop
은false
를 반환하고,result
는 기본값으로 설정됩니다.
TryPeek
스택의 맨 위 요소를 제거하지 않고 반환합니다.
if (stack.TryPeek(out int topElement))
{
Console.WriteLine(topElement); // 스택의 맨 위 요소 출력
}
기타 유용한 메서드
IsEmpty
스택이 비어 있는지 확인합니다.
if (stack.IsEmpty)
{
Console.WriteLine("스택이 비어 있습니다.");
}
Count
스택에 있는 요소의 개수를 반환합니다.
int count = stack.Count;
Console.WriteLine($"스택에 있는 요소의 개수: {count}");
ToArray
스택의 요소를 배열로 복사합니다.
int[] array = stack.ToArray();
- 스택의 순서가 반영된 배열이 생성됩니다.
기본 사용 예제
다음은 ConcurrentStack<T>
의 기본적인 사용 예제를 보여줍니다.
using System;
using System.Collections.Concurrent;
class Program
{
static void Main()
{
// ConcurrentStack 생성
ConcurrentStack<int> stack = new ConcurrentStack<int>();
// 요소 추가
stack.Push(1);
stack.Push(2);
stack.Push(3);
// 요소 제거 및 출력
if (stack.TryPop(out int result))
{
Console.WriteLine($"Pop된 요소: {result}"); // 출력: Pop된 요소: 3
}
// 스택의 맨 위 요소 확인
if (stack.TryPeek(out int topElement))
{
Console.WriteLine($"현재 맨 위 요소: {topElement}"); // 출력: 현재 맨 위 요소: 2
}
// 스택의 모든 요소 출력
Console.WriteLine("스택의 모든 요소:");
foreach (var item in stack)
{
Console.WriteLine(item); // 출력: 2, 1
}
}
}
위 예제에서는 ConcurrentStack
에 요소를 추가하고, 맨 위의 요소를 제거하거나 확인하는 기본적인 작업을 보여줍니다.
롤백 가능한 작업 관리
ConcurrentStack<T>
는 스택에 데이터를 추가했다가 롤백할 수 있도록 설계하기에 적합합니다. 예를 들어, 트랜잭션 기반 시스템에서 작업을 롤백해야 하는 상황에서, 처리된 작업을 순차적으로 취소하는 데 활용할 수 있습니다.
using System;
using System.Collections.Concurrent;
public class TransactionManager
{
private ConcurrentStack<Action> rollbackActions = new ConcurrentStack<Action>();
public void AddTransaction(Action transaction, Action rollback)
{
try
{
transaction.Invoke();
rollbackActions.Push(rollback); // 롤백 작업 저장
}
catch
{
Rollback();
}
}
public void Rollback()
{
while (rollbackActions.TryPop(out Action rollback))
{
rollback.Invoke(); // 롤백 작업 실행
Console.WriteLine("Rolled back a transaction.");
}
}
}
- 작업이 실패한 경우
Rollback
메서드를 호출하여 저장된 작업들을 순차적으로 롤백함으로써 데이터 일관성을 유지합니다.
캐싱과 데이터 재사용
ConcurrentStack<T>
는 다중 스레드 환경에서 반복적으로 필요한 데이터를 캐싱하고 재사용하는 데 유리합니다. 예를 들어, 데이터베이스 연결이나 HTTP 요청 객체 등을 미리 만들어 두고 스택에 넣어두면 필요할 때마다 재사용할 수 있습니다.
using System;
using System.Collections.Concurrent;
public class ConnectionPool
{
private ConcurrentStack<DatabaseConnection> connectionStack = new ConcurrentStack<DatabaseConnection>();
public ConnectionPool(int initialSize)
{
for (int i = 0; i < initialSize; i++)
{
connectionStack.Push(new DatabaseConnection()); // 초기 DB 연결 생성
}
}
public DatabaseConnection GetConnection()
{
if (connectionStack.TryPop(out DatabaseConnection connection))
{
return connection;
}
return new DatabaseConnection(); // 스택이 비어있으면 새로운 연결 생성
}
public void ReleaseConnection(DatabaseConnection connection)
{
connectionStack.Push(connection); // 사용 후 스택에 다시 추가
}
}
public class DatabaseConnection
{
public void Open() { /* 연결 열기 */ }
public void Close() { /* 연결 닫기 */ }
}
- DB 연결 객체를 풀에서 꺼내 사용하고 다시 반납하여 자원 재사용을 최적화합니다.
- 연결이 빈번한 애플리케이션에서 성능을 크게 개선할 수 있습니다.